feat: add android init and ios init commands (#4942)
6
.changes/mobile-init.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cli.rs": minor
|
||||
"cli.js": minor
|
||||
---
|
||||
|
||||
Added `android init` and `ios init` commands.
|
||||
642
tooling/cli/Cargo.lock
generated
@@ -26,7 +26,15 @@ include = [
|
||||
name = "cargo-tauri"
|
||||
path = "src/main.rs"
|
||||
|
||||
[patch.crates-io]
|
||||
bossy = { git = "https://github.com/lucasfernog/bossy", branch = "fix/winapi-features" }
|
||||
|
||||
[dependencies]
|
||||
# cargo-mobile = { path = "../../../cargo-mobile/", default-features = false }
|
||||
cargo-mobile = { git = "https://github.com/tauri-apps/cargo-mobile", branch = "feat/library", default-features = false }
|
||||
bossy = "0.2"
|
||||
textwrap = { version = "0.11.0", features = ["term_size"] }
|
||||
thiserror = "1"
|
||||
clap = { version = "3.2", features = [ "derive" ] }
|
||||
anyhow = "1.0"
|
||||
tauri-bundler = { version = "1.0.5", path = "../bundler" }
|
||||
|
||||
@@ -3,22 +3,74 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::{create_dir_all, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use handlebars::{to_json, Handlebars};
|
||||
use include_dir::Dir;
|
||||
use serde::Serialize;
|
||||
use serde_json::value::{Map, Value as JsonValue};
|
||||
|
||||
pub fn render<P: AsRef<Path>>(
|
||||
/// Map of template variable names and values.
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct JsonMap(Map<String, JsonValue>);
|
||||
|
||||
impl Default for JsonMap {
|
||||
fn default() -> Self {
|
||||
Self(Map::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonMap {
|
||||
pub fn insert(&mut self, name: &str, value: impl Serialize) {
|
||||
self.0.insert(name.to_owned(), to_json(value));
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &Map<String, JsonValue> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<P: AsRef<Path>, D: Serialize>(
|
||||
handlebars: &Handlebars<'_>,
|
||||
data: &BTreeMap<&str, serde_json::Value>,
|
||||
data: &D,
|
||||
dir: &Dir<'_>,
|
||||
out_dir: P,
|
||||
) -> crate::Result<()> {
|
||||
create_dir_all(out_dir.as_ref().join(dir.path()))?;
|
||||
let out_dir = out_dir.as_ref();
|
||||
let mut created_dirs = Vec::new();
|
||||
render_with_generator(
|
||||
handlebars,
|
||||
data,
|
||||
dir,
|
||||
&out_dir,
|
||||
&mut |file_path: &PathBuf| {
|
||||
let path = out_dir.join(file_path);
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
File::create(path)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_with_generator<
|
||||
P: AsRef<Path>,
|
||||
D: Serialize,
|
||||
F: FnMut(&PathBuf) -> std::io::Result<File>,
|
||||
>(
|
||||
handlebars: &Handlebars<'_>,
|
||||
data: &D,
|
||||
dir: &Dir<'_>,
|
||||
out_dir: P,
|
||||
out_file_generator: &mut F,
|
||||
) -> crate::Result<()> {
|
||||
let out_dir = out_dir.as_ref();
|
||||
for file in dir.files() {
|
||||
let mut file_path = file.path().to_path_buf();
|
||||
// cargo for some reason ignores the /templates folder packaging when it has a Cargo.toml file inside
|
||||
@@ -28,7 +80,7 @@ pub fn render<P: AsRef<Path>>(
|
||||
file_path.set_extension("toml");
|
||||
}
|
||||
}
|
||||
let mut output_file = File::create(out_dir.as_ref().join(file_path))?;
|
||||
let mut output_file = out_file_generator(&file_path)?;
|
||||
if let Some(utf8) = file.contents_utf8() {
|
||||
handlebars
|
||||
.render_template_to_write(utf8, &data, &mut output_file)
|
||||
@@ -38,7 +90,7 @@ pub fn render<P: AsRef<Path>>(
|
||||
}
|
||||
}
|
||||
for dir in dir.dirs() {
|
||||
render(handlebars, data, dir, out_dir.as_ref())?;
|
||||
render_with_generator(handlebars, data, dir, out_dir, out_file_generator)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ mod helpers;
|
||||
mod info;
|
||||
mod init;
|
||||
mod interface;
|
||||
mod mobile;
|
||||
mod plugin;
|
||||
mod signer;
|
||||
|
||||
@@ -66,6 +67,9 @@ enum Commands {
|
||||
Init(init::Options),
|
||||
Plugin(plugin::Cli),
|
||||
Signer(signer::Cli),
|
||||
Android(mobile::android::Cli),
|
||||
#[cfg(target_os = "macos")]
|
||||
Ios(mobile::ios::Cli),
|
||||
}
|
||||
|
||||
fn format_error<I: IntoApp>(err: clap::Error) -> clap::Error {
|
||||
@@ -164,6 +168,9 @@ where
|
||||
Commands::Init(options) => init::command(options)?,
|
||||
Commands::Plugin(cli) => plugin::command(cli)?,
|
||||
Commands::Signer(cli) => signer::command(cli)?,
|
||||
Commands::Android(cli) => mobile::android::command(cli)?,
|
||||
#[cfg(target_os = "macos")]
|
||||
Commands::Ios(cli) => mobile::ios::command(cli)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
36
tooling/cli/src/mobile/android.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use super::init::{command as init_command, Options as InitOptions, Target as InitTarget};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) mod project;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author,
|
||||
version,
|
||||
about = "Android commands",
|
||||
subcommand_required(true),
|
||||
arg_required_else_help(true)
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init(InitOptions),
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(options, InitTarget::Android)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
184
tooling/cli/src/mobile/android/project.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::helpers::template;
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
config::{Config, Metadata},
|
||||
env::Env,
|
||||
ndk,
|
||||
target::Target,
|
||||
},
|
||||
dot_cargo, os,
|
||||
target::TargetTrait as _,
|
||||
util::{
|
||||
self,
|
||||
cli::{Report, TextWrapper},
|
||||
ln, prefix_path,
|
||||
},
|
||||
};
|
||||
use handlebars::Handlebars;
|
||||
use include_dir::{include_dir, Dir};
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/android");
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("failed to run rustup: {0}")]
|
||||
RustupFailed(bossy::Error),
|
||||
#[error("failed to process template: {0}")]
|
||||
TemplateProcessingFailed(String),
|
||||
#[error("failed to create directory at {path}: {cause}")]
|
||||
DirectoryCreationFailed {
|
||||
path: PathBuf,
|
||||
cause: std::io::Error,
|
||||
},
|
||||
#[error("failed to symlink asset directory")]
|
||||
AssetDirSymlinkFailed,
|
||||
#[error(transparent)]
|
||||
DotCargoGenFailed(ndk::MissingToolError),
|
||||
#[error("failed to copy {src} to {dest}: {cause}")]
|
||||
FileCopyFailed {
|
||||
src: PathBuf,
|
||||
dest: PathBuf,
|
||||
cause: std::io::Error,
|
||||
},
|
||||
#[error("asset source {0} is invalid")]
|
||||
AssetSourceInvalid(PathBuf),
|
||||
}
|
||||
|
||||
pub fn gen(
|
||||
config: &Config,
|
||||
metadata: &Metadata,
|
||||
env: &Env,
|
||||
(handlebars, mut map): (Handlebars, template::JsonMap),
|
||||
wrapper: &TextWrapper,
|
||||
dot_cargo: &mut dot_cargo::DotCargo,
|
||||
) -> Result<(), Error> {
|
||||
println!("Installing Android toolchains...");
|
||||
Target::install_all().map_err(Error::RustupFailed)?;
|
||||
println!("Generating Android Studio project...");
|
||||
let dest = config.project_dir();
|
||||
let asset_packs = metadata.asset_packs().unwrap_or_default();
|
||||
|
||||
map.insert(
|
||||
"root-dir-rel",
|
||||
Path::new(&os::replace_path_separator(
|
||||
util::relativize_path(config.app().root_dir(), config.project_dir()).into_os_string(),
|
||||
)),
|
||||
);
|
||||
map.insert("root-dir", config.app().root_dir());
|
||||
map.insert("targets", Target::all().values().collect::<Vec<_>>());
|
||||
map.insert("target-names", Target::all().keys().collect::<Vec<_>>());
|
||||
map.insert(
|
||||
"arches",
|
||||
Target::all()
|
||||
.values()
|
||||
.map(|target| target.arch)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
map.insert("android-app-plugins", metadata.app_plugins());
|
||||
map.insert(
|
||||
"android-project-dependencies",
|
||||
metadata.project_dependencies(),
|
||||
);
|
||||
map.insert("android-app-dependencies", metadata.app_dependencies());
|
||||
map.insert(
|
||||
"android-app-dependencies-platform",
|
||||
metadata.app_dependencies_platform(),
|
||||
);
|
||||
map.insert(
|
||||
"has-code",
|
||||
metadata.project_dependencies().is_some()
|
||||
|| metadata.app_dependencies().is_some()
|
||||
|| metadata.app_dependencies_platform().is_some(),
|
||||
);
|
||||
map.insert(
|
||||
"asset-packs",
|
||||
asset_packs
|
||||
.iter()
|
||||
.map(|p| p.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
map.insert("windows", cfg!(windows));
|
||||
|
||||
let domain = config.app().reverse_domain().replace('.', "/");
|
||||
let package_path = format!("java/{}/{}", domain, config.app().name());
|
||||
|
||||
let mut created_dirs = Vec::new();
|
||||
template::render_with_generator(
|
||||
&handlebars,
|
||||
map.inner(),
|
||||
&TEMPLATE_DIR,
|
||||
&dest,
|
||||
&mut |path| {
|
||||
let path = if path.extension() == Some(OsStr::new("kt")) {
|
||||
let parent = path.parent().unwrap();
|
||||
let file_name = path.file_name().unwrap();
|
||||
let out_dir = dest.join(parent).join(&package_path);
|
||||
out_dir.join(file_name)
|
||||
} else {
|
||||
dest.join(path)
|
||||
};
|
||||
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
fs::create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
|
||||
fs::File::create(path)
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::TemplateProcessingFailed(e.to_string()))?;
|
||||
|
||||
if !asset_packs.is_empty() {
|
||||
Report::action_request(
|
||||
"When running from Android Studio, you must first set your deployment option to \"APK from app bundle\".",
|
||||
"Android Studio will not be able to find your asset packs otherwise. The option can be found under \"Run > Edit Configurations > Deploy\"."
|
||||
).print(wrapper);
|
||||
}
|
||||
|
||||
let source_dest = dest.join("app");
|
||||
for source in metadata.app_sources() {
|
||||
let source_src = config.app().root_dir().join(&source);
|
||||
let source_file = source_src
|
||||
.file_name()
|
||||
.ok_or_else(|| Error::AssetSourceInvalid(source_src.clone()))?;
|
||||
fs::copy(&source_src, source_dest.join(source_file)).map_err(|cause| {
|
||||
Error::FileCopyFailed {
|
||||
src: source_src,
|
||||
dest: source_dest.clone(),
|
||||
cause,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
let dest = prefix_path(dest, "app/src/main/");
|
||||
fs::create_dir_all(&dest).map_err(|cause| Error::DirectoryCreationFailed {
|
||||
path: dest.clone(),
|
||||
cause,
|
||||
})?;
|
||||
os::ln::force_symlink_relative(config.app().asset_dir(), dest, ln::TargetStyle::Directory)
|
||||
.map_err(|_| Error::AssetDirSymlinkFailed)?;
|
||||
|
||||
{
|
||||
for target in Target::all().values() {
|
||||
dot_cargo.insert_target(
|
||||
target.triple.to_owned(),
|
||||
target
|
||||
.generate_cargo_config(config, env)
|
||||
.map_err(Error::DotCargoGenFailed)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
435
tooling/cli/src/mobile/init.rs
Normal file
@@ -0,0 +1,435 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::helpers::{app_paths::tauri_dir, template::JsonMap};
|
||||
use crate::Result;
|
||||
use cargo_mobile::{
|
||||
android,
|
||||
config::{
|
||||
self,
|
||||
metadata::{self, Metadata},
|
||||
Config,
|
||||
},
|
||||
dot_cargo,
|
||||
init::{DOT_FIRST_INIT_CONTENTS, DOT_FIRST_INIT_FILE_NAME},
|
||||
opts,
|
||||
os::code_command,
|
||||
util::{
|
||||
self,
|
||||
cli::{Report, TextWrapper},
|
||||
},
|
||||
};
|
||||
use clap::Parser;
|
||||
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};
|
||||
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use opts::{NonInteractive, OpenInEditor, ReinstallDeps, SkipDevTools};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about = "Initializes a Tauri Android project")]
|
||||
pub struct Options {
|
||||
/// Skip prompting for values
|
||||
#[clap(long)]
|
||||
ci: bool,
|
||||
}
|
||||
|
||||
pub fn command(mut options: Options, target: Target) -> Result<()> {
|
||||
options.ci = options.ci || std::env::var("CI").is_ok();
|
||||
|
||||
let wrapper = TextWrapper::with_splitter(textwrap::termwidth(), textwrap::NoHyphenation);
|
||||
exec(
|
||||
target,
|
||||
&wrapper,
|
||||
options.ci.into(),
|
||||
SkipDevTools::No,
|
||||
ReinstallDeps::Yes,
|
||||
OpenInEditor::No,
|
||||
tauri_dir(),
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{:#}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
ConfigLoadOrGen(config::LoadOrGenError),
|
||||
#[error("failed to init first init file {path}: {cause}")]
|
||||
DotFirstInitWrite { path: PathBuf, cause: io::Error },
|
||||
#[error("failed to create asset dir {asset_dir}: {cause}")]
|
||||
AssetDirCreation {
|
||||
asset_dir: PathBuf,
|
||||
cause: io::Error,
|
||||
},
|
||||
#[error("failed to install LLDB VS Code extension: {0}")]
|
||||
LldbExtensionInstall(bossy::Error),
|
||||
#[error(transparent)]
|
||||
DotCargoLoad(dot_cargo::LoadError),
|
||||
#[error(transparent)]
|
||||
HostTargetTripleDetection(util::HostTargetTripleError),
|
||||
#[error(transparent)]
|
||||
Metadata(metadata::Error),
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error(transparent)]
|
||||
IosInit(super::ios::project::Error),
|
||||
#[error(transparent)]
|
||||
AndroidEnv(android::env::Error),
|
||||
#[error(transparent)]
|
||||
AndroidInit(super::android::project::Error),
|
||||
#[error(transparent)]
|
||||
DotCargoWrite(dot_cargo::WriteError),
|
||||
#[error("failed to delete first init file {path}: {cause}")]
|
||||
DotFirstInitDelete { path: PathBuf, cause: io::Error },
|
||||
#[error(transparent)]
|
||||
OpenInEditor(util::OpenInEditorError),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Target {
|
||||
Android,
|
||||
#[cfg(target_os = "macos")]
|
||||
Ios,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
target: Target,
|
||||
wrapper: &TextWrapper,
|
||||
non_interactive: NonInteractive,
|
||||
skip_dev_tools: SkipDevTools,
|
||||
#[allow(unused_variables)] reinstall_deps: ReinstallDeps,
|
||||
open_in_editor: OpenInEditor,
|
||||
cwd: impl AsRef<Path>,
|
||||
) -> Result<Config, Error> {
|
||||
let cwd = cwd.as_ref();
|
||||
let (config, config_origin) =
|
||||
Config::load_or_gen(cwd, non_interactive, wrapper).map_err(Error::ConfigLoadOrGen)?;
|
||||
let dot_first_init_path = config.app().root_dir().join(DOT_FIRST_INIT_FILE_NAME);
|
||||
let dot_first_init_exists = {
|
||||
let dot_first_init_exists = dot_first_init_path.exists();
|
||||
if config_origin.freshly_minted() && !dot_first_init_exists {
|
||||
// indicate first init is ongoing, so that if we error out and exit
|
||||
// the next init will know to still use `WildWest` filtering
|
||||
log::info!("creating first init dot file at {:?}", dot_first_init_path);
|
||||
fs::write(&dot_first_init_path, DOT_FIRST_INIT_CONTENTS).map_err(|cause| {
|
||||
Error::DotFirstInitWrite {
|
||||
path: dot_first_init_path.clone(),
|
||||
cause,
|
||||
}
|
||||
})?;
|
||||
true
|
||||
} else {
|
||||
dot_first_init_exists
|
||||
}
|
||||
};
|
||||
|
||||
let asset_dir = config.app().asset_dir();
|
||||
if !asset_dir.is_dir() {
|
||||
fs::create_dir_all(&asset_dir).map_err(|cause| Error::AssetDirCreation { asset_dir, cause })?;
|
||||
}
|
||||
if skip_dev_tools.no() && util::command_present("code").unwrap_or_default() {
|
||||
let mut command = code_command();
|
||||
command.add_args(&["--install-extension", "vadimcn.vscode-lldb"]);
|
||||
if non_interactive.yes() {
|
||||
command.add_arg("--force");
|
||||
}
|
||||
command
|
||||
.run_and_wait()
|
||||
.map_err(Error::LldbExtensionInstall)?;
|
||||
}
|
||||
let mut dot_cargo = dot_cargo::DotCargo::load(config.app()).map_err(Error::DotCargoLoad)?;
|
||||
// Mysteriously, builds that don't specify `--target` seem to fight over
|
||||
// the build cache with builds that use `--target`! This means that
|
||||
// alternating between i.e. `cargo run` and `cargo apple run` would
|
||||
// result in clean builds being made each time you switched... which is
|
||||
// pretty nightmarish. Specifying `build.target` in `.cargo/config`
|
||||
// fortunately has the same effect as specifying `--target`, so now we can
|
||||
// `cargo run` with peace of mind!
|
||||
//
|
||||
// This behavior could be explained here:
|
||||
// https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
|
||||
dot_cargo
|
||||
.set_default_target(util::host_target_triple().map_err(Error::HostTargetTripleDetection)?);
|
||||
|
||||
let metadata = Metadata::load(config.app().root_dir()).map_err(Error::Metadata)?;
|
||||
|
||||
// Generate Xcode project
|
||||
#[cfg(target_os = "macos")]
|
||||
if target == Target::Ios && metadata.apple().supported() {
|
||||
super::ios::project::gen(
|
||||
config.apple(),
|
||||
metadata.apple(),
|
||||
handlebars(&config),
|
||||
wrapper,
|
||||
non_interactive,
|
||||
skip_dev_tools,
|
||||
reinstall_deps,
|
||||
)
|
||||
.map_err(Error::IosInit)?;
|
||||
} else {
|
||||
println!("Skipping iOS init, since it's marked as unsupported in your Cargo.toml metadata");
|
||||
}
|
||||
|
||||
// Generate Android Studio project
|
||||
if target == Target::Android && metadata.android().supported() {
|
||||
match android::env::Env::new() {
|
||||
Ok(env) => super::android::project::gen(
|
||||
config.android(),
|
||||
metadata.android(),
|
||||
&env,
|
||||
handlebars(&config),
|
||||
wrapper,
|
||||
&mut dot_cargo,
|
||||
)
|
||||
.map_err(Error::AndroidInit)?,
|
||||
Err(err) => {
|
||||
if err.sdk_or_ndk_issue() {
|
||||
Report::action_request(
|
||||
" to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `cargo mobile init`!",
|
||||
err,
|
||||
)
|
||||
.print(wrapper);
|
||||
} else {
|
||||
return Err(Error::AndroidEnv(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Skipping Android init, since it's marked as unsupported in your Cargo.toml metadata");
|
||||
}
|
||||
|
||||
dot_cargo
|
||||
.write(config.app())
|
||||
.map_err(Error::DotCargoWrite)?;
|
||||
if dot_first_init_exists {
|
||||
log::info!("deleting first init dot file at {:?}", dot_first_init_path);
|
||||
fs::remove_file(&dot_first_init_path).map_err(|cause| Error::DotFirstInitDelete {
|
||||
path: dot_first_init_path,
|
||||
cause,
|
||||
})?;
|
||||
}
|
||||
Report::victory(
|
||||
"Project generated successfully!",
|
||||
"Make cool apps! 🌻 🐕 🎉",
|
||||
)
|
||||
.print(wrapper);
|
||||
if open_in_editor.yes() {
|
||||
util::open_in_editor(cwd).map_err(Error::OpenInEditor)?;
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn handlebars(config: &Config) -> (Handlebars<'static>, JsonMap) {
|
||||
let mut h = Handlebars::new();
|
||||
h.register_escape_fn(handlebars::no_escape);
|
||||
|
||||
h.register_helper("html-escape", Box::new(html_escape));
|
||||
h.register_helper("join", Box::new(join));
|
||||
h.register_helper("quote-and-join", Box::new(quote_and_join));
|
||||
h.register_helper(
|
||||
"quote-and-join-colon-prefix",
|
||||
Box::new(quote_and_join_colon_prefix),
|
||||
);
|
||||
h.register_helper("snake-case", Box::new(snake_case));
|
||||
h.register_helper("reverse-domain", Box::new(reverse_domain));
|
||||
h.register_helper(
|
||||
"reverse-domain-snake-case",
|
||||
Box::new(reverse_domain_snake_case),
|
||||
);
|
||||
// don't mix these up or very bad things will happen to all of us
|
||||
h.register_helper("prefix-path", Box::new(prefix_path));
|
||||
h.register_helper("unprefix-path", Box::new(unprefix_path));
|
||||
|
||||
let mut map = JsonMap::default();
|
||||
map.insert("app", config.app());
|
||||
#[cfg(target_os = "macos")]
|
||||
map.insert("apple", config.apple());
|
||||
map.insert("android", config.android());
|
||||
|
||||
(h, map)
|
||||
}
|
||||
|
||||
fn get_str<'a>(helper: &'a Helper) -> &'a str {
|
||||
helper
|
||||
.param(0)
|
||||
.and_then(|v| v.value().as_str())
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
fn get_str_array<'a>(
|
||||
helper: &'a Helper,
|
||||
formatter: impl Fn(&str) -> String,
|
||||
) -> Option<Vec<String>> {
|
||||
helper.param(0).and_then(|v| {
|
||||
v.value().as_array().and_then(|arr| {
|
||||
arr
|
||||
.iter()
|
||||
.map(|val| {
|
||||
val.as_str().map(
|
||||
#[allow(clippy::redundant_closure)]
|
||||
|s| formatter(s),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn html_escape(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(&handlebars::html_escape(get_str(helper)))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn join(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| s.to_string())
|
||||
.ok_or_else(|| RenderError::new("`join` helper wasn't given an array"))?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn quote_and_join(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| format!("{:?}", s))
|
||||
.ok_or_else(|| RenderError::new("`quote-and-join` helper wasn't given an array"))?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn quote_and_join_colon_prefix(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
&get_str_array(helper, |s| format!("{:?}", format!(":{}", s)))
|
||||
.ok_or_else(|| {
|
||||
RenderError::new("`quote-and-join-colon-prefix` helper wasn't given an array")
|
||||
})?
|
||||
.join(", "),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn snake_case(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
use heck::ToSnekCase as _;
|
||||
out
|
||||
.write(&get_str(helper).to_snek_case())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn reverse_domain(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(&util::reverse_domain(get_str(helper)))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn reverse_domain_snake_case(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
use heck::ToSnekCase as _;
|
||||
out
|
||||
.write(&util::reverse_domain(get_str(helper)).to_snek_case())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn app_root(ctx: &Context) -> Result<&str, RenderError> {
|
||||
let app_root = ctx
|
||||
.data()
|
||||
.get("app")
|
||||
.ok_or_else(|| RenderError::new("`app` missing from template data."))?
|
||||
.get("root-dir")
|
||||
.ok_or_else(|| RenderError::new("`app.root-dir` missing from template data."))?;
|
||||
app_root
|
||||
.as_str()
|
||||
.ok_or_else(|| RenderError::new("`app.root-dir` contained invalid UTF-8."))
|
||||
}
|
||||
|
||||
fn prefix_path(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
util::prefix_path(app_root(ctx)?, get_str(helper))
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
RenderError::new(
|
||||
"Either the `app.root-dir` or the specified path contained invalid UTF-8.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn unprefix_path(
|
||||
helper: &Helper,
|
||||
_: &Handlebars,
|
||||
ctx: &Context,
|
||||
_: &mut RenderContext,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
out
|
||||
.write(
|
||||
util::unprefix_path(app_root(ctx)?, get_str(helper))
|
||||
.map_err(|_| {
|
||||
RenderError::new("Attempted to unprefix a path that wasn't in the app root dir.")
|
||||
})?
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
RenderError::new(
|
||||
"Either the `app.root-dir` or the specified path contained invalid UTF-8.",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
36
tooling/cli/src/mobile/ios.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use super::init::{command as init_command, Options as InitOptions, Target as InitTarget};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) mod project;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author,
|
||||
version,
|
||||
about = "iOS commands",
|
||||
subcommand_required(true),
|
||||
arg_required_else_help(true)
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init(InitOptions),
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(options, InitTarget::Ios)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
193
tooling/cli/src/mobile/ios/project.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::helpers::template;
|
||||
use cargo_mobile::{
|
||||
apple::{
|
||||
config::{Config, Metadata},
|
||||
deps, rust_version_check,
|
||||
target::Target,
|
||||
},
|
||||
opts,
|
||||
target::TargetTrait as _,
|
||||
util::{self, cli::TextWrapper, ln},
|
||||
};
|
||||
use handlebars::Handlebars;
|
||||
use include_dir::{include_dir, Dir};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fs::{create_dir_all, File},
|
||||
path::{Component, PathBuf},
|
||||
};
|
||||
|
||||
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/ios");
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Rustup(bossy::Error),
|
||||
#[error(transparent)]
|
||||
RustVersionCheck(util::RustVersionError),
|
||||
#[error("failed to install Apple dependencies: {0}")]
|
||||
DepsInstall(deps::Error),
|
||||
#[error("failed to process template: {0}")]
|
||||
TemplateProcessing(String),
|
||||
#[error("failed to symlink asset directory")]
|
||||
AssetDirSymlink,
|
||||
#[error("failed to create directory at {path}: {cause}")]
|
||||
DirectoryCreation {
|
||||
path: PathBuf,
|
||||
cause: std::io::Error,
|
||||
},
|
||||
#[error("failed to run `xcodegen`: {0}")]
|
||||
Xcodegen(bossy::Error),
|
||||
#[error("failed to run `pod install`: {0}")]
|
||||
PodInstall(bossy::Error),
|
||||
}
|
||||
|
||||
// unprefixed app_root seems pretty dangerous!!
|
||||
// TODO: figure out what cargo-mobile meant by that
|
||||
pub fn gen(
|
||||
config: &Config,
|
||||
metadata: &Metadata,
|
||||
(handlebars, mut map): (Handlebars, template::JsonMap),
|
||||
wrapper: &TextWrapper,
|
||||
non_interactive: opts::NonInteractive,
|
||||
skip_dev_tools: opts::SkipDevTools,
|
||||
reinstall_deps: opts::ReinstallDeps,
|
||||
) -> Result<(), Error> {
|
||||
println!("Installing iOS toolchains...");
|
||||
Target::install_all().map_err(Error::Rustup)?;
|
||||
rust_version_check(wrapper).map_err(Error::RustVersionCheck)?;
|
||||
|
||||
deps::install_all(wrapper, non_interactive, skip_dev_tools, reinstall_deps)
|
||||
.map_err(Error::DepsInstall)?;
|
||||
|
||||
let dest = config.project_dir();
|
||||
let rel_prefix = util::relativize_path(config.app().root_dir(), &dest);
|
||||
let source_dirs = vec![rel_prefix.join("src")];
|
||||
|
||||
let asset_catalogs = metadata.ios().asset_catalogs().unwrap_or_default();
|
||||
let ios_pods = metadata.ios().pods().unwrap_or_default();
|
||||
let macos_pods = metadata.macos().pods().unwrap_or_default();
|
||||
|
||||
let default_archs = [String::from("arm64"), String::from("x86_64")];
|
||||
|
||||
map.insert("file-groups", &source_dirs);
|
||||
map.insert("ios-frameworks", metadata.ios().frameworks());
|
||||
map.insert(
|
||||
"ios-valid-archs",
|
||||
metadata.ios().valid_archs().unwrap_or(&default_archs),
|
||||
);
|
||||
map.insert("ios-vendor-frameworks", metadata.ios().vendor_frameworks());
|
||||
map.insert("ios-vendor-sdks", metadata.ios().vendor_sdks());
|
||||
map.insert("macos-frameworks", metadata.macos().frameworks());
|
||||
map.insert(
|
||||
"macos-vendor-frameworks",
|
||||
metadata.macos().vendor_frameworks(),
|
||||
);
|
||||
map.insert("macos-vendor-sdks", metadata.macos().vendor_frameworks());
|
||||
map.insert("asset-catalogs", asset_catalogs);
|
||||
map.insert("ios-pods", ios_pods);
|
||||
map.insert("macos-pods", macos_pods);
|
||||
map.insert(
|
||||
"ios-additional-targets",
|
||||
metadata.ios().additional_targets(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-additional-targets",
|
||||
metadata.macos().additional_targets(),
|
||||
);
|
||||
map.insert("ios-pre-build-scripts", metadata.ios().pre_build_scripts());
|
||||
map.insert(
|
||||
"ios-post-compile-scripts",
|
||||
metadata.ios().post_compile_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"ios-post-build-scripts",
|
||||
metadata.ios().post_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-pre-build-scripts",
|
||||
metadata.macos().pre_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-post-compile-scripts",
|
||||
metadata.macos().post_compile_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-post-build-scripts",
|
||||
metadata.macos().post_build_scripts(),
|
||||
);
|
||||
map.insert(
|
||||
"ios-command-line-arguments",
|
||||
metadata.ios().command_line_arguments(),
|
||||
);
|
||||
map.insert(
|
||||
"macos-command-line-arguments",
|
||||
metadata.macos().command_line_arguments(),
|
||||
);
|
||||
|
||||
let mut created_dirs = Vec::new();
|
||||
template::render_with_generator(
|
||||
&handlebars,
|
||||
map.inner(),
|
||||
&TEMPLATE_DIR,
|
||||
&dest,
|
||||
&mut |path| {
|
||||
let mut components: Vec<_> = path.components().collect();
|
||||
let mut new_component = None;
|
||||
for component in &mut components {
|
||||
if let Component::Normal(c) = component {
|
||||
let c = c.to_string_lossy();
|
||||
if c.contains("{{app.name}}") {
|
||||
new_component.replace(OsString::from(
|
||||
&c.replace("{{app.name}}", config.app().name()),
|
||||
));
|
||||
*component = Component::Normal(new_component.as_ref().unwrap());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let path = dest.join(components.iter().collect::<PathBuf>());
|
||||
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
if !created_dirs.contains(&parent) {
|
||||
create_dir_all(&parent)?;
|
||||
created_dirs.push(parent);
|
||||
}
|
||||
|
||||
File::create(path)
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::TemplateProcessing(e.to_string()))?;
|
||||
|
||||
ln::force_symlink_relative(config.app().asset_dir(), &dest, ln::TargetStyle::Directory)
|
||||
.map_err(|_| Error::AssetDirSymlink)?;
|
||||
|
||||
// Create all asset catalog directories if they don't already exist
|
||||
for dir in asset_catalogs {
|
||||
std::fs::create_dir_all(dir).map_err(|cause| Error::DirectoryCreation {
|
||||
path: dest.clone(),
|
||||
cause,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Note that Xcode doesn't always reload the project nicely; reopening is
|
||||
// often necessary.
|
||||
println!("Generating Xcode project...");
|
||||
bossy::Command::impure("xcodegen")
|
||||
.with_args(&["generate", "--spec"])
|
||||
.with_arg(dest.join("project.yml"))
|
||||
.run_and_wait()
|
||||
.map_err(Error::Xcodegen)?;
|
||||
|
||||
if !ios_pods.is_empty() || !macos_pods.is_empty() {
|
||||
bossy::Command::impure_parse("pod install")
|
||||
.with_arg(format!("--project-directory={}", dest.display()))
|
||||
.run_and_wait()
|
||||
.map_err(Error::PodInstall)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
8
tooling/cli/src/mobile/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pub mod android;
|
||||
mod init;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod ios;
|
||||
12
tooling/cli/templates/mobile/android/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
15
tooling/cli/templates/mobile/android/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
1
tooling/cli/templates/mobile/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
85
tooling/cli/templates/mobile/android/app/build.gradle.kts
Normal file
@@ -0,0 +1,85 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
{{~#each android-app-plugins}}
|
||||
id("{{this}}"){{/each}}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 31
|
||||
defaultConfig {
|
||||
applicationId = "{{reverse-domain app.domain}}.{{snake-case app.name}}"
|
||||
minSdk = {{android.min-sdk-version}}
|
||||
targetSdk = 31
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
{{#if android.vulkan-validation}}// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
{{/if}}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
{{~#each targets}}
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/{{this.abi}}/*.so")
|
||||
{{/each}}
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
{{~#each targets}}
|
||||
|
||||
create("{{this.arch}}") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("{{this.abi}}")
|
||||
}
|
||||
}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf({{quote-and-join-colon-prefix asset-packs}})
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "{{root-dir-rel}}"
|
||||
targets = listOf({{quote-and-join target-names}})
|
||||
arches = listOf({{quote-and-join arches}})
|
||||
}
|
||||
|
||||
dependencies {
|
||||
{{~#each android-app-dependencies-platform}}
|
||||
implementation(platform("{{this}}")){{/each}}
|
||||
{{~#each android-app-dependencies}}
|
||||
implementation("{{this}}"){{/each}}
|
||||
implementation("androidx.webkit:webkit:1.4.0")
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
implementation("com.google.android.material:material:1.6.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.all {
|
||||
val buildType = "${buildType.name.capitalize()}"
|
||||
productFlavors.forEach {
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
}
|
||||
}
|
||||
21
tooling/cli/templates/mobile/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="{{reverse-domain app.domain}}.{{snake-case app.name}}">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.{{snake-case app.name}}">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
18
tooling/cli/templates/mobile/android/app/src/main/Ipc.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
import android.webkit.*
|
||||
|
||||
class Ipc {
|
||||
@JavascriptInterface
|
||||
fun postMessage(message: String) {
|
||||
this.ipc(message)
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("{{snake-case app.name}}")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun ipc(message: String)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
class MainActivity : TauriActivity() {}
|
||||
@@ -0,0 +1,26 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
import android.webkit.*
|
||||
|
||||
class RustWebChromeClient: WebChromeClient() {
|
||||
private var loadedUrl: String? = null
|
||||
|
||||
override fun onProgressChanged(view: WebView, progress: Int) {
|
||||
var url = view.url ?: ""
|
||||
if (url.endsWith("##")) {
|
||||
url = url.dropLast(2)
|
||||
}
|
||||
if (loadedUrl != url) {
|
||||
loadedUrl = url
|
||||
runInitializationScripts()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("{{snake-case app.name}}")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun runInitializationScripts()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
import android.webkit.*
|
||||
|
||||
class RustWebViewClient: WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
return handleRequest(request)
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("{{snake-case app.name}}")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun handleRequest(request: WebResourceRequest): WebResourceResponse?
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package {{reverse-domain app.domain}}.{{snake-case app.name}}
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
abstract class TauriActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
create(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
resume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
pause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
stop()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
focus(hasFocus)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
save()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
destroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
memory()
|
||||
}
|
||||
|
||||
fun getAppClass(name: String): Class<*> {
|
||||
return Class.forName(name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("{{snake-case app.name}}")
|
||||
}
|
||||
}
|
||||
|
||||
private external fun create(activity: TauriActivity)
|
||||
private external fun start()
|
||||
private external fun resume()
|
||||
private external fun pause()
|
||||
private external fun stop()
|
||||
private external fun save()
|
||||
private external fun destroy()
|
||||
private external fun memory()
|
||||
private external fun focus(focus: Boolean)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.{{snake-case app.name}}" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">qoo</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.{{snake-case app.name}}" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
27
tooling/cli/templates/mobile/android/build.gradle.kts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.0.2")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
||||
{{~#each android-project-dependencies}}
|
||||
classpath("{{this}}"){{/each}}
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
delete("build")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
kotlinDslPluginOptions {
|
||||
experimentalWarning.set(false)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("pluginsForCoolKids") {
|
||||
id = "rustPlugin"
|
||||
implementationClass = "{{reverse-domain app.domain}}.RustPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
implementation("com.android.tools.build:gradle:7.0.2")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package {{reverse-domain app.domain}}
|
||||
|
||||
import com.android.build.gradle.*
|
||||
import java.io.File
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.PathSensitive
|
||||
import org.gradle.api.tasks.PathSensitivity
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
open class BuildTask : DefaultTask() {
|
||||
@InputDirectory
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
var rootDirRel: File? = null
|
||||
@Input
|
||||
var target: String? = null
|
||||
@Input
|
||||
var release: Boolean? = null
|
||||
|
||||
@TaskAction
|
||||
fun build() {
|
||||
val rootDirRel = rootDirRel
|
||||
if (rootDirRel == null) {
|
||||
throw GradleException("rootDirRel cannot be null")
|
||||
}
|
||||
val target = target
|
||||
if (target == null) {
|
||||
throw GradleException("target cannot be null")
|
||||
}
|
||||
val release = release
|
||||
if (release == null) {
|
||||
throw GradleException("release cannot be null")
|
||||
}
|
||||
project.exec {
|
||||
workingDir(File(project.getProjectDir(), rootDirRel.getPath()))
|
||||
executable("cargo")
|
||||
args(listOf("android", "build"))
|
||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
||||
args("-vv")
|
||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
||||
args("-v")
|
||||
}
|
||||
if (release) {
|
||||
args("--release")
|
||||
}
|
||||
args("${target}")
|
||||
}.assertNormalExitValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package {{reverse-domain app.domain}}
|
||||
|
||||
import com.android.build.gradle.*
|
||||
import java.io.File
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
||||
const val TASK_GROUP = "rust"
|
||||
|
||||
open class Config {
|
||||
var rootDirRel: String? = null
|
||||
var targets: List<String>? = null
|
||||
var arches: List<String>? = null
|
||||
}
|
||||
|
||||
open class RustPlugin : Plugin<Project> {
|
||||
internal lateinit var config: Config
|
||||
|
||||
override fun apply(project: Project) {
|
||||
config = project.extensions.create("rust", Config::class.java)
|
||||
project.afterEvaluate {
|
||||
if (config.targets == null) {
|
||||
throw GradleException("targets cannot be null")
|
||||
}
|
||||
if (config.arches == null) {
|
||||
throw GradleException("arches cannot be null")
|
||||
}
|
||||
for (profile in listOf("debug", "release")) {
|
||||
val buildTask = project.tasks.maybeCreate("rustBuild${profile.capitalize()}", DefaultTask::class.java).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in ${profile} mode for all targets"
|
||||
}
|
||||
for (targetPair in config.targets!!.withIndex()) {
|
||||
val targetName = targetPair.value
|
||||
val targetArch = config.arches!![targetPair.index]
|
||||
val targetBuildTask = project.tasks.maybeCreate("rustBuild${targetArch.capitalize()}${profile.capitalize()}", BuildTask::class.java).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in ${profile} mode for $targetArch"
|
||||
rootDirRel = File(config.rootDirRel)
|
||||
target = targetName
|
||||
release = profile == "release"
|
||||
}
|
||||
buildTask.dependsOn(targetBuildTask)
|
||||
project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
tooling/cli/templates/mobile/android/gradle.properties
Normal file
@@ -0,0 +1,23 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
BIN
tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Tue May 10 19:22:52 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
185
tooling/cli/templates/mobile/android/gradlew
vendored
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
tooling/cli/templates/mobile/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
3
tooling/cli/templates/mobile/android/settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
include ':app'
|
||||
{{~#each asset-packs}}
|
||||
include ':{{this}}'{{/each}}
|
||||
2
tooling/cli/templates/mobile/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
xcuserdata/
|
||||
build/
|
||||
8
tooling/cli/templates/mobile/ios/ExportOptions.plist
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
25
tooling/cli/templates/mobile/ios/Podfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Uncomment the next line to define a global platform for your project
|
||||
|
||||
target '{{app.name}}_iOS' do
|
||||
platform :ios, '{{apple.ios-version}}'
|
||||
# Pods for {{app.name}}_iOS
|
||||
{{~#each ios-pods}}
|
||||
pod '{{this.name}}'{{#if this.version}}, '{{this.version}}'{{/if}}{{/each}}
|
||||
end
|
||||
|
||||
target '{{app.name}}_macOS' do
|
||||
platform :osx, '{{apple.macos-version}}'
|
||||
# Pods for {{app.name}}_macOS
|
||||
{{~#each macos-pods}}
|
||||
pod '{{this.name}}'{{#if this.version}}, '{{this.version}}'{{/if}}{{/each}}
|
||||
end
|
||||
|
||||
# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
|
||||
config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace ffi {
|
||||
extern "C" {
|
||||
void start_app();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "bindings/bindings.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
ffi::start_app();
|
||||
return 0;
|
||||
}
|
||||
280
tooling/cli/templates/mobile/ios/project.yml
Normal file
@@ -0,0 +1,280 @@
|
||||
name: {{app.name}}
|
||||
options:
|
||||
bundleIdPrefix: {{reverse-domain app.domain}}
|
||||
deploymentTarget:
|
||||
iOS: {{apple.ios-version}}
|
||||
macOS: {{apple.macos-version}}
|
||||
fileGroups: [{{join file-groups}}]
|
||||
configs:
|
||||
debug: debug
|
||||
release: release
|
||||
settingGroups:
|
||||
app:
|
||||
base:
|
||||
PRODUCT_NAME: {{app.name}}
|
||||
PRODUCT_BUNDLE_IDENTIFIER: {{reverse-domain app.domain}}.{{app.name}}
|
||||
DEVELOPMENT_TEAM: {{apple.development-team}}
|
||||
targetTemplates:
|
||||
app:
|
||||
type: application
|
||||
sources:
|
||||
- path: Sources
|
||||
scheme:
|
||||
environmentVariables:
|
||||
RUST_BACKTRACE: full
|
||||
RUST_LOG: info
|
||||
settings:
|
||||
groups: [app]
|
||||
targets:
|
||||
{{app.name}}_iOS:
|
||||
type: application
|
||||
platform: iOS
|
||||
sources:
|
||||
- path: Sources
|
||||
- path: {{app.asset-dir}}
|
||||
buildPhase: resources
|
||||
type: folder
|
||||
{{~#each asset-catalogs}}
|
||||
- {{prefix-path this}}{{/each}}
|
||||
{{~#each ios-additional-targets}}
|
||||
- path: {{prefix-path this}}{{/each}}
|
||||
info:
|
||||
path: {{app.name}}_iOS/Info.plist
|
||||
properties:
|
||||
LSRequiresIPhoneOS: true
|
||||
UILaunchStoryboardName: LaunchScreen
|
||||
UIRequiredDeviceCapabilities: [arm64, metal]
|
||||
UISupportedInterfaceOrientations:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
UISupportedInterfaceOrientations~ipad:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationPortraitUpsideDown
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
CFBundleShortVersionString: {{apple.bundle-version-short}}
|
||||
CFBundleVersion: {{apple.bundle-version}}
|
||||
{{~#each apple.plist-pairs}}
|
||||
{{this.key}}: {{this.value}}{{/each}}
|
||||
scheme:
|
||||
environmentVariables:
|
||||
RUST_BACKTRACE: full
|
||||
RUST_LOG: info
|
||||
{{~#if ios-command-line-arguments}}
|
||||
commandLineArguments:
|
||||
{{~#each ios-command-line-arguments}}
|
||||
"{{this}}": true
|
||||
{{/each}}{{~/if}}
|
||||
settings:
|
||||
base:
|
||||
ENABLE_BITCODE: false
|
||||
ARCHS: [{{join ios-valid-archs}}]
|
||||
VALID_ARCHS: {{~#each ios-valid-archs}} {{this}} {{/each}}
|
||||
LIBRARY_SEARCH_PATHS[sdk=iphoneos*]: $(inherited) "{{prefix-path "target/aarch64-apple-ios/$(CONFIGURATION)"}}"
|
||||
LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]: $(inherited) "{{prefix-path "target/x86_64-apple-ios/$(CONFIGURATION)"}}"
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
|
||||
groups: [app]
|
||||
dependencies:
|
||||
- target: lib_{{app.name}}_iOS
|
||||
embed: false
|
||||
link: false
|
||||
- framework: lib{{snake-case app.name}}.a
|
||||
embed: false
|
||||
{{~#each ios-vendor-frameworks}}
|
||||
- framework: {{prefix-path this}}{{/each}}
|
||||
{{~#each ios-vendor-sdks}}
|
||||
- sdk: {{prefix-path this}}{{/each}}
|
||||
- sdk: CoreGraphics.framework
|
||||
- sdk: Metal.framework
|
||||
- sdk: MetalKit.framework
|
||||
- sdk: QuartzCore.framework
|
||||
- sdk: Security.framework
|
||||
- sdk: UIKit.framework
|
||||
{{~#each ios-frameworks}}
|
||||
- sdk: {{this}}.framework{{/each}}
|
||||
- sdk: WebKit.framework
|
||||
{{~#if ios-pre-build-scripts}}
|
||||
preBuildScripts:
|
||||
{{~#each ios-pre-build-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if~}}
|
||||
{{~#if ios-post-compile-scripts}}
|
||||
postCompileScripts:
|
||||
{{~#each ios-post-compile-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if~}}
|
||||
{{~#if ios-post-build-scripts}}
|
||||
postBuildScripts:
|
||||
{{~#each ios-post-build-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if}}
|
||||
|
||||
{{app.name}}_macOS:
|
||||
type: application
|
||||
platform: macOS
|
||||
sources: Sources
|
||||
{{~#each macos-additional-targets}}
|
||||
- path: {{prefix-path this}}{{/each}}
|
||||
info:
|
||||
path: {{app.name}}_macOS/Info.plist
|
||||
properties:
|
||||
NSHighResolutionCapable: true
|
||||
scheme:
|
||||
environmentVariables:
|
||||
RUST_BACKTRACE: full
|
||||
RUST_LOG: info
|
||||
{{~#if ios-command-line-arguments}}
|
||||
commandLineArguments:
|
||||
{{~#each ios-command-line-arguments}}
|
||||
"{{this}}": true
|
||||
{{/each}}{{~/if}}
|
||||
settings:
|
||||
base:
|
||||
LIBRARY_SEARCH_PATHS: $(inherited) "{{prefix-path "target/x86_64-apple-darwin/$(CONFIGURATION)"}}"
|
||||
groups: [app]
|
||||
dependencies:
|
||||
- target: lib_{{app.name}}_macOS
|
||||
embed: false
|
||||
link: false
|
||||
- framework: lib{{snake-case app.name}}.a
|
||||
embed: false
|
||||
{{~#each macos-vendor-frameworks}}
|
||||
- framework: {{prefix-path this}}{{/each}}
|
||||
{{~#each macos-vendor-sdks}}
|
||||
- sdk: {{prefix-path this}}{{/each}}
|
||||
- sdk: Metal.framework
|
||||
{{~#each macos-frameworks}}
|
||||
- sdk: {{this}}.framework{{/each}}
|
||||
{{~#if macos-pre-build-scripts}}
|
||||
preBuildScripts:
|
||||
{{~#each macos-pre-build-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if~}}
|
||||
{{#if macos-post-compile-scripts}}
|
||||
postCompileScripts:
|
||||
{{~#each macos-post-compile-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if~}}
|
||||
{{#if macos-post-build-scripts}}
|
||||
postBuildScripts:
|
||||
{{~#each macos-post-build-scripts}}{{#if this.path}}
|
||||
- path {{this.path}}{{/if}}{{#if this.script}}
|
||||
- script: {{this.script}}{{/if}}{{#if this.name}}
|
||||
name: {{this.name}}{{/if}}{{#if this.input-files}}
|
||||
inputFiles: {{~#each this.input-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-files}}
|
||||
outputFiles: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.input-file-lists}}
|
||||
inputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.output-file-lists}}
|
||||
outputFileLists: {{~#each this.output-files}}
|
||||
- {{this}}{{/each}}{{/if}}{{#if this.shell}}
|
||||
shell: {{this.shell}}{{/if}}{{#if this.show-env-vars}}
|
||||
showEnvVars: {{this.show_env_vars}}{{/if}}{{#if this.run-only-when-installing}}
|
||||
runOnlyWhenInstalling: {{this.run-only-when-installing}}{{/if}}{{#if this.based-on-dependency-analysis}}
|
||||
basedOnDependencyAnalysis: {{this.based-on-dependency-analysis}}{{/if}}{{#if this.discovered-dependency-file}}
|
||||
discoveredDependencyFile: {{this.discovered-dependency-file}}{{/if}}
|
||||
{{~/each~}}
|
||||
{{~/if}}
|
||||
|
||||
lib_{{app.name}}_iOS:
|
||||
type: ""
|
||||
platform: iOS
|
||||
settings:
|
||||
ENABLE_BITCODE: false
|
||||
ARCHS: [{{join ios-valid-archs}}]
|
||||
VALID_ARCHS: {{~#each ios-valid-archs}} {{this}} {{/each}}
|
||||
legacy:
|
||||
toolPath: ${HOME}/.cargo/bin/cargo-apple
|
||||
arguments: xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
|
||||
passSettings: false # prevents evil linker errors
|
||||
workingDirectory: $(SRCROOT)/..
|
||||
lib_{{app.name}}_macOS:
|
||||
type: ""
|
||||
platform: macOS
|
||||
legacy:
|
||||
toolPath: ${HOME}/.cargo/bin/cargo-apple
|
||||
arguments: xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}
|
||||
passSettings: false
|
||||
workingDirectory: $(SRCROOT)/..
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
{{#if apple.use-legacy-build-system}}
|
||||
<key>BuildSystemType</key>
|
||||
<string>Original</string>
|
||||
<key>DisableBuildSystemDeprecationDiagnostic</key>
|
||||
<true/>
|
||||
{{/if}}
|
||||
</dict>
|
||||
</plist>
|
||||