feat: add android init and ios init commands (#4942)

This commit is contained in:
Lucas Fernandes Nogueira
2022-08-15 12:43:50 -03:00
committed by GitHub
parent d3179b84b5
commit d44f67f7af
58 changed files with 2973 additions and 28 deletions

6
.changes/mobile-init.md Normal file
View File

@@ -0,0 +1,6 @@
---
"cli.rs": minor
"cli.js": minor
---
Added `android init` and `ios init` commands.

642
tooling/cli/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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" }

View File

@@ -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(())
}

View File

@@ -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(())

View 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(())
}

View 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(())
}

View 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)
}

View 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(())
}

View 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(())
}

View 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;

View 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

View 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

View File

@@ -0,0 +1 @@
/build

View 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}"])
}
}
}

View 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

View File

@@ -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>

View 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)
}

View File

@@ -0,0 +1,3 @@
package {{reverse-domain app.domain}}.{{snake-case app.name}}
class MainActivity : TauriActivity() {}

View File

@@ -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()
}

View File

@@ -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?
}

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">qoo</string>
</resources>

View File

@@ -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>

View 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")
}

View File

@@ -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")
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}
}
}
}

View 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

View 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
View 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" "$@"

View 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

View File

@@ -0,0 +1,3 @@
include ':app'
{{~#each asset-packs}}
include ':{{this}}'{{/each}}

View File

@@ -0,0 +1,2 @@
xcuserdata/
build/

View 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>

View 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

View File

@@ -0,0 +1,8 @@
#pragma once
namespace ffi {
extern "C" {
void start_app();
}
}

View File

@@ -0,0 +1,6 @@
#include "bindings/bindings.h"
int main(int argc, char * argv[]) {
ffi::start_app();
return 0;
}

View 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)/..

View File

@@ -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>