diff --git a/.changes/mobile-init.md b/.changes/mobile-init.md new file mode 100644 index 000000000..97c17f266 --- /dev/null +++ b/.changes/mobile-init.md @@ -0,0 +1,6 @@ +--- +"cli.rs": minor +"cli.js": minor +--- + +Added `android init` and `ios init` commands. diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 92c3200f0..7dc825708 100644 --- a/tooling/cli/Cargo.lock +++ b/tooling/cli/Cargo.lock @@ -58,6 +58,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.58" @@ -70,6 +79,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "attohttpc" version = "0.22.0" @@ -112,6 +127,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +[[package]] +name = "bicycle" +version = "0.1.0" +source = "git+https://github.com/BrainiumLLC/bicycle?rev=28080e0c6fa4067d9dd1b0f2b7322b6b32178e1f#28080e0c6fa4067d9dd1b0f2b7322b6b32178e1f" +dependencies = [ + "handlebars 3.5.5", + "log", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "bit_field" version = "0.10.1" @@ -136,6 +163,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.7.3" @@ -166,6 +199,16 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bossy" +version = "0.2.1" +source = "git+https://github.com/lucasfernog/bossy?branch=fix/winapi-features#83ee04daddbc9b985b5f8dcf54d7229f0542d41d" +dependencies = [ + "libc", + "log", + "winapi 0.3.9", +] + [[package]] name = "bstr" version = "0.2.17" @@ -226,6 +269,48 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cargo-mobile" +version = "0.1.0" +source = "git+https://github.com/tauri-apps/cargo-mobile?branch=feat/library#d2b3f7248657c60dcc0669303329b802f04a6291" +dependencies = [ + "bicycle", + "bossy", + "cocoa", + "colored 1.9.3", + "const-utf16", + "core-foundation 0.7.0", + "deunicode", + "dunce", + "embed-resource", + "english-numbers", + "env_logger 0.7.1", + "freedesktop_entry_parser", + "heck 0.4.0", + "hit", + "home", + "ignore", + "indexmap", + "java-properties", + "lexical-core", + "log", + "objc", + "objc_id", + "once-cell-regex", + "openssl", + "path_abs", + "reserved-names", + "serde", + "serde_json", + "structopt", + "textwrap 0.11.0", + "thiserror", + "toml", + "ureq", + "windows 0.26.0", + "yes-or-no", +] + [[package]] name = "cc" version = "1.0.73" @@ -276,6 +361,21 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + [[package]] name = "clap" version = "3.2.7" @@ -288,9 +388,9 @@ dependencies = [ "clap_lex", "indexmap", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", - "textwrap", + "textwrap 0.15.0", ] [[package]] @@ -299,7 +399,7 @@ version = "3.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" dependencies = [ - "heck", + "heck 0.4.0", "proc-macro-error", "proc-macro2", "quote", @@ -315,12 +415,38 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "cocoa" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.7.0", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +dependencies = [ + "atty", + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "colored" version = "2.0.0" @@ -357,6 +483,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "const-utf16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90feefab165fe011746e3be2f0708b7b180fcbd9f5054ff81a454d7bd93d8285" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -375,22 +507,50 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.2" @@ -550,7 +710,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn", ] @@ -564,7 +724,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn", ] @@ -622,6 +782,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c9736e15e7df1638a7f6eee92a6511615c738246a052af5ba86f039b65aede" + [[package]] name = "dialoguer" version = "0.10.1" @@ -689,6 +855,12 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dunce" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" + [[package]] name = "dyn-clone" version = "1.0.6" @@ -701,12 +873,108 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "embed-resource" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "english-numbers" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4f5d6e192964d498b45abee72ca445e91909094bc8e8791259e82c2a0d1aa6" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.9.0" @@ -714,7 +982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", - "humantime", + "humantime 2.1.0", "log", "regex", "termcolor", @@ -838,6 +1106,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freedesktop_entry_parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" +dependencies = [ + "nom", + "thiserror", +] + [[package]] name = "fsevent" version = "0.4.0" @@ -992,6 +1270,20 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + [[package]] name = "handlebars" version = "4.3.1" @@ -1012,6 +1304,15 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.0" @@ -1033,6 +1334,18 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8b280c3c6e2a2a51b3dde2f1071b28afff0ebce59e29443b1b391e7efe32fd" +dependencies = [ + "bossy", + "log", + "once-cell-regex", + "thiserror", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1042,6 +1355,15 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -1067,6 +1389,15 @@ dependencies = [ "itoa 1.0.2", ] +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1241,6 +1572,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "java-properties" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1904d8654a1ef51034d02d5a9411b50bf91bea15b0ab644ae179d1325976263" +dependencies = [ + "encoding", + "lazy_static", + "regex", +] + [[package]] name = "jobserver" version = "0.1.24" @@ -1349,6 +1691,19 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.126" @@ -1426,6 +1781,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -1473,6 +1837,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "minisign" version = "0.7.0" @@ -1661,6 +2031,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "4.0.17" @@ -1728,6 +2108,34 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once-cell-regex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de7e389a5043420c8f2b95ed03f3f104ad6f4c41f7d7e27298f033abc253e8" +dependencies = [ + "once_cell", + "regex", +] + [[package]] name = "once_cell" version = "1.13.0" @@ -1852,6 +2260,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "path_abs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3" +dependencies = [ + "serde", + "serde_derive", + "std_prelude", + "stfu8", +] + [[package]] name = "pbkdf2" version = "0.10.1" @@ -2148,6 +2568,18 @@ dependencies = [ "url", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.20" @@ -2308,6 +2740,14 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "reserved-names" +version = "0.1.0" +source = "git+https://github.com/BrainiumLLC/reserved-names#cc46545db485b13851e9136280b7d8e5a95a9d18" +dependencies = [ + "thiserror", +] + [[package]] name = "ring" version = "0.16.20" @@ -2481,8 +2921,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", - "core-foundation", - "core-foundation-sys", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", "libc", "security-framework-sys", ] @@ -2493,7 +2933,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.3", "libc", ] @@ -2736,6 +3176,28 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "std_prelude" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" + +[[package]] +name = "stfu8" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019f0c664fd85d5a87dcfb62b40b691055392a35a6e59f4df83d4b770db7e876" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "string_cache" version = "0.8.4" @@ -2762,12 +3224,42 @@ dependencies = [ "quote", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -2825,8 +3317,8 @@ dependencies = [ "bitness", "dirs-next", "glob", - "handlebars", - "heck", + "handlebars 4.3.1", + "heck 0.4.0", "hex", "icns", "image", @@ -2839,7 +3331,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "strsim", + "strsim 0.10.0", "tar", "tauri-utils", "tempfile", @@ -2858,14 +3350,16 @@ version = "1.0.5" dependencies = [ "anyhow", "base64", - "clap", - "colored", + "bossy", + "cargo-mobile", + "clap 3.2.7", + "colored 2.0.0", "ctrlc", "dialoguer", - "env_logger", + "env_logger 0.9.0", "glob", - "handlebars", - "heck", + "handlebars 4.3.1", + "heck 0.4.0", "ignore", "include_dir", "json-patch", @@ -2888,6 +3382,8 @@ dependencies = [ "tauri-utils", "tempfile", "terminal_size 0.2.1", + "textwrap 0.11.0", + "thiserror", "toml", "toml_edit", "unicode-width", @@ -2917,7 +3413,7 @@ dependencies = [ "ctor", "getrandom 0.2.7", "glob", - "heck", + "heck 0.4.0", "html5ever", "json-patch", "json5", @@ -2934,7 +3430,7 @@ dependencies = [ "toml", "url", "walkdir", - "windows", + "windows 0.37.0", ] [[package]] @@ -2962,6 +3458,16 @@ dependencies = [ "utf-8", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -2991,6 +3497,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "term_size", + "unicode-width", +] + [[package]] name = "textwrap" version = "0.15.0" @@ -3103,6 +3619,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ + "indexmap", "serde", ] @@ -3168,6 +3685,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + [[package]] name = "unicode-width" version = "0.1.9" @@ -3293,12 +3816,38 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -3460,6 +4009,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347cdcaae1addebdff584aea1f9fbc0426dedbe1315f1dcf30c7a9876401cd25" +dependencies = [ + "windows_aarch64_msvc 0.26.0", + "windows_i686_gnu 0.26.0", + "windows_i686_msvc 0.26.0", + "windows_x86_64_gnu 0.26.0", + "windows_x86_64_msvc 0.26.0", +] + [[package]] name = "windows" version = "0.37.0" @@ -3503,6 +4065,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169" +[[package]] +name = "windows_aarch64_msvc" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7758986b022add546ae53ccad31f4852ce6bd2e2c2d3cc2b1d7d06dea0b90da" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -3515,6 +4083,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" +[[package]] +name = "windows_i686_gnu" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29261214caab8e589f61031ba1ccd5c3c25e61db2118a3aec4459f58ff798726" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -3527,6 +4101,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" +[[package]] +name = "windows_i686_msvc" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43984fb3b944743142112ae926e7adeccb60f35bb81d43114f4d0fe2871f60ba" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -3539,6 +4119,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" +[[package]] +name = "windows_x86_64_gnu" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a80fc90e1ad19769e596a3f58d0776319059e21cac9069a5a2a791362ce7190" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -3551,6 +4137,12 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc24ddac19a0cf02ad2b32d8897f202fc1a13ef285e2d4774e6610783cc8398f" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -3597,6 +4189,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +[[package]] +name = "yes-or-no" +version = "0.1.0" +source = "git+https://github.com/BrainiumLLC/yes-or-no#71d932693601fcf93ff906b90ad61f85b52117c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.5.5" diff --git a/tooling/cli/Cargo.toml b/tooling/cli/Cargo.toml index c2a77100a..8fd0c9263 100644 --- a/tooling/cli/Cargo.toml +++ b/tooling/cli/Cargo.toml @@ -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" } diff --git a/tooling/cli/src/helpers/template.rs b/tooling/cli/src/helpers/template.rs index 5ff775d17..4aa8b7343 100644 --- a/tooling/cli/src/helpers/template.rs +++ b/tooling/cli/src/helpers/template.rs @@ -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>( +/// Map of template variable names and values. +#[derive(Clone, Debug)] +#[repr(transparent)] +pub struct JsonMap(Map); + +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 { + &self.0 + } +} + +pub fn render, 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, + D: Serialize, + F: FnMut(&PathBuf) -> std::io::Result, +>( + 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>( 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>( } } 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(()) } diff --git a/tooling/cli/src/lib.rs b/tooling/cli/src/lib.rs index f96294060..5fc4b6433 100644 --- a/tooling/cli/src/lib.rs +++ b/tooling/cli/src/lib.rs @@ -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(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(()) diff --git a/tooling/cli/src/mobile/android.rs b/tooling/cli/src/mobile/android.rs new file mode 100644 index 000000000..b3b3a2435 --- /dev/null +++ b/tooling/cli/src/mobile/android.rs @@ -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(()) +} diff --git a/tooling/cli/src/mobile/android/project.rs b/tooling/cli/src/mobile/android/project.rs new file mode 100644 index 000000000..b70771b07 --- /dev/null +++ b/tooling/cli/src/mobile/android/project.rs @@ -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::>()); + map.insert("target-names", Target::all().keys().collect::>()); + map.insert( + "arches", + Target::all() + .values() + .map(|target| target.arch) + .collect::>(), + ); + 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::>(), + ); + 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(()) +} diff --git a/tooling/cli/src/mobile/init.rs b/tooling/cli/src/mobile/init.rs new file mode 100644 index 000000000..1089eff71 --- /dev/null +++ b/tooling/cli/src/mobile/init.rs @@ -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, +) -> Result { + 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> { + 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) +} diff --git a/tooling/cli/src/mobile/ios.rs b/tooling/cli/src/mobile/ios.rs new file mode 100644 index 000000000..0b10b2d64 --- /dev/null +++ b/tooling/cli/src/mobile/ios.rs @@ -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(()) +} diff --git a/tooling/cli/src/mobile/ios/project.rs b/tooling/cli/src/mobile/ios/project.rs new file mode 100644 index 000000000..b743151ad --- /dev/null +++ b/tooling/cli/src/mobile/ios/project.rs @@ -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::()); + + 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(()) +} diff --git a/tooling/cli/src/mobile/mod.rs b/tooling/cli/src/mobile/mod.rs new file mode 100644 index 000000000..83baa249c --- /dev/null +++ b/tooling/cli/src/mobile/mod.rs @@ -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; diff --git a/tooling/cli/templates/mobile/android/.editorconfig b/tooling/cli/templates/mobile/android/.editorconfig new file mode 100644 index 000000000..ebe51d3bf --- /dev/null +++ b/tooling/cli/templates/mobile/android/.editorconfig @@ -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 \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/.gitignore b/tooling/cli/templates/mobile/android/.gitignore new file mode 100644 index 000000000..aa724b770 --- /dev/null +++ b/tooling/cli/templates/mobile/android/.gitignore @@ -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 diff --git a/tooling/cli/templates/mobile/android/app/.gitignore b/tooling/cli/templates/mobile/android/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/build.gradle.kts b/tooling/cli/templates/mobile/android/app/build.gradle.kts new file mode 100644 index 000000000..13906b47c --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/build.gradle.kts @@ -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}"]) + } + } +} diff --git a/tooling/cli/templates/mobile/android/app/proguard-rules.pro b/tooling/cli/templates/mobile/android/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/AndroidManifest.xml b/tooling/cli/templates/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..cf42dcd2c --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/tooling/cli/templates/mobile/android/app/src/main/Ipc.kt b/tooling/cli/templates/mobile/android/app/src/main/Ipc.kt new file mode 100644 index 000000000..f2bb26f15 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/Ipc.kt @@ -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) +} diff --git a/tooling/cli/templates/mobile/android/app/src/main/MainActivity.kt b/tooling/cli/templates/mobile/android/app/src/main/MainActivity.kt new file mode 100644 index 000000000..187d1996c --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/MainActivity.kt @@ -0,0 +1,3 @@ +package {{reverse-domain app.domain}}.{{snake-case app.name}} + +class MainActivity : TauriActivity() {} diff --git a/tooling/cli/templates/mobile/android/app/src/main/RustWebChromeClient.kt b/tooling/cli/templates/mobile/android/app/src/main/RustWebChromeClient.kt new file mode 100644 index 000000000..d810e18a9 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/RustWebChromeClient.kt @@ -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() +} diff --git a/tooling/cli/templates/mobile/android/app/src/main/RustWebViewClient.kt b/tooling/cli/templates/mobile/android/app/src/main/RustWebViewClient.kt new file mode 100644 index 000000000..c6c65fa95 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/RustWebViewClient.kt @@ -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? +} diff --git a/tooling/cli/templates/mobile/android/app/src/main/TauriActivity.kt b/tooling/cli/templates/mobile/android/app/src/main/TauriActivity.kt new file mode 100644 index 000000000..84b13f949 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/TauriActivity.kt @@ -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) +} diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/tooling/cli/templates/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml b/tooling/cli/templates/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/layout/activity_main.xml b/tooling/cli/templates/mobile/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..4fc244418 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 000000000..c209e78ec Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 000000000..b2dfe3d1b Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 000000000..4f0f1d64e Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 000000000..62b611da0 Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 000000000..948a3070f Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..1b9a6956b Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 000000000..28d4b77f9 Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9287f5083 Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 000000000..aa7d6427e Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9126ae37c Binary files /dev/null and b/tooling/cli/templates/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/values-night/themes.xml b/tooling/cli/templates/mobile/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 000000000..73c60177c --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/values/colors.xml b/tooling/cli/templates/mobile/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..f8c6127d3 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/values/strings.xml b/tooling/cli/templates/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..d7c29c74f --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + qoo + \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/app/src/main/res/values/themes.xml b/tooling/cli/templates/mobile/android/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..16a259052 --- /dev/null +++ b/tooling/cli/templates/mobile/android/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + diff --git a/tooling/cli/templates/mobile/android/build.gradle.kts b/tooling/cli/templates/mobile/android/build.gradle.kts new file mode 100644 index 000000000..f45e9c35a --- /dev/null +++ b/tooling/cli/templates/mobile/android/build.gradle.kts @@ -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") +} + diff --git a/tooling/cli/templates/mobile/android/buildSrc/build.gradle.kts b/tooling/cli/templates/mobile/android/buildSrc/build.gradle.kts new file mode 100644 index 000000000..b0d0d4e65 --- /dev/null +++ b/tooling/cli/templates/mobile/android/buildSrc/build.gradle.kts @@ -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") +} + diff --git a/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/BuildTask.kt b/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/BuildTask.kt new file mode 100644 index 000000000..454531d0a --- /dev/null +++ b/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/BuildTask.kt @@ -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() + } +} + diff --git a/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/RustPlugin.kt b/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/RustPlugin.kt new file mode 100644 index 000000000..86b86cd4f --- /dev/null +++ b/tooling/cli/templates/mobile/android/buildSrc/src/main/kotlin/RustPlugin.kt @@ -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? = null + var arches: List? = null +} + +open class RustPlugin : Plugin { + 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) + } + } + } + } +} diff --git a/tooling/cli/templates/mobile/android/gradle.properties b/tooling/cli/templates/mobile/android/gradle.properties new file mode 100644 index 000000000..cd0519bb2 --- /dev/null +++ b/tooling/cli/templates/mobile/android/gradle.properties @@ -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 \ No newline at end of file diff --git a/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.jar b/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e708b1c02 Binary files /dev/null and b/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.properties b/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..543ef62a1 --- /dev/null +++ b/tooling/cli/templates/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/tooling/cli/templates/mobile/android/gradlew b/tooling/cli/templates/mobile/android/gradlew new file mode 100755 index 000000000..4f906e0c8 --- /dev/null +++ b/tooling/cli/templates/mobile/android/gradlew @@ -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" "$@" diff --git a/tooling/cli/templates/mobile/android/gradlew.bat b/tooling/cli/templates/mobile/android/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/tooling/cli/templates/mobile/android/gradlew.bat @@ -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 diff --git a/tooling/cli/templates/mobile/android/settings.gradle b/tooling/cli/templates/mobile/android/settings.gradle new file mode 100644 index 000000000..74680c9c1 --- /dev/null +++ b/tooling/cli/templates/mobile/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' +{{~#each asset-packs}} +include ':{{this}}'{{/each}} diff --git a/tooling/cli/templates/mobile/ios/.gitignore b/tooling/cli/templates/mobile/ios/.gitignore new file mode 100644 index 000000000..6aecd276a --- /dev/null +++ b/tooling/cli/templates/mobile/ios/.gitignore @@ -0,0 +1,2 @@ +xcuserdata/ +build/ diff --git a/tooling/cli/templates/mobile/ios/ExportOptions.plist b/tooling/cli/templates/mobile/ios/ExportOptions.plist new file mode 100644 index 000000000..b69cf1de8 --- /dev/null +++ b/tooling/cli/templates/mobile/ios/ExportOptions.plist @@ -0,0 +1,8 @@ + + + + + method + development + + diff --git a/tooling/cli/templates/mobile/ios/Podfile b/tooling/cli/templates/mobile/ios/Podfile new file mode 100644 index 000000000..0c0df4e64 --- /dev/null +++ b/tooling/cli/templates/mobile/ios/Podfile @@ -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 diff --git a/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/bindings/bindings.h b/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/bindings/bindings.h new file mode 100644 index 000000000..51522007b --- /dev/null +++ b/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/bindings/bindings.h @@ -0,0 +1,8 @@ +#pragma once + +namespace ffi { + extern "C" { + void start_app(); + } +} + diff --git a/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/main.mm b/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/main.mm new file mode 100644 index 000000000..7793a9d5c --- /dev/null +++ b/tooling/cli/templates/mobile/ios/Sources/{{app.name}}/main.mm @@ -0,0 +1,6 @@ +#include "bindings/bindings.h" + +int main(int argc, char * argv[]) { + ffi::start_app(); + return 0; +} diff --git a/tooling/cli/templates/mobile/ios/project.yml b/tooling/cli/templates/mobile/ios/project.yml new file mode 100644 index 000000000..b4e3a5dda --- /dev/null +++ b/tooling/cli/templates/mobile/ios/project.yml @@ -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)/.. diff --git a/tooling/cli/templates/mobile/ios/{{app.name}}.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/tooling/cli/templates/mobile/ios/{{app.name}}.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..0225e8973 --- /dev/null +++ b/tooling/cli/templates/mobile/ios/{{app.name}}.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,12 @@ + + + + +{{#if apple.use-legacy-build-system}} + BuildSystemType + Original + DisableBuildSystemDeprecationDiagnostic + +{{/if}} + +