From 9b33892acaf8d89adba58a90ff3e50c9c5e8e957 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 17 Dec 2024 14:06:36 +1300 Subject: [PATCH 01/19] Build xcframework --- Cargo.lock | 733 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 + src/apple_platform.rs | 68 ++++ src/build.rs | 194 +++++++++++ src/cli.rs | 60 ++++ src/main.rs | 9 + src/utils.rs | 78 +++++ src/xcframework.rs | 374 +++++++++++++++++++++ 8 files changed, 1528 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/apple_platform.rs create mode 100644 src/build.rs create mode 100644 src/cli.rs create mode 100644 src/main.rs create mode 100644 src/utils.rs create mode 100644 src/xcframework.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4572925 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,733 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.7", +] + +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +dependencies = [ + "thiserror-impl 2.0.7", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "uniffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" +dependencies = [ + "anyhow", + "cargo_metadata 0.15.4", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi-swift-helpers" +version = "0.1.0" +dependencies = [ + "anyhow", + "cargo_metadata 0.19.1", + "clap", + "serde_json", + "uniffi", + "uniffi_bindgen", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata 0.15.4", + "fs-err", + "glob", + "goblin", + "heck", + "once_cell", + "paste", + "serde", + "textwrap", + "toml", + "uniffi_meta", + "uniffi_udl", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "uniffi_core" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" +dependencies = [ + "anyhow", + "bytes", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata 0.15.4", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a7ac6a3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "uniffi-swift-helpers" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +cargo_metadata = "0.19" +clap = { version = "4.5", features = ["derive"] } +serde_json = "1.0" +uniffi = "0.28.3" +uniffi_bindgen = "0.28.3" diff --git a/src/apple_platform.rs b/src/apple_platform.rs new file mode 100644 index 0000000..a055eee --- /dev/null +++ b/src/apple_platform.rs @@ -0,0 +1,68 @@ +use std::fmt::Display; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ApplePlatform { + MacOS, + #[allow(clippy::upper_case_acronyms)] + IOS, + TvOS, + WatchOS, +} + +impl ApplePlatform { + pub fn all() -> Vec { + vec![ + Self::MacOS, + Self::IOS, + Self::TvOS, + Self::WatchOS, + ] + } + + pub fn target_triples(&self) -> Vec<&'static str> { + match self { + Self::IOS => vec![ + "aarch64-apple-ios", + "x86_64-apple-ios", + "aarch64-apple-ios-sim", + ], + Self::MacOS => vec!["x86_64-apple-darwin", "aarch64-apple-darwin"], + Self::WatchOS => vec![ + "arm64_32-apple-watchos", + "x86_64-apple-watchos-sim", + "aarch64-apple-watchos-sim", + ], + Self::TvOS => vec!["aarch64-apple-tvos", "aarch64-apple-tvos-sim"], + } + } + + pub fn requires_nightly_toolchain(&self) -> bool { + matches!(self, Self::TvOS | Self::WatchOS) + } +} + +impl TryFrom<&str> for ApplePlatform { + type Error = anyhow::Error; + + fn try_from(s: &str) -> std::result::Result { + match s { + "darwin" => Ok(ApplePlatform::MacOS), + "ios" => Ok(ApplePlatform::IOS), + "tvos" => Ok(ApplePlatform::TvOS), + "watchos" => Ok(ApplePlatform::WatchOS), + _ => anyhow::bail!("Unknown Apple platform: {}", s), + } + } +} + +impl Display for ApplePlatform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + ApplePlatform::MacOS => "macos", + ApplePlatform::IOS => "ios", + ApplePlatform::TvOS => "tvos", + ApplePlatform::WatchOS => "watchos", + }; + write!(f, "{}", name) + } +} diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..732d249 --- /dev/null +++ b/src/build.rs @@ -0,0 +1,194 @@ +use std::{ + fs::File, + path::{Path, PathBuf}, + process::Command, +}; + +use std::io::Write; + +use anyhow::{Context, Result}; +use cargo_metadata::{Metadata, MetadataCommand}; +use uniffi_bindgen::bindings::SwiftBindingsOptions; + +use crate::utils::*; +use crate::{apple_platform::ApplePlatform, xcframework::create_xcframework}; + +pub fn build( + package: String, + profile: String, + ffi_module_name: String, + apple_platforms: Vec, +) -> Result<()> { + if profile != "release" && profile != "dev" { + anyhow::bail!( + "Profile must be either 'release' or 'dev', found {}", + profile + ) + } + + let metadata = MetadataCommand::new() + .no_deps() + .exec() + .with_context(|| "Can't get cargo metadata")?; + let package = metadata + .packages + .iter() + .find(|p| p.name == package) + .context(format!( + "Package {} not found. Make sure run this command in the cargo root directory", + package + ))?; + + let target_dirs: Vec<_> = if apple_platforms.is_empty() { + build_uniffi_package(&package.name, &profile, None, &metadata)? + } else { + apple_platforms + .iter() + .map(|platform| { + build_uniffi_package(&package.name, &profile, Some(*platform), &metadata) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect() + }; + + for target_dir in target_dirs { + let libraries = target_dir.files_with_extension("a")?; + if libraries.len() != 1 { + anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) + } + + generate_bindings(&libraries[0], &ffi_module_name)?; + } + + if apple_platforms.is_empty() { + // TODO: Linux + unimplemented!("Not implemented for Linux yet") + } else { + create_xcframework( + metadata.target_directory.as_std_path(), + apple_platforms + .iter() + .flat_map(|p| p.target_triples()) + .map(|s| s.to_string()) + .collect(), + profile.to_string(), + &ffi_module_name, + ) + } +} + +fn build_uniffi_package( + package: &str, + profile: &str, + platform: Option, + metadata: &Metadata, +) -> Result> { + let profile_dirname = match profile { + "release" => "release", + "dev" => "debug", + _ => anyhow::bail!("Invalid profile: {}", profile), + }; + + let mut build = vec!["cargo"]; + + if platform + .as_ref() + .map_or(false, |p| p.requires_nightly_toolchain()) + { + // TODO: Use a specific nightly toolchain? + build.extend(["+nightly", "-Z", "build-std=panic_abort,std"]); + } + + // Include debug symbols. + let config_debug = format!("profile.{}.debug=true", profile); + // Abort on panic to include Rust backtrace in crash reports. + let panic_config = format!(r#"profile.{}.panic="abort""#, profile); + build.extend(["--config", &config_debug, "--config", &panic_config]); + + build.extend(["build", "--package", package, "--profile", profile]); + + let cargo_target_dir: &Path = metadata.target_directory.as_std_path(); + + let targets = platform.as_ref().map_or(vec![], |p| p.target_triples()); + if targets.is_empty() { + let mut cmd = Command::new(build[0]); + cmd.args(&build[1..]); + + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!("Failed to build package {}", package) + } + + let target_dir = cargo_target_dir.join(profile_dirname); + Ok(vec![target_dir]) + } else { + targets + .into_iter() + .map(|target| { + let mut cmd = Command::new(build[0]); + cmd.args(&build[1..]); + cmd.args(["--target", target]); + + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!("Failed to build package {} for target {}", package, target) + } + + let target_dir = cargo_target_dir.join(target).join(profile_dirname); + + Ok(target_dir) + }) + .collect() + } +} + +fn generate_bindings(library_path: &Path, module_name: &str) -> Result { + let out_dir = library_path.parent().unwrap().join("swift-bindings"); + fs::recreate_dir(&out_dir)?; + + let options = SwiftBindingsOptions { + generate_swift_sources: true, + generate_headers: true, + generate_modulemap: false, + library_path: library_path.to_path_buf().try_into()?, + out_dir: out_dir.clone().try_into()?, + xcframework: false, + module_name: None, + modulemap_filename: None, + metadata_no_deps: false, + }; + uniffi_bindgen::bindings::generate_swift_bindings(options)?; + + reorganize_binding_files(&out_dir, module_name)?; + + Ok(out_dir) +} + +fn reorganize_binding_files(bindings_dir: &Path, module_name: &str) -> Result<()> { + let headers_dir = bindings_dir.join("Headers"); + fs::recreate_dir(&headers_dir)?; + + let modulemap_path = headers_dir.join("module.modulemap"); + let mut modulemap = File::create_new(modulemap_path)?; + writeln!(modulemap, r#"module {} {{"#, module_name)?; + for entry in std::fs::read_dir(bindings_dir)? { + let entry = entry?; + if entry.path().extension() == Some("h".as_ref()) { + writeln!( + modulemap, + r#" header "{}""#, + entry.file_name().to_str().unwrap() + )?; + fs::move_file(&entry.path(), &headers_dir)?; + } + } + writeln!(modulemap, r#" export *"#)?; + writeln!(modulemap, r#"}}"#)?; + + // TODO: https://github.com/mozilla/uniffi-rs/pull/2341 + // sed -i '' 's/^protocol UniffiForeignFutureTask /fileprivate protocol UniffiForeignFutureTask /' ".swift" + + Ok(()) +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..c720780 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,60 @@ +use std::env; + +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use crate::apple_platform::ApplePlatform; +use crate::build; + +#[derive(Parser)] +pub(crate) struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + Build(BuildArgs), +} + +#[derive(Parser)] +struct BuildArgs { + #[arg(long)] + package: String, + #[arg(long)] + only_ios: bool, + #[arg(long)] + only_macos: bool, + #[arg(long)] + profile: String, + #[arg(long)] + ffi_module_name: String, +} + +impl Cli { + pub fn execute() -> Result<()> { + let args = Cli::parse(); + match args.command { + Commands::Build(args) => build(args), + } + } +} + +fn build(args: BuildArgs) -> Result<()> { + let apple_platforms = if args.only_ios { + vec![ApplePlatform::IOS] + } else if args.only_macos { + vec![ApplePlatform::MacOS] + } else if env::consts::OS == "macos" { + ApplePlatform::all() + } else { + vec![] + }; + + build::build( + args.package, + args.profile, + args.ffi_module_name, + apple_platforms, + ) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2cc60ce --- /dev/null +++ b/src/main.rs @@ -0,0 +1,9 @@ +mod apple_platform; +mod build; +mod cli; +mod utils; +mod xcframework; + +fn main() -> anyhow::Result<()> { + cli::Cli::execute() +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..69474d1 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,78 @@ +use std::{ + path::{Path, PathBuf}, + process::{Command, Output}, +}; + +use anyhow::{Context, Result}; + +#[allow(dead_code)] +pub(crate) trait ExecuteCommand { + fn successful_output(&mut self) -> Result; +} + +impl ExecuteCommand for Command { + fn successful_output(&mut self) -> Result { + let output = self + .output() + .with_context(|| format!("Command failed: $ {:?}", self))?; + if output.status.success() { + Ok(output) + } else { + anyhow::bail!( + "Command failed with exit code: {}\nstdout: {:?}\nstderr: {:?}\n$ {:?}", + output.status, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + self + ) + } + } +} + +pub(crate) trait FileSystemExtensions { + fn files_with_extension(&self, ext: &str) -> Result>; +} + +impl FileSystemExtensions for Path { + fn files_with_extension(&self, ext: &str) -> Result> { + let files = std::fs::read_dir(self)? + .filter_map(|f| f.ok()) + .map(|f| f.path()) + .filter(|p| p.is_file() && p.extension().map_or(false, |e| e == ext)) + .collect(); + Ok(files) + } +} + +pub(crate) mod fs { + + use std::path::PathBuf; + + use super::*; + + pub fn recreate_dir(dir: &Path) -> Result<()> { + if dir.exists() { + std::fs::remove_dir_all(dir) + .with_context(|| format!("Failed to remove directory at {:?}", dir))?; + } + + std::fs::create_dir_all(dir) + .with_context(|| format!("Failed to create directory: {:?}", dir)) + } + + pub fn move_file(src: &Path, dst: &Path) -> Result { + assert!(src.exists(), "Source file does not exist: {:?}", src); + assert!(src.is_file(), "Source is not a file: {:?}", src); + + let destination: PathBuf = if dst.is_dir() { + dst.join(src.file_name().unwrap()) + } else { + dst.to_path_buf() + }; + + std::fs::rename(src, &destination) + .with_context(|| format!("Failed to move directory from {:?} to {:?}", src, dst))?; + + Ok(destination) + } +} diff --git a/src/xcframework.rs b/src/xcframework.rs new file mode 100644 index 0000000..80fd094 --- /dev/null +++ b/src/xcframework.rs @@ -0,0 +1,374 @@ +use anyhow::{Context, Result}; +use std::collections::HashMap; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use crate::apple_platform::ApplePlatform; +use crate::utils::*; + +pub fn create_xcframework( + cargo_target_dir: &Path, + targets: Vec, + profile: String, + name: &str, +) -> Result<()> { + let temp_dir = cargo_target_dir.join("tmp/wp-rs-xcframework"); + fs::recreate_dir(&temp_dir)?; + + let output = cargo_target_dir.join(name); + fs::recreate_dir(&output)?; + + let xcframework = output.join(format!("{}.xcframework", name,)); + let swift_wrapper = output.join("swift-wrapper"); + XCFramework::new(&targets, &profile)?.create( + cargo_target_dir, + name, + &temp_dir, + &xcframework, + &swift_wrapper, + )?; + + Ok(()) +} + +// Represent a xcframework that contains static libraries for multiple platforms. +// +// Since `xcodebuild -create-xcframework` command requires its `-libraray` not +// having duplicated platform. This type along with `LibraryGroup` and `Slice` +// work together to make it easier to create a xcframework. +struct XCFramework { + libraries: Vec, +} + +// Represent a group of static libraries that are built for the same platform. +struct LibraryGroup { + id: LibraryGroupId, + slices: Vec, +} + +// Represent a thin static library which is built with `cargo build --target --profile ` +struct Slice { + target: String, + profile: String, +} + +impl XCFramework { + fn new(targets: &Vec, profile: &str) -> Result { + let mut groups = HashMap::::new(); + for target in targets { + let id = LibraryGroupId::from_target(target)?; + let id_clone = id.clone(); + groups + .entry(id) + .or_insert(LibraryGroup { + id: id_clone, + slices: Vec::new(), + }) + .slices + .push(Slice { + target: target.clone(), + profile: profile.to_owned(), + }); + } + + Ok(Self { + libraries: groups.into_values().collect(), + }) + } + + fn create( + &self, + cargo_target_dir: &Path, + library_file_name: &str, + temp_dir: &Path, + dest: &Path, + swift_wrapper_dir: &Path, + ) -> Result<()> { + self.preview(); + + let temp_dest = self.create_xcframework(cargo_target_dir, library_file_name, temp_dir)?; + self.patch_xcframework(&temp_dest)?; + + fs::recreate_dir(dest)?; + std::fs::rename(temp_dest, dest).with_context(|| "Failed to move xcframework")?; + println!("xcframework created at {}", &dest.display()); + + // It's okay to use the first element (or any element), since Swift binding files in all + // targets should be exactly the same. + fs::recreate_dir(swift_wrapper_dir)?; + for file in self.libraries[0].swift_binding_files(cargo_target_dir)? { + let dest = swift_wrapper_dir.join(file.file_name().unwrap()); + std::fs::copy(&file, &dest).with_context(|| { + format!("Failed to copy {} to {}", file.display(), dest.display()) + })?; + } + println!("Swift bindings created at {}", &swift_wrapper_dir.display()); + + Ok(()) + } + + fn preview(&self) { + println!("Creating xcframework to include the following targets:"); + for lib in &self.libraries { + println!(" Platform: {}", lib.id); + for slice in &lib.slices { + println!(" - {}", slice.target); + } + } + } + + fn create_xcframework( + &self, + cargo_target_dir: &Path, + library_file_name: &str, + temp_dir: &Path, + ) -> Result { + let temp_dest = temp_dir.join(format!("{}.xcframework", library_file_name)); + std::fs::remove_dir_all(&temp_dest).ok(); + + let library_args: Result> = self + .libraries + .iter() + .map(|library| { + let lib = library.create(cargo_target_dir, library_file_name, temp_dir)?; + let header = library.headers_dir(cargo_target_dir)?; + Ok((lib, header)) + }) + .collect(); + let library_args = library_args?; + + let library_args = library_args.iter().flat_map(|(lib, headers)| { + [ + "-library".as_ref(), + lib.as_os_str(), + "-headers".as_ref(), + headers.as_os_str(), + ] + }); + Command::new("xcodebuild") + .arg("-create-xcframework") + .args(library_args) + .arg("-output") + .arg(&temp_dest) + .successful_output()?; + + Ok(temp_dest) + } + + // Fixes an issue including the XCFramework in an Xcode project that already contains an XCFramework: https://github.com/jessegrosjean/module-map-error + fn patch_xcframework(&self, temp_dir: &Path) -> Result<()> { + println!("Patching XCFramework to have a unique header directory"); + + for dir_entry in std::fs::read_dir(temp_dir)? { + let path = dir_entry.expect("Invalid Path").path(); + if path.is_dir() { + let headers_dir = temp_dir.join(&path).join("Headers"); + let non_lib_files: Vec = std::fs::read_dir(&headers_dir)? + .flat_map(|f| f.ok()) + .filter_map(|f| { + if f.path().ends_with(".a") { + None + } else { + Some(f.path()) + } + }) + .collect(); + + let new_headers_dir = headers_dir.join("libwordpressFFI"); + fs::recreate_dir(&new_headers_dir)?; + + for file in non_lib_files { + std::fs::rename(&file, new_headers_dir.join(file.file_name().unwrap()))?; + } + } + } + + Ok(()) + } +} + +impl LibraryGroup { + fn create( + &self, + cargo_target_dir: &Path, + library_file_name: &str, + temp_dir: &Path, + ) -> Result { + let mut libraries: Vec = Vec::new(); + for slice in &self.slices { + libraries.push(slice.create(cargo_target_dir, library_file_name, temp_dir)?); + } + + let dir = temp_dir.join(self.id.to_string()); + fs::recreate_dir(&dir)?; + + let dest = dir.join(format!("{}.a", library_file_name)); + Command::new("xcrun") + .arg("lipo") + .arg("-create") + .args(libraries) + .arg("-output") + .arg(&dest) + .successful_output()?; + + Ok(dest) + } + + fn swift_bindings_dir(&self, cargo_target_dir: &Path) -> Result { + let slice = self + .slices + .first() + .with_context(|| "No slices in library group")?; + let path = slice + .built_product_dir(cargo_target_dir) + .join("swift-bindings"); + if !path.exists() { + anyhow::bail!("Headers not found: {}", path.display()) + } + Ok(path) + } + + fn headers_dir(&self, cargo_target_dir: &Path) -> Result { + let path = self.swift_bindings_dir(cargo_target_dir)?.join("headers"); + if !path.exists() { + anyhow::bail!("Headers not found: {}", path.display()) + } + Ok(path) + } + + fn swift_binding_files(&self, cargo_target_dir: &Path) -> Result> { + self.swift_bindings_dir(cargo_target_dir)? + .files_with_extension("swift") + } +} + +impl Slice { + fn create( + &self, + cargo_target_dir: &Path, + library_file_name: &str, + temp_dir: &Path, + ) -> Result { + let libs = self.built_libraries(cargo_target_dir)?; + + // If there are more static libraries (a.k.a cargo packages), we'll + // need to bundle them together into one static library. + // At the moment, we only have one libwp_api, so we can just copy it. + assert!( + libs.len() == 1, + "Expected exactly one library for each slice. Found: {:?}", + libs + ); + + let lib = &libs[0]; + if !lib.exists() { + anyhow::bail!("Library not found: {}", lib.display()) + } + + let dir = temp_dir.join(&self.target); + fs::recreate_dir(&dir)?; + + let dest = dir.join(format!("{}.a", library_file_name)); + std::fs::copy(lib, &dest) + .with_context(|| format!("Failed to copy {} to {}", lib.display(), dest.display()))?; + + Ok(dest) + } + + /// Returns the directory where the built static libraries are located. + fn built_product_dir(&self, cargo_target_dir: &Path) -> PathBuf { + let mut target_dir: PathBuf = cargo_target_dir.join(&self.target); + if self.profile == "dev" { + target_dir.push("debug"); + } else { + target_dir.push(&self.profile); + } + + target_dir + } + + fn built_libraries(&self, cargo_target_dir: &Path) -> Result> { + self.built_product_dir(cargo_target_dir) + .files_with_extension("a") + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct LibraryGroupId { + os: ApplePlatform, + is_sim: bool, +} + +impl LibraryGroupId { + fn from_target(target: &str) -> Result { + let mut parts = target.split('-'); + _ /* arch */= parts.next(); + if parts.next() != Some("apple") { + anyhow::bail!("{} is not an Apple platform", target) + } + + let os: ApplePlatform = parts + .next() + .with_context(|| format!("No OS in target: {}", target))? + .try_into()?; + + let output = Command::new("rustc") + .env("RUSTC_BOOTSTRAP", "1") + .args([ + "-Z", + "unstable-options", + "--print", + "target-spec-json", + "--target", + ]) + .arg(target) + .successful_output()?; + let json = serde_json::from_slice::(&output.stdout) + .with_context(|| "Failed to parse command output as JSON")?; + let llvm_target = json + .get("llvm-target") + .and_then(|t| t.as_str()) + .with_context(|| "No llvm-target in command output")?; + + Ok(Self { + os, + is_sim: llvm_target.ends_with("-simulator"), + }) + } +} + +impl Display for LibraryGroupId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.os)?; + + if self.is_sim { + write!(f, "-sim") + } else { + Ok(()) + } + } +} + +trait ExecuteCommand { + fn successful_output(&mut self) -> Result; +} + +impl ExecuteCommand for Command { + fn successful_output(&mut self) -> Result { + let output = self + .output() + .with_context(|| format!("Command failed: $ {:?}", self))?; + if output.status.success() { + Ok(output) + } else { + anyhow::bail!( + "Command failed with exit code: {}\nstdout: {:?}\nstderr: {:?}\n$ {:?}", + output.status, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + self + ) + } + } +} From e0275268eb11dfb7277f4b59ecd78fbff7357da5 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 17 Dec 2024 21:07:02 +1300 Subject: [PATCH 02/19] Generate Package.swift --- Cargo.lock | 76 ++++++++++++++++++ Cargo.toml | 2 + src/build.rs | 34 +++++++- src/cli.rs | 30 +++++++ src/main.rs | 1 + src/spm.rs | 168 ++++++++++++++++++++++++++++++++++++++++ templates/Package.swift | 154 ++++++++++++++++++++++++++++++++++++ 7 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 src/spm.rs create mode 100644 templates/Package.swift diff --git a/Cargo.lock b/Cargo.lock index 4572925..d383d6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,6 +252,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -264,6 +273,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "log" version = "0.4.22" @@ -320,6 +335,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "plain" version = "0.2.3" @@ -344,6 +371,53 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rinja" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5" +dependencies = [ + "humansize", + "itoa", + "percent-encoding", + "rinja_derive", +] + +[[package]] +name = "rinja_derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b" +dependencies = [ + "basic-toml", + "memchr", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "rinja_parser", + "rustc-hash", + "serde", + "syn", +] + +[[package]] +name = "rinja_parser" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610" +dependencies = [ + "memchr", + "nom", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "ryu" version = "1.0.18" @@ -536,6 +610,8 @@ dependencies = [ "anyhow", "cargo_metadata 0.19.1", "clap", + "pathdiff", + "rinja", "serde_json", "uniffi", "uniffi_bindgen", diff --git a/Cargo.toml b/Cargo.toml index a7ac6a3..33bc7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" anyhow = "1.0" cargo_metadata = "0.19" clap = { version = "4.5", features = ["derive"] } +rinja = "0.3" serde_json = "1.0" uniffi = "0.28.3" uniffi_bindgen = "0.28.3" +pathdiff = "0.2" diff --git a/src/build.rs b/src/build.rs index 732d249..29b83c3 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,5 +1,6 @@ use std::{ fs::File, + io::{BufRead, BufReader}, path::{Path, PathBuf}, process::Command, }; @@ -162,6 +163,7 @@ fn generate_bindings(library_path: &Path, module_name: &str) -> Result uniffi_bindgen::bindings::generate_swift_bindings(options)?; reorganize_binding_files(&out_dir, module_name)?; + fix_swift_bindings(&out_dir)?; Ok(out_dir) } @@ -187,8 +189,36 @@ fn reorganize_binding_files(bindings_dir: &Path, module_name: &str) -> Result<() writeln!(modulemap, r#" export *"#)?; writeln!(modulemap, r#"}}"#)?; - // TODO: https://github.com/mozilla/uniffi-rs/pull/2341 - // sed -i '' 's/^protocol UniffiForeignFutureTask /fileprivate protocol UniffiForeignFutureTask /' ".swift" + Ok(()) +} + +fn fix_swift_bindings(dir: &Path) -> Result<()> { + let swift_files = dir.files_with_extension("swift")?; + + for path in swift_files { + let file = File::open(&path)?; + let reader = BufReader::new(file); + + let updated_content: Vec = reader + .lines() + .map(|line| { + let line = line.unwrap_or_default(); + + // Can be removed once the PR is released (probably in 0.28.4). + // https://github.com/mozilla/uniffi-rs/pull/2341 + if line == "protocol UniffiForeignFutureTask {" { + "fileprivate protocol UniffiForeignFutureTask {".to_string() + } else { + line + } + }) + .collect(); + + let mut file = File::create(&path)?; + for line in updated_content { + writeln!(file, "{}", line)?; + } + } Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index c720780..6d7532e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::env; use anyhow::Result; @@ -5,6 +6,7 @@ use clap::{Parser, Subcommand}; use crate::apple_platform::ApplePlatform; use crate::build; +use crate::spm; #[derive(Parser)] pub(crate) struct Cli { @@ -15,6 +17,7 @@ pub(crate) struct Cli { #[derive(Subcommand)] enum Commands { Build(BuildArgs), + GeneratePackage(GeneratePackageArgs), } #[derive(Parser)] @@ -31,11 +34,24 @@ struct BuildArgs { ffi_module_name: String, } +#[derive(Parser)] +struct GeneratePackageArgs { + #[arg(long)] + package: String, + #[arg(long)] + ffi_module_name: String, + #[arg(long)] + project_name: String, + #[arg(long)] + package_name_map: String, +} + impl Cli { pub fn execute() -> Result<()> { let args = Cli::parse(); match args.command { Commands::Build(args) => build(args), + Commands::GeneratePackage(args) => generate_package(args), } } } @@ -58,3 +74,17 @@ fn build(args: BuildArgs) -> Result<()> { apple_platforms, ) } + +fn generate_package(args: GeneratePackageArgs) -> Result<()> { + let map = args.package_name_map.split(',') + .map(|pair| { + let mut iter = pair.split(':'); + let key = iter.next().unwrap(); + let value = iter.next().unwrap(); + (key.to_string(), value.to_string()) + }) + .collect::>(); + + // spm::generate_swift_package(&args.package, map) + spm::generate_swift_package(args.package, args.ffi_module_name, args.project_name, map) +} diff --git a/src/main.rs b/src/main.rs index 2cc60ce..1de7b9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod apple_platform; mod build; mod cli; +mod spm; mod utils; mod xcframework; diff --git a/src/spm.rs b/src/spm.rs new file mode 100644 index 0000000..64bb97f --- /dev/null +++ b/src/spm.rs @@ -0,0 +1,168 @@ +use std::{ + collections::HashMap, + fs::File, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{Context, Result}; +use cargo_metadata::{DependencyKind, MetadataCommand, Package}; +use pathdiff::diff_paths; +use rinja::Template; + +use crate::utils::ExecuteCommand; + +#[derive(Template)] +#[template(path = "Package.swift", escape = "none")] +struct PackageTemplate { + package_name: String, + ffi_module_name: String, + project_name: String, + targets: Vec, +} + +struct Target { + name: String, + library_source_path: String, + test_source_path: String, + dependencies: Vec, + has_test_resources: bool, +} + +impl Target { + fn new(name: String, package: &Package, root_dir: &Path) -> Result { + let root_dir = root_dir.canonicalize()?; + + if !package.id.repr.starts_with("git+") && !package.id.repr.starts_with("path+") { + anyhow::bail!("Unsupported package id: {}. We can only find Swift source code when package is integrated as a git repo or a local path.", package.id.repr) + } + + let metadata = MetadataCommand::new() + .manifest_path(&package.manifest_path) + .exec() + .with_context(|| format!("Can't get cargo metadata for package {}", package.name))?; + + let swift_code_dir = metadata + .workspace_root + .join("native/swift") + .canonicalize()?; + if !swift_code_dir.is_dir() { + anyhow::bail!( + "Swift code for package {} is not a directory at {}", + package.name, + &swift_code_dir.display() + ) + } + + // There could be 'Sources' and 'Tests' directories in the swift code directory. + // We need the 'Sources' directory. + let sources_dir = get_only_subdir(&swift_code_dir.join("Sources"))?; + let tests_dir = get_only_subdir(&swift_code_dir.join("Tests"))?; + + let library_source_path = relative_path(&sources_dir, &root_dir); + let test_source_path = relative_path(&tests_dir, &root_dir); + + Ok(Self { + name, + library_source_path, + test_source_path, + dependencies: vec![], + has_test_resources: tests_dir.join("Resources").exists(), + }) + } +} + +pub fn generate_swift_package( + top_level_package: String, + ffi_module_name: String, + project_name: String, + packages: HashMap, +) -> Result<()> { + let metadata = MetadataCommand::new() + .no_deps() + .exec() + .with_context(|| "Can't get cargo metadata")?; + + if metadata.workspace_root.as_std_path() != std::env::current_dir()? { + anyhow::bail!("The current directory is not the cargo root directory") + } + + let uniffi_packages: Vec<_> = metadata + .packages + .iter() + .filter(|p| packages.contains_key(&p.name)) + .collect(); + println!("Found {} uniffi packages", uniffi_packages.len()); + for pkg in &uniffi_packages { + println!(" - {}", pkg.name); + } + + let mut targets: Vec = vec![]; + for package in &uniffi_packages { + let name = packages + .get(&package.name) + .context(format!( + "No module name specified for package {}", + &package.name + ))? + .clone(); + let mut target = Target::new(name, package, metadata.workspace_root.as_std_path())?; + target.dependencies = package + .dependencies + .iter() + .filter(|d| d.name == target.name && !d.optional && d.kind == DependencyKind::Normal) + .map(|d| { + let spm_target_name = packages + .get(&d.name) + .context("No module name specified for dependency")?; + Ok(spm_target_name.clone()) + }) + .collect::>>()?; + targets.push(target); + } + + let template = PackageTemplate { + package_name: packages.get(&top_level_package).unwrap().clone(), + ffi_module_name, + project_name, + targets, + }; + let content = template.render()?; + let dest = metadata.workspace_root.join("Package.swift"); + File::create(&dest)?.write_all(content.as_bytes())?; + + Command::new("swift") + .args(["format", "--in-place"]) + .arg(&dest) + .successful_output()?; + + Ok(()) +} + +fn get_only_subdir(path: &Path) -> Result { + let subdirs = path + .read_dir()? + .map(|p| p.context("Can't read directory entry")) + .collect::>>()?; + if subdirs.len() != 1 { + anyhow::bail!( + "Expected 1 subdirectory in {}, found {:?}", + path.display(), + subdirs + ) + } + Ok(subdirs[0].path()) +} + +fn relative_path(path: P, base: B) -> String +where + P: AsRef, + B: AsRef, +{ + diff_paths(path, base) + .as_ref() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + .unwrap() +} diff --git a/templates/Package.swift b/templates/Package.swift new file mode 100644 index 0000000..c0a3136 --- /dev/null +++ b/templates/Package.swift @@ -0,0 +1,154 @@ +// swift-tools-version: 6.0 + +import Foundation +import PackageDescription + +let projectName = "{{ project_name }}" +let packageName = "{{ package_name }}" +let ffiModuleName = "{{ ffi_module_name }}" + +// SPM targets +let libraryTargetName = packageName +let swiftWrapperTargetName = "\(packageName)Internal" +let testTargetName = "\(packageName)Tests" + +// Source code paths +let ffiSwiftWrapperSourcePath = "target/\(ffiModuleName)/swift-wrapper" +let ffiXCFrameworkPath = "target/\(ffiModuleName)/\(ffiModuleName).xcframework" + +let ffiVersion: FFIVersion = .local + +#if os(Linux) +let ffiTarget: Target = .systemLibrary( + name: ffiModuleName, + path: "target/release/\(ffiModuleName)-linux/" + ) +#elseif os(macOS) +let ffiTarget: Target = ffiVersion.target +#endif + +var package = Package( + name: packageName, + platforms: [ + .iOS(.v13), + .macOS(.v11), + .tvOS(.v13), + .watchOS(.v8) + ], + products: [ + .library( + name: packageName, + targets: [libraryTargetName] + ) + ], + dependencies: [], + targets: [ + {% for target in targets %} + .target( + name: "{{ target.name }}", + dependencies: [ + .target(name: swiftWrapperTargetName), + {% for dep in target.dependencies %} + .target(name: "{{ dep }}"), + {% endfor %} + ], + path: "{{ target.library_source_path }}", + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + ] + ), + {% endfor %} + + .target( + name: swiftWrapperTargetName, + dependencies: [ + .target(name: ffiTarget.name) + ], + path: ffiSwiftWrapperSourcePath, + swiftSettings: [ + .swiftLanguageMode(.v5) + ] + ), + ffiTarget, + {% for target in targets %} + .testTarget( + name: "{{ target.name }}Tests", + dependencies: [ + .target(name: "{{ target.name }}"), + .target(name: ffiTarget.name) + ], + path: "{{ target.test_source_path }}", + resources: [ + {% if target.has_test_resources %} + .process("Resources") + {% endif %} + ] + ) + {% endfor %} + ] +) + +// MARK: - Enable local development toolings + +let localDevelopment = ffiVersion.isLocal + +if localDevelopment { + try enableSwiftLint() +} + +// MARK: - Helpers + +enum FFIVersion { + case local + case release(version: String, checksum: String) + + var isLocal: Bool { + if case .local = self { + return true + } + return false + } + + var target: Target { + switch self { + case .local: + return .binaryTarget(name: ffiModuleName, path: ffiXCFrameworkPath) + case let .release(version, checksum): + return .binaryTarget( + name: ffiModuleName, + url: "https://cdn.a8c-ci.services/\(projectName)/\(version)/\(ffiModuleName).xcframework.zip", + checksum: checksum + ) + } + } +} + +// Add SwiftLint to the package so that we can see linting issues directly from Xcode. +@MainActor +func enableSwiftLint() throws { +#if os(macOS) + let filePath = URL(string:"./.swiftlint.yml", relativeTo: URL(filePath: #filePath))! + let version = try String(contentsOf: filePath, encoding: .utf8) + .split(separator: "\n") + .first(where: { $0.starts(with: "swiftlint_version") })? + .split(separator: ":") + .last? + .trimmingCharacters(in: .whitespaces) + guard let version else { + fatalError("Can't find swiftlint_version in .swiftlint.yml") + } + + package.dependencies.append(.package(url: "https://github.com/realm/SwiftLint", exact: .init(version)!)) + + var platforms = package.platforms ?? [] + if let mac = platforms.firstIndex(where: { $0 == .macOS(.v11) }) { + platforms.remove(at: mac) + platforms.append(.macOS(.v12)) + } + package.platforms = platforms + + if let target = package.targets.first(where: { $0.name == "WordPressAPI" }) { + target.plugins = (target.plugins ?? []) + [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")] + } +#endif +} From 0513b2265b2c15be682c6716de3ca5f27fbd751c Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 17 Dec 2024 21:09:45 +1300 Subject: [PATCH 03/19] Fix import in generated swift binding --- Cargo.lock | 67 +++++++++++++++++++++++++ Cargo.toml | 3 +- src/apple_platform.rs | 7 +-- src/build.rs | 91 ++++++++++++++++++++-------------- src/cli.rs | 4 +- templates/binding-prefix.swift | 5 ++ templates/module.modulemap | 7 +++ 7 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 templates/binding-prefix.swift create mode 100644 templates/module.modulemap diff --git a/Cargo.lock b/Cargo.lock index d383d6c..12077ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytes" version = "1.9.0" @@ -174,6 +180,12 @@ dependencies = [ "thiserror 2.0.7", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.23" @@ -220,6 +232,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fs-err" version = "2.11.0" @@ -273,12 +301,24 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.22" @@ -418,6 +458,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.18" @@ -520,6 +573,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "textwrap" version = "0.16.1" @@ -613,6 +679,7 @@ dependencies = [ "pathdiff", "rinja", "serde_json", + "tempfile", "uniffi", "uniffi_bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 33bc7be..25a4299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,9 @@ edition = "2021" anyhow = "1.0" cargo_metadata = "0.19" clap = { version = "4.5", features = ["derive"] } +pathdiff = "0.2" rinja = "0.3" serde_json = "1.0" +tempfile = "3.14" uniffi = "0.28.3" uniffi_bindgen = "0.28.3" -pathdiff = "0.2" diff --git a/src/apple_platform.rs b/src/apple_platform.rs index a055eee..99e23e8 100644 --- a/src/apple_platform.rs +++ b/src/apple_platform.rs @@ -11,12 +11,7 @@ pub enum ApplePlatform { impl ApplePlatform { pub fn all() -> Vec { - vec![ - Self::MacOS, - Self::IOS, - Self::TvOS, - Self::WatchOS, - ] + vec![Self::MacOS, Self::IOS, Self::TvOS, Self::WatchOS] } pub fn target_triples(&self) -> Vec<&'static str> { diff --git a/src/build.rs b/src/build.rs index 29b83c3..1e07d50 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,14 +1,14 @@ use std::{ fs::File, - io::{BufRead, BufReader}, + io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::Command, }; -use std::io::Write; - use anyhow::{Context, Result}; use cargo_metadata::{Metadata, MetadataCommand}; +use rinja::Template; +use tempfile::tempdir; use uniffi_bindgen::bindings::SwiftBindingsOptions; use crate::utils::*; @@ -145,7 +145,7 @@ fn build_uniffi_package( } } -fn generate_bindings(library_path: &Path, module_name: &str) -> Result { +fn generate_bindings(library_path: &Path, ffi_module_name: &str) -> Result { let out_dir = library_path.parent().unwrap().join("swift-bindings"); fs::recreate_dir(&out_dir)?; @@ -162,62 +162,77 @@ fn generate_bindings(library_path: &Path, module_name: &str) -> Result }; uniffi_bindgen::bindings::generate_swift_bindings(options)?; - reorganize_binding_files(&out_dir, module_name)?; - fix_swift_bindings(&out_dir)?; + reorganize_binding_files(&out_dir, ffi_module_name)?; + fix_swift_bindings(&out_dir, ffi_module_name)?; Ok(out_dir) } -fn reorganize_binding_files(bindings_dir: &Path, module_name: &str) -> Result<()> { +fn reorganize_binding_files(bindings_dir: &Path, ffi_module_name: &str) -> Result<()> { + #[derive(Template)] + #[template(path = "module.modulemap", escape = "none")] + struct ModuleMapTemplate { + ffi_module_name: String, + header_files: Vec, + } + let headers_dir = bindings_dir.join("Headers"); fs::recreate_dir(&headers_dir)?; - let modulemap_path = headers_dir.join("module.modulemap"); - let mut modulemap = File::create_new(modulemap_path)?; - writeln!(modulemap, r#"module {} {{"#, module_name)?; + let mut header_files = vec![]; for entry in std::fs::read_dir(bindings_dir)? { let entry = entry?; if entry.path().extension() == Some("h".as_ref()) { - writeln!( - modulemap, - r#" header "{}""#, - entry.file_name().to_str().unwrap() - )?; + header_files.push(entry.file_name().into_string().unwrap()); fs::move_file(&entry.path(), &headers_dir)?; } } - writeln!(modulemap, r#" export *"#)?; - writeln!(modulemap, r#"}}"#)?; + + let template = ModuleMapTemplate { + ffi_module_name: ffi_module_name.to_string(), + header_files, + }; + let content = template.render()?; + let mut modulemap = File::create_new(headers_dir.join("module.modulemap"))?; + modulemap.write_all(content.as_bytes())?; Ok(()) } -fn fix_swift_bindings(dir: &Path) -> Result<()> { +fn fix_swift_bindings(dir: &Path, ffi_module_name: &str) -> Result<()> { let swift_files = dir.files_with_extension("swift")?; + let tempdir = tempdir()?; + + #[derive(Template)] + #[template(path = "binding-prefix.swift", escape = "none")] + struct PrefixTemplate { + ffi_module_name: String, + } + let prefix = PrefixTemplate { + ffi_module_name: ffi_module_name.to_string(), + } + .render()?; for path in swift_files { - let file = File::open(&path)?; - let reader = BufReader::new(file); - - let updated_content: Vec = reader - .lines() - .map(|line| { - let line = line.unwrap_or_default(); - - // Can be removed once the PR is released (probably in 0.28.4). - // https://github.com/mozilla/uniffi-rs/pull/2341 - if line == "protocol UniffiForeignFutureTask {" { - "fileprivate protocol UniffiForeignFutureTask {".to_string() - } else { - line - } - }) - .collect(); + let reader = BufReader::new(File::open(&path)?); + let tempfile_path = tempdir.path().join("temp.swift"); + let mut tempfile = File::create(&tempfile_path)?; + + writeln!(tempfile, "{}\n", prefix)?; - let mut file = File::create(&path)?; - for line in updated_content { - writeln!(file, "{}", line)?; + for line in reader.lines() { + let mut line = line?; + if line == "protocol UniffiForeignFutureTask {" { + line = "fileprivate protocol UniffiForeignFutureTask {".to_string() + } + + writeln!(tempfile, "{}", line)?; } + + tempfile.sync_all()?; + std::mem::drop(tempfile); + + std::fs::copy(tempfile_path, path)?; } Ok(()) diff --git a/src/cli.rs b/src/cli.rs index 6d7532e..158af86 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -76,7 +76,9 @@ fn build(args: BuildArgs) -> Result<()> { } fn generate_package(args: GeneratePackageArgs) -> Result<()> { - let map = args.package_name_map.split(',') + let map = args + .package_name_map + .split(',') .map(|pair| { let mut iter = pair.split(':'); let key = iter.next().unwrap(); diff --git a/templates/binding-prefix.swift b/templates/binding-prefix.swift new file mode 100644 index 0000000..94ba637 --- /dev/null +++ b/templates/binding-prefix.swift @@ -0,0 +1,5 @@ +// This is a harmless import statement. It is used to ensure the FFI xcframework +// module is imported, when multiple uniffi packages are built into one xcframework. +#if canImport({{ ffi_module_name }}) +import {{ ffi_module_name }}; +#endif diff --git a/templates/module.modulemap b/templates/module.modulemap new file mode 100644 index 0000000..810290d --- /dev/null +++ b/templates/module.modulemap @@ -0,0 +1,7 @@ +module {{ ffi_module_name }} { + {%- for file in header_files -%} + header "{{ file }}" + {%- endfor -%} + + export * +} From d48ae27a66727a8fcc526e3325c2a35a8ca9eeda Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 17 Dec 2024 21:25:43 +1300 Subject: [PATCH 04/19] Turn into a library --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/{main.rs => lib.rs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/{main.rs => lib.rs} (71%) diff --git a/Cargo.lock b/Cargo.lock index 12077ad..349cd7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,7 +670,7 @@ dependencies = [ ] [[package]] -name = "uniffi-swift-helpers" +name = "uniffi-swift-helper" version = "0.1.0" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 25a4299..c984a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "uniffi-swift-helpers" +name = "uniffi-swift-helper" version = "0.1.0" edition = "2021" diff --git a/src/main.rs b/src/lib.rs similarity index 71% rename from src/main.rs rename to src/lib.rs index 1de7b9c..c0b363c 100644 --- a/src/main.rs +++ b/src/lib.rs @@ -5,6 +5,6 @@ mod spm; mod utils; mod xcframework; -fn main() -> anyhow::Result<()> { +pub fn cli_main() -> anyhow::Result<()> { cli::Cli::execute() } From b0c2ac2dbcc230105815840febc7cceccae81a21 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 18 Dec 2024 10:54:01 +1300 Subject: [PATCH 05/19] Split internal targets --- src/spm.rs | 72 +++++++++++++++++++++++++++++++++++++++-- src/utils.rs | 20 ++++++++++++ templates/Package.swift | 29 ++++++++++------- 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/src/spm.rs b/src/spm.rs index 64bb97f..d6b6db2 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -11,7 +11,7 @@ use cargo_metadata::{DependencyKind, MetadataCommand, Package}; use pathdiff::diff_paths; use rinja::Template; -use crate::utils::ExecuteCommand; +use crate::utils::{fs, ExecuteCommand, FileSystemExtensions}; #[derive(Template)] #[template(path = "Package.swift", escape = "none")] @@ -20,6 +20,7 @@ struct PackageTemplate { ffi_module_name: String, project_name: String, targets: Vec, + internal_targets: Vec, } struct Target { @@ -43,7 +44,7 @@ impl Target { .exec() .with_context(|| format!("Can't get cargo metadata for package {}", package.name))?; - let swift_code_dir = metadata + let mut swift_code_dir = metadata .workspace_root .join("native/swift") .canonicalize()?; @@ -55,6 +56,31 @@ impl Target { ) } + if !swift_code_dir.starts_with(&root_dir) { + println!( + "{} swift code directory is outside of the cargo root directory.", + package.name + ); + println!( + "⚠️ Remember to run the command again when {} cargo dependency is updated.", + package.name + ); + + println!("Copying swift code directory to the cargo root directory"); + + let cargo_target_dir = root_dir.join("target"); + let vendor_path = cargo_target_dir.join("uniffi-swift-helper/vendor"); + let new_path = vendor_path.join(&package.name); + fs::recreate_dir(new_path.as_path())?; + + println!(" - from: {}", swift_code_dir.display()); + println!(" - to: {}", new_path.display()); + + fs::copy_dir(&swift_code_dir, &new_path)?; + + swift_code_dir = new_path; + } + // There could be 'Sources' and 'Tests' directories in the swift code directory. // We need the 'Sources' directory. let sources_dir = get_only_subdir(&swift_code_dir.join("Sources"))?; @@ -80,7 +106,6 @@ pub fn generate_swift_package( packages: HashMap, ) -> Result<()> { let metadata = MetadataCommand::new() - .no_deps() .exec() .with_context(|| "Can't get cargo metadata")?; @@ -122,11 +147,17 @@ pub fn generate_swift_package( targets.push(target); } + let internal_targets = internal_targets( + Path::new(&format!("target/{}/swift-wrapper", &ffi_module_name)), + &packages, + )?; + let template = PackageTemplate { package_name: packages.get(&top_level_package).unwrap().clone(), ffi_module_name, project_name, targets, + internal_targets, }; let content = template.render()?; let dest = metadata.workspace_root.join("Package.swift"); @@ -166,3 +197,38 @@ where .map(|s| s.to_string()) .unwrap() } + +struct InternalTarget { + name: String, + swift_wrapper_dir: String, + source_file: String, +} + +fn internal_targets( + swift_wrapper_dir: &Path, + packages: &HashMap, +) -> Result> { + let files = swift_wrapper_dir.files_with_extension("swift")?; + if files.is_empty() { + anyhow::bail!( + "No Swift source files found in {}. Run the build command first.", + swift_wrapper_dir.display() + ) + } + + let targets = files.iter().map(|f| { + let file_name = f.file_name().and_then(|f| f.to_str()).unwrap(); + let cargo_package_name = file_name + .strip_suffix(".swift") + .map(|f| f.to_string()) + .unwrap(); + let target_name = packages.get(&cargo_package_name).unwrap(); + InternalTarget { + name: format!("{}Internal", target_name), + swift_wrapper_dir: swift_wrapper_dir.to_str().unwrap().to_string(), + source_file: file_name.to_string(), + } + }); + + Ok(targets.collect()) +} diff --git a/src/utils.rs b/src/utils.rs index 69474d1..fb632e9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -75,4 +75,24 @@ pub(crate) mod fs { Ok(destination) } + + pub fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> { + if !dst.exists() { + std::fs::create_dir_all(dst)?; + } + + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + + if src_path.is_dir() { + copy_dir(&src_path, &dst_path)?; + } else { + std::fs::copy(&src_path, &dst_path)?; + } + } + + Ok(()) + } } diff --git a/templates/Package.swift b/templates/Package.swift index c0a3136..7b0389d 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -47,7 +47,7 @@ var package = Package( .target( name: "{{ target.name }}", dependencies: [ - .target(name: swiftWrapperTargetName), + .target(name: "{{ target.name }}Internal"), {% for dep in target.dependencies %} .target(name: "{{ dep }}"), {% endfor %} @@ -59,17 +59,22 @@ var package = Package( ), {% endfor %} - .target( - name: swiftWrapperTargetName, - dependencies: [ - .target(name: ffiTarget.name) - ], - path: ffiSwiftWrapperSourcePath, - swiftSettings: [ - .swiftLanguageMode(.v5) - ] - ), + {% for target in internal_targets %} + .target( + name: "{{ target.name }}", + dependencies: [ + .target(name: ffiTarget.name) + ], + path: "{{ target.swift_wrapper_dir }}", + sources: ["{{ target.source_file }}"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] + ), + {% endfor %} + ffiTarget, + {% for target in targets %} .testTarget( name: "{{ target.name }}Tests", @@ -83,7 +88,7 @@ var package = Package( .process("Resources") {% endif %} ] - ) + ), {% endfor %} ] ) From 1b0d997ebebe86bbe3e1f4ebc0944d353ace0722 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 18 Dec 2024 15:34:46 +1300 Subject: [PATCH 06/19] Refine implementation details --- src/cli.rs | 3 +- src/spm.rs | 414 +++++++++++++++++++++++++--------------- src/utils.rs | 9 +- templates/Package.swift | 5 +- 4 files changed, 275 insertions(+), 156 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 158af86..0511130 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -88,5 +88,6 @@ fn generate_package(args: GeneratePackageArgs) -> Result<()> { .collect::>(); // spm::generate_swift_package(&args.package, map) - spm::generate_swift_package(args.package, args.ffi_module_name, args.project_name, map) + // spm::generate_swift_package(args.package, args.ffi_module_name, args.project_name, map) + spm::generate_swift_package2(args.ffi_module_name, args.project_name, map) } diff --git a/src/spm.rs b/src/spm.rs index d6b6db2..e0b6d23 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -7,11 +7,11 @@ use std::{ }; use anyhow::{Context, Result}; -use cargo_metadata::{DependencyKind, MetadataCommand, Package}; +use cargo_metadata::{camino::Utf8PathBuf, DependencyKind, Metadata, MetadataCommand, Package}; use pathdiff::diff_paths; use rinja::Template; -use crate::utils::{fs, ExecuteCommand, FileSystemExtensions}; +use crate::utils::{fs, ExecuteCommand}; #[derive(Template)] #[template(path = "Package.swift", escape = "none")] @@ -31,76 +31,52 @@ struct Target { has_test_resources: bool, } -impl Target { - fn new(name: String, package: &Package, root_dir: &Path) -> Result { - let root_dir = root_dir.canonicalize()?; - - if !package.id.repr.starts_with("git+") && !package.id.repr.starts_with("path+") { - anyhow::bail!("Unsupported package id: {}. We can only find Swift source code when package is integrated as a git repo or a local path.", package.id.repr) - } - - let metadata = MetadataCommand::new() - .manifest_path(&package.manifest_path) - .exec() - .with_context(|| format!("Can't get cargo metadata for package {}", package.name))?; - - let mut swift_code_dir = metadata - .workspace_root - .join("native/swift") - .canonicalize()?; - if !swift_code_dir.is_dir() { - anyhow::bail!( - "Swift code for package {} is not a directory at {}", - package.name, - &swift_code_dir.display() - ) - } - - if !swift_code_dir.starts_with(&root_dir) { - println!( - "{} swift code directory is outside of the cargo root directory.", - package.name - ); - println!( - "⚠️ Remember to run the command again when {} cargo dependency is updated.", - package.name - ); - - println!("Copying swift code directory to the cargo root directory"); - - let cargo_target_dir = root_dir.join("target"); - let vendor_path = cargo_target_dir.join("uniffi-swift-helper/vendor"); - let new_path = vendor_path.join(&package.name); - fs::recreate_dir(new_path.as_path())?; - - println!(" - from: {}", swift_code_dir.display()); - println!(" - to: {}", new_path.display()); - - fs::copy_dir(&swift_code_dir, &new_path)?; - - swift_code_dir = new_path; - } +fn get_only_subdir

(path: P) -> Result +where + P: AsRef, +{ + let path = path.as_ref(); + let subdirs = path + .read_dir()? + .map(|p| p.context("Can't read directory entry")) + .collect::>>()?; + if subdirs.len() != 1 { + anyhow::bail!( + "Expected 1 subdirectory in {}, found {:?}", + path.display(), + subdirs + ) + } + Ok(subdirs[0].path()) +} - // There could be 'Sources' and 'Tests' directories in the swift code directory. - // We need the 'Sources' directory. - let sources_dir = get_only_subdir(&swift_code_dir.join("Sources"))?; - let tests_dir = get_only_subdir(&swift_code_dir.join("Tests"))?; +fn relative_path(path: P, base: B) -> String +where + P: AsRef, + B: AsRef, +{ + diff_paths(path, base) + .as_ref() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + .unwrap() +} - let library_source_path = relative_path(&sources_dir, &root_dir); - let test_source_path = relative_path(&tests_dir, &root_dir); +struct InternalTarget { + name: String, + swift_wrapper_dir: String, + source_file: String, + dependencies: Vec, +} - Ok(Self { - name, - library_source_path, - test_source_path, - dependencies: vec![], - has_test_resources: tests_dir.join("Resources").exists(), - }) - } +#[derive(Debug)] +struct UniffiPackage { + name: String, + manifest_path: Utf8PathBuf, + dependencies: Vec, } -pub fn generate_swift_package( - top_level_package: String, +pub fn generate_swift_package2( ffi_module_name: String, project_name: String, packages: HashMap, @@ -113,54 +89,56 @@ pub fn generate_swift_package( anyhow::bail!("The current directory is not the cargo root directory") } - let uniffi_packages: Vec<_> = metadata + let uniffi_packages = metadata .packages .iter() - .filter(|p| packages.contains_key(&p.name)) - .collect(); - println!("Found {} uniffi packages", uniffi_packages.len()); - for pkg in &uniffi_packages { - println!(" - {}", pkg.name); - } - - let mut targets: Vec = vec![]; + .filter(|p| is_uniffi_package(p)) + .collect::>(); for package in &uniffi_packages { - let name = packages - .get(&package.name) - .context(format!( - "No module name specified for package {}", - &package.name - ))? - .clone(); - let mut target = Target::new(name, package, metadata.workspace_root.as_std_path())?; - target.dependencies = package - .dependencies - .iter() - .filter(|d| d.name == target.name && !d.optional && d.kind == DependencyKind::Normal) - .map(|d| { - let spm_target_name = packages - .get(&d.name) - .context("No module name specified for dependency")?; - Ok(spm_target_name.clone()) - }) - .collect::>>()?; - targets.push(target); + if !package.id.repr.starts_with("git+") && !package.id.repr.starts_with("path+") { + anyhow::bail!("Unsupported package id: {}. We can only find Swift source code when package is integrated as a git repo or a local path.", package.id.repr) + } } - let internal_targets = internal_targets( - Path::new(&format!("target/{}/swift-wrapper", &ffi_module_name)), - &packages, - )?; + let uniffi_packages = uniffi_packages + .iter() + .map(|p| UniffiPackage::new(p, &uniffi_packages)) + .collect::>(); + let top_level_package = uniffi_packages + .iter() + .find(|p| { + !uniffi_packages + .iter() + .any(|other| other.depends_on(&p.name)) + }) + .unwrap(); + + let resolver = SPMResolver { + metadata: ProjectMetadata { + ffi_module_name, + cargo_metadata: metadata.clone(), + }, + cargo_package_to_spm_target_map: packages, + }; + + let targets = uniffi_packages + .iter() + .map(|p| resolver.public_target(p)) + .collect::>>()?; + let internal_targets = uniffi_packages + .iter() + .map(|p| resolver.internal_target(p)) + .collect::>>()?; let template = PackageTemplate { - package_name: packages.get(&top_level_package).unwrap().clone(), - ffi_module_name, + package_name: resolver.spm_target_name(&top_level_package.name), + ffi_module_name: resolver.metadata.ffi_module_name.clone(), project_name, targets, internal_targets, }; let content = template.render()?; - let dest = metadata.workspace_root.join("Package.swift"); + let dest = resolver.swift_package_manifest_file_path(); File::create(&dest)?.write_all(content.as_bytes())?; Command::new("swift") @@ -171,64 +149,196 @@ pub fn generate_swift_package( Ok(()) } -fn get_only_subdir(path: &Path) -> Result { - let subdirs = path - .read_dir()? - .map(|p| p.context("Can't read directory entry")) - .collect::>>()?; - if subdirs.len() != 1 { - anyhow::bail!( - "Expected 1 subdirectory in {}, found {:?}", - path.display(), - subdirs - ) +fn is_uniffi_package(package: &Package) -> bool { + let depends_on_uniffi = package + .dependencies + .iter() + .any(|d| d.name == "uniffi" && !d.optional && d.kind == DependencyKind::Normal); + let has_uniffi_toml = package.manifest_path.with_file_name("uniffi.toml").exists(); + depends_on_uniffi && has_uniffi_toml +} + +impl UniffiPackage { + fn new(package: &Package, all_uniffi_packages: &Vec<&Package>) -> Self { + let dependencies: Vec<_> = package + .dependencies + .iter() + .filter_map(|d| { + all_uniffi_packages + .iter() + .find(|p| p.name == d.name) + .map(|p| Self::new(p, all_uniffi_packages)) + }) + .collect(); + + UniffiPackage { + name: package.name.clone(), + manifest_path: package.manifest_path.clone(), + dependencies, + } + } + + fn depends_on(&self, other: &str) -> bool { + self.dependencies.iter().any(|d| d.name == other) + } + + fn swift_wrapper_file_name(&self) -> String { + format!("{}.swift", self.name) } - Ok(subdirs[0].path()) } -fn relative_path(path: P, base: B) -> String -where - P: AsRef, - B: AsRef, -{ - diff_paths(path, base) - .as_ref() - .and_then(|s| s.to_str()) - .map(|s| s.to_string()) - .unwrap() +struct ProjectMetadata { + ffi_module_name: String, + cargo_metadata: Metadata, } -struct InternalTarget { - name: String, - swift_wrapper_dir: String, - source_file: String, +impl ProjectMetadata { + fn swift_wrapper_dir(&self) -> Utf8PathBuf { + self.cargo_metadata + .target_directory + .join(&self.ffi_module_name) + .join("swift-wrapper") + } } -fn internal_targets( - swift_wrapper_dir: &Path, - packages: &HashMap, -) -> Result> { - let files = swift_wrapper_dir.files_with_extension("swift")?; - if files.is_empty() { - anyhow::bail!( - "No Swift source files found in {}. Run the build command first.", - swift_wrapper_dir.display() - ) +struct SPMResolver { + metadata: ProjectMetadata, + cargo_package_to_spm_target_map: HashMap, +} + +impl SPMResolver { + fn swift_package_manifest_file_path(&self) -> Utf8PathBuf { + self.metadata + .cargo_metadata + .workspace_root + .join("Package.swift") + } + + fn spm_target_name(&self, cargo_package_name: &str) -> String { + self.cargo_package_to_spm_target_map + .get(cargo_package_name) + .unwrap_or_else(|| { + panic!( + "No SPM target name specified for cargo package {}", + cargo_package_name + ) + }) + .to_string() + } + + fn public_target_name(&self, package: &UniffiPackage) -> String { + self.spm_target_name(&package.name) + } + + fn internal_target_name(&self, package: &UniffiPackage) -> String { + format!("{}Internal", self.spm_target_name(&package.name)) + } + + fn internal_target(&self, package: &UniffiPackage) -> Result { + let swift_wrapper_dir = self.metadata.swift_wrapper_dir(); + let source_file_name = package.swift_wrapper_file_name(); + let binding_file = swift_wrapper_dir.join(&source_file_name); + if !binding_file.exists() { + anyhow::bail!( + "Swift wrapper file is not found at {}. Need to build xcframework first.", + binding_file + ) + } + + let dependencies = package + .dependencies + .iter() + .map(|p| self.internal_target_name(p)) + .collect::>(); + + Ok(InternalTarget { + name: self.internal_target_name(package), + swift_wrapper_dir: relative_path( + swift_wrapper_dir, + &self.metadata.cargo_metadata.workspace_root, + ), + source_file: source_file_name, + dependencies, + }) } - let targets = files.iter().map(|f| { - let file_name = f.file_name().and_then(|f| f.to_str()).unwrap(); - let cargo_package_name = file_name - .strip_suffix(".swift") - .map(|f| f.to_string()) - .unwrap(); - let target_name = packages.get(&cargo_package_name).unwrap(); - InternalTarget { - name: format!("{}Internal", target_name), - swift_wrapper_dir: swift_wrapper_dir.to_str().unwrap().to_string(), - source_file: file_name.to_string(), + fn public_target(&self, package: &UniffiPackage) -> Result { + let swift_code_dir = self.vend_swift_source_code(package)?; + + // There could be 'Sources' and 'Tests' directories in the swift code directory. + // We need the 'Sources' directory. + let sources_dir = get_only_subdir(swift_code_dir.join("Sources"))?; + let tests_dir = get_only_subdir(swift_code_dir.join("Tests"))?; + + let root_dir = &self.metadata.cargo_metadata.workspace_root; + let library_source_path = relative_path(&sources_dir, root_dir); + let test_source_path = relative_path(&tests_dir, root_dir); + + let dependencies = package + .dependencies + .iter() + .map(|p| self.spm_target_name(&p.name)) + .collect(); + + Ok(Target { + name: self.public_target_name(package), + library_source_path, + test_source_path, + dependencies, + has_test_resources: tests_dir.join("Resources").exists(), + }) + } + + fn vend_swift_source_code(&self, package: &UniffiPackage) -> Result { + let root_dir = &self.metadata.cargo_metadata.workspace_root; + if !root_dir.is_absolute() { + anyhow::bail!( + "Cargo workspace root dir is not an absolute path: {}", + root_dir + ) + } + + let metadata = MetadataCommand::new() + .manifest_path(&package.manifest_path) + .exec() + .with_context(|| format!("Can't get cargo metadata for package {}", package.name))?; + + let mut swift_code_dir = metadata.workspace_root.join("native/swift"); + if !swift_code_dir.is_dir() { + anyhow::bail!( + "Swift code for package {} is not a directory at {}", + package.name, + &swift_code_dir + ) + } + + if swift_code_dir.starts_with(root_dir) { + return Ok(swift_code_dir); } - }); - Ok(targets.collect()) + println!( + "{} swift code directory is outside of the cargo root directory.", + package.name + ); + println!( + "⚠️ Remember to run the command again when {} cargo dependency is updated.", + package.name + ); + + println!("Copying swift code directory to the cargo root directory"); + + let cargo_target_dir = root_dir.join("target"); + let vendor_path = cargo_target_dir.join("uniffi-swift-helper/vendor"); + let new_path = vendor_path.join(&package.name); + fs::recreate_dir(&new_path)?; + + println!(" - from: {}", swift_code_dir); + println!(" - to: {}", new_path); + + fs::copy_dir(&swift_code_dir, &new_path)?; + + swift_code_dir = new_path; + + Ok(swift_code_dir) + } } diff --git a/src/utils.rs b/src/utils.rs index fb632e9..363fabf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -50,7 +50,9 @@ pub(crate) mod fs { use super::*; - pub fn recreate_dir(dir: &Path) -> Result<()> { + pub fn recreate_dir

(dir: P) -> Result<()> where P: AsRef { + let dir = dir.as_ref(); + if dir.exists() { std::fs::remove_dir_all(dir) .with_context(|| format!("Failed to remove directory at {:?}", dir))?; @@ -76,7 +78,10 @@ pub(crate) mod fs { Ok(destination) } - pub fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> { + pub fn copy_dir

(src: P, dst: P) -> std::io::Result<()> where P: AsRef { + let src = src.as_ref(); + let dst = dst.as_ref(); + if !dst.exists() { std::fs::create_dir_all(dst)?; } diff --git a/templates/Package.swift b/templates/Package.swift index 7b0389d..3335bc0 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -47,10 +47,10 @@ var package = Package( .target( name: "{{ target.name }}", dependencies: [ - .target(name: "{{ target.name }}Internal"), {% for dep in target.dependencies %} .target(name: "{{ dep }}"), {% endfor %} + .target(name: "{{ target.name }}Internal") ], path: "{{ target.library_source_path }}", swiftSettings: [ @@ -63,6 +63,9 @@ var package = Package( .target( name: "{{ target.name }}", dependencies: [ + {% for dep in target.dependencies %} + .target(name: "{{ dep }}"), + {% endfor %} .target(name: ffiTarget.name) ], path: "{{ target.swift_wrapper_dir }}", From 11d19811edfb7204ae6e9c927c668f58ec12b05e Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 18 Dec 2024 22:21:11 +1300 Subject: [PATCH 07/19] Refactor global functions into types --- src/build.rs | 389 ++++++++++++++++++++++----------------------- src/cli.rs | 25 ++- src/lib.rs | 1 + src/project.rs | 140 ++++++++++++++++ src/spm.rs | 195 +++++------------------ src/utils.rs | 24 ++- src/xcframework.rs | 12 +- 7 files changed, 406 insertions(+), 380 deletions(-) create mode 100644 src/project.rs diff --git a/src/build.rs b/src/build.rs index 1e07d50..355fd3e 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,239 +1,228 @@ -use std::{ - fs::File, - io::{BufRead, BufReader, Write}, - path::{Path, PathBuf}, - process::Command, -}; - -use anyhow::{Context, Result}; -use cargo_metadata::{Metadata, MetadataCommand}; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use anyhow::Result; +use cargo_metadata::camino::Utf8PathBuf; use rinja::Template; use tempfile::tempdir; use uniffi_bindgen::bindings::SwiftBindingsOptions; use crate::utils::*; -use crate::{apple_platform::ApplePlatform, xcframework::create_xcframework}; - -pub fn build( - package: String, - profile: String, - ffi_module_name: String, - apple_platforms: Vec, -) -> Result<()> { - if profile != "release" && profile != "dev" { - anyhow::bail!( - "Profile must be either 'release' or 'dev', found {}", - profile - ) - } +use crate::{apple_platform::ApplePlatform, project::Project}; - let metadata = MetadataCommand::new() - .no_deps() - .exec() - .with_context(|| "Can't get cargo metadata")?; - let package = metadata - .packages - .iter() - .find(|p| p.name == package) - .context(format!( - "Package {} not found. Make sure run this command in the cargo root directory", - package - ))?; - - let target_dirs: Vec<_> = if apple_platforms.is_empty() { - build_uniffi_package(&package.name, &profile, None, &metadata)? - } else { - apple_platforms - .iter() - .map(|platform| { - build_uniffi_package(&package.name, &profile, Some(*platform), &metadata) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect() - }; - - for target_dir in target_dirs { - let libraries = target_dir.files_with_extension("a")?; - if libraries.len() != 1 { - anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) +pub trait BuildExtensions { + fn build(&self, profile: String, apple_platforms: Vec) -> Result<()>; +} + +impl BuildExtensions for Project { + fn build(&self, profile: String, apple_platforms: Vec) -> Result<()> { + if profile != "release" && profile != "dev" { + anyhow::bail!( + "Profile must be either 'release' or 'dev', found {}", + profile + ) } - generate_bindings(&libraries[0], &ffi_module_name)?; - } + let package = self.uniffi_package()?.name; - if apple_platforms.is_empty() { - // TODO: Linux - unimplemented!("Not implemented for Linux yet") - } else { - create_xcframework( - metadata.target_directory.as_std_path(), + let target_dirs: Vec<_> = if apple_platforms.is_empty() { + self.build_uniffi_package(&package, &profile, None)? + } else { apple_platforms .iter() - .flat_map(|p| p.target_triples()) - .map(|s| s.to_string()) - .collect(), - profile.to_string(), - &ffi_module_name, - ) + .map(|platform| self.build_uniffi_package(&package, &profile, Some(*platform))) + .collect::>>()? + .into_iter() + .flatten() + .collect() + }; + + for target_dir in target_dirs { + let libraries = target_dir.files_with_extension("a")?; + if libraries.len() != 1 { + anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) + } + + self.generate_bindings(&libraries[0])?; + } + + if apple_platforms.is_empty() { + // TODO: Linux + unimplemented!("Not implemented for Linux yet") + } else { + crate::xcframework::create_xcframework( + self.cargo_metadata.target_directory.as_std_path(), + apple_platforms + .iter() + .flat_map(|p| p.target_triples()) + .map(|s| s.to_string()) + .collect(), + profile.to_string(), + &self.ffi_module_name, + self.xcframework_path().as_std_path(), + self.swift_wrapper_dir().as_std_path(), + ) + } } } -fn build_uniffi_package( - package: &str, - profile: &str, - platform: Option, - metadata: &Metadata, -) -> Result> { - let profile_dirname = match profile { - "release" => "release", - "dev" => "debug", - _ => anyhow::bail!("Invalid profile: {}", profile), - }; - - let mut build = vec!["cargo"]; - - if platform - .as_ref() - .map_or(false, |p| p.requires_nightly_toolchain()) - { - // TODO: Use a specific nightly toolchain? - build.extend(["+nightly", "-Z", "build-std=panic_abort,std"]); - } +impl Project { + fn build_uniffi_package( + &self, + package: &str, + profile: &str, + platform: Option, + ) -> Result> { + let profile_dirname = match profile { + "release" => "release", + "dev" => "debug", + _ => anyhow::bail!("Invalid profile: {}", profile), + }; + + let mut build = vec!["cargo"]; + + if platform + .as_ref() + .map_or(false, |p| p.requires_nightly_toolchain()) + { + // TODO: Use a specific nightly toolchain? + build.extend(["+nightly", "-Z", "build-std=panic_abort,std"]); + } - // Include debug symbols. - let config_debug = format!("profile.{}.debug=true", profile); - // Abort on panic to include Rust backtrace in crash reports. - let panic_config = format!(r#"profile.{}.panic="abort""#, profile); - build.extend(["--config", &config_debug, "--config", &panic_config]); + // Include debug symbols. + let config_debug = format!("profile.{}.debug=true", profile); + // Abort on panic to include Rust backtrace in crash reports. + let panic_config = format!(r#"profile.{}.panic="abort""#, profile); + build.extend(["--config", &config_debug, "--config", &panic_config]); - build.extend(["build", "--package", package, "--profile", profile]); + build.extend(["build", "--package", package, "--profile", profile]); - let cargo_target_dir: &Path = metadata.target_directory.as_std_path(); + let cargo_target_dir = &self.cargo_metadata.target_directory; + let targets = platform.as_ref().map_or(vec![], |p| p.target_triples()); + if targets.is_empty() { + let mut cmd = Command::new(build[0]); + cmd.args(&build[1..]); - let targets = platform.as_ref().map_or(vec![], |p| p.target_triples()); - if targets.is_empty() { - let mut cmd = Command::new(build[0]); - cmd.args(&build[1..]); + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!("Failed to build package {}", package) + } - println!("$ {:?}", cmd); - if !cmd.spawn()?.wait()?.success() { - anyhow::bail!("Failed to build package {}", package) + let target_dir = cargo_target_dir.join(profile_dirname); + Ok(vec![target_dir]) + } else { + targets + .into_iter() + .map(|target| { + let mut cmd = Command::new(build[0]); + cmd.args(&build[1..]); + cmd.args(["--target", target]); + + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!("Failed to build package {} for target {}", package, target) + } + + let target_dir = cargo_target_dir.join(target).join(profile_dirname); + + Ok(target_dir) + }) + .collect() } - - let target_dir = cargo_target_dir.join(profile_dirname); - Ok(vec![target_dir]) - } else { - targets - .into_iter() - .map(|target| { - let mut cmd = Command::new(build[0]); - cmd.args(&build[1..]); - cmd.args(["--target", target]); - - println!("$ {:?}", cmd); - if !cmd.spawn()?.wait()?.success() { - anyhow::bail!("Failed to build package {} for target {}", package, target) - } - - let target_dir = cargo_target_dir.join(target).join(profile_dirname); - - Ok(target_dir) - }) - .collect() } -} -fn generate_bindings(library_path: &Path, ffi_module_name: &str) -> Result { - let out_dir = library_path.parent().unwrap().join("swift-bindings"); - fs::recreate_dir(&out_dir)?; - - let options = SwiftBindingsOptions { - generate_swift_sources: true, - generate_headers: true, - generate_modulemap: false, - library_path: library_path.to_path_buf().try_into()?, - out_dir: out_dir.clone().try_into()?, - xcframework: false, - module_name: None, - modulemap_filename: None, - metadata_no_deps: false, - }; - uniffi_bindgen::bindings::generate_swift_bindings(options)?; - - reorganize_binding_files(&out_dir, ffi_module_name)?; - fix_swift_bindings(&out_dir, ffi_module_name)?; - - Ok(out_dir) -} - -fn reorganize_binding_files(bindings_dir: &Path, ffi_module_name: &str) -> Result<()> { - #[derive(Template)] - #[template(path = "module.modulemap", escape = "none")] - struct ModuleMapTemplate { - ffi_module_name: String, - header_files: Vec, + fn generate_bindings(&self, library_path: &Path) -> Result { + let out_dir = library_path.parent().unwrap().join("swift-bindings"); + fs::recreate_dir(&out_dir)?; + + let options = SwiftBindingsOptions { + generate_swift_sources: true, + generate_headers: true, + generate_modulemap: false, + library_path: library_path.to_path_buf().try_into()?, + out_dir: out_dir.clone().try_into()?, + xcframework: false, + module_name: None, + modulemap_filename: None, + metadata_no_deps: false, + }; + uniffi_bindgen::bindings::generate_swift_bindings(options)?; + + self.reorganize_binding_files(&out_dir)?; + self.fix_swift_bindings(&out_dir)?; + + Ok(out_dir) } - let headers_dir = bindings_dir.join("Headers"); - fs::recreate_dir(&headers_dir)?; - - let mut header_files = vec![]; - for entry in std::fs::read_dir(bindings_dir)? { - let entry = entry?; - if entry.path().extension() == Some("h".as_ref()) { - header_files.push(entry.file_name().into_string().unwrap()); - fs::move_file(&entry.path(), &headers_dir)?; + fn reorganize_binding_files(&self, bindings_dir: &Path) -> Result<()> { + #[derive(Template)] + #[template(path = "module.modulemap", escape = "none")] + struct ModuleMapTemplate { + ffi_module_name: String, + header_files: Vec, } - } - let template = ModuleMapTemplate { - ffi_module_name: ffi_module_name.to_string(), - header_files, - }; - let content = template.render()?; - let mut modulemap = File::create_new(headers_dir.join("module.modulemap"))?; - modulemap.write_all(content.as_bytes())?; + let headers_dir = bindings_dir.join("Headers"); + fs::recreate_dir(&headers_dir)?; - Ok(()) -} + let mut header_files = vec![]; + for entry in std::fs::read_dir(bindings_dir)? { + let entry = entry?; + if entry.path().extension() == Some("h".as_ref()) { + header_files.push(entry.file_name().into_string().unwrap()); + fs::move_file(&entry.path(), &headers_dir)?; + } + } -fn fix_swift_bindings(dir: &Path, ffi_module_name: &str) -> Result<()> { - let swift_files = dir.files_with_extension("swift")?; - let tempdir = tempdir()?; + let template = ModuleMapTemplate { + ffi_module_name: self.ffi_module_name.clone(), + header_files, + }; + let content = template.render()?; + let mut modulemap = File::create_new(headers_dir.join("module.modulemap"))?; + modulemap.write_all(content.as_bytes())?; - #[derive(Template)] - #[template(path = "binding-prefix.swift", escape = "none")] - struct PrefixTemplate { - ffi_module_name: String, + Ok(()) } - let prefix = PrefixTemplate { - ffi_module_name: ffi_module_name.to_string(), - } - .render()?; - for path in swift_files { - let reader = BufReader::new(File::open(&path)?); - let tempfile_path = tempdir.path().join("temp.swift"); - let mut tempfile = File::create(&tempfile_path)?; + fn fix_swift_bindings(&self, dir: &Path) -> Result<()> { + let swift_files = dir.files_with_extension("swift")?; + let tempdir = tempdir()?; - writeln!(tempfile, "{}\n", prefix)?; + #[derive(Template)] + #[template(path = "binding-prefix.swift", escape = "none")] + struct PrefixTemplate { + ffi_module_name: String, + } + let prefix = PrefixTemplate { + ffi_module_name: self.ffi_module_name.clone(), + } + .render()?; + + for path in swift_files { + let reader = BufReader::new(File::open(&path)?); + let tempfile_path = tempdir.path().join("temp.swift"); + let mut tempfile = File::create(&tempfile_path)?; + + writeln!(tempfile, "{}\n", prefix)?; + + for line in reader.lines() { + let mut line = line?; + if line == "protocol UniffiForeignFutureTask {" { + line = "fileprivate protocol UniffiForeignFutureTask {".to_string() + } - for line in reader.lines() { - let mut line = line?; - if line == "protocol UniffiForeignFutureTask {" { - line = "fileprivate protocol UniffiForeignFutureTask {".to_string() + writeln!(tempfile, "{}", line)?; } - writeln!(tempfile, "{}", line)?; - } + tempfile.sync_all()?; + std::mem::drop(tempfile); - tempfile.sync_all()?; - std::mem::drop(tempfile); + std::fs::rename(tempfile_path, path)? + } - std::fs::copy(tempfile_path, path)?; + Ok(()) } - - Ok(()) } diff --git a/src/cli.rs b/src/cli.rs index 0511130..3736c58 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,8 +5,9 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use crate::apple_platform::ApplePlatform; -use crate::build; -use crate::spm; +use crate::build::BuildExtensions; +use crate::project::Project; +use crate::spm::SPMResolver; #[derive(Parser)] pub(crate) struct Cli { @@ -22,8 +23,6 @@ enum Commands { #[derive(Parser)] struct BuildArgs { - #[arg(long)] - package: String, #[arg(long)] only_ios: bool, #[arg(long)] @@ -36,8 +35,6 @@ struct BuildArgs { #[derive(Parser)] struct GeneratePackageArgs { - #[arg(long)] - package: String, #[arg(long)] ffi_module_name: String, #[arg(long)] @@ -67,12 +64,8 @@ fn build(args: BuildArgs) -> Result<()> { vec![] }; - build::build( - args.package, - args.profile, - args.ffi_module_name, - apple_platforms, - ) + let project = Project::new(args.ffi_module_name)?; + project.build(args.profile, apple_platforms) } fn generate_package(args: GeneratePackageArgs) -> Result<()> { @@ -87,7 +80,9 @@ fn generate_package(args: GeneratePackageArgs) -> Result<()> { }) .collect::>(); - // spm::generate_swift_package(&args.package, map) - // spm::generate_swift_package(args.package, args.ffi_module_name, args.project_name, map) - spm::generate_swift_package2(args.ffi_module_name, args.project_name, map) + let resolver = SPMResolver { + project: Project::new(args.ffi_module_name)?, + cargo_package_to_spm_target_map: map, + }; + resolver.generate_swift_package(args.project_name) } diff --git a/src/lib.rs b/src/lib.rs index c0b363c..faa1d82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod apple_platform; mod build; mod cli; +mod project; mod spm; mod utils; mod xcframework; diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..129a05d --- /dev/null +++ b/src/project.rs @@ -0,0 +1,140 @@ +use anyhow::{Context, Result}; +use cargo_metadata::{camino::Utf8PathBuf, DependencyKind, Metadata, MetadataCommand, Package}; + +pub struct Project { + pub ffi_module_name: String, + pub cargo_metadata: Metadata, +} + +impl Project { + pub fn new(ffi_module_name: String) -> Result { + let cargo_metadata = MetadataCommand::new() + .exec() + .with_context(|| "Can't get cargo metadata")?; + + if cargo_metadata.workspace_root.as_std_path() != std::env::current_dir()? { + anyhow::bail!("The current directory is not the cargo root directory") + } + + Ok(Self { + ffi_module_name, + cargo_metadata, + }) + } + + pub fn uniffi_package(&self) -> Result { + let is_uniffi_package = |package: &Package| { + let depends_on_uniffi = package + .dependencies + .iter() + .any(|d| d.name == "uniffi" && !d.optional && d.kind == DependencyKind::Normal); + let has_uniffi_toml = package.manifest_path.with_file_name("uniffi.toml").exists(); + depends_on_uniffi && has_uniffi_toml + }; + let uniffi_packages = self + .cargo_metadata + .packages + .iter() + .filter(|p| is_uniffi_package(p)) + .collect::>(); + + for package in &uniffi_packages { + if !package.id.repr.starts_with("git+") && !package.id.repr.starts_with("path+") { + anyhow::bail!("Unsupported package id: {}. We can only find Swift source code when package is integrated as a git repo or a local path.", package.id.repr) + } + } + + let mut uniffi_packages = uniffi_packages + .iter() + .map(|p| UniffiPackage::new(p, &uniffi_packages)) + .collect::>(); + let top_level_packages = uniffi_packages + .iter() + .enumerate() + .filter(|p| { + !uniffi_packages + .iter() + .any(|other| other.depends_on(&p.1.name)) + }) + .collect::>(); + + if top_level_packages.len() != 1 { + anyhow::bail!( + "Expected 1 top-level package, found {:?}", + top_level_packages + .iter() + .map(|(_, p)| p.name.to_string()) + .collect::>() + ) + } + + let index = top_level_packages[0].0; + Ok(uniffi_packages.remove(index)) + } + + pub fn xcframework_path(&self) -> Utf8PathBuf { + self.cargo_metadata + .target_directory + .join(&self.ffi_module_name) + .join(format!("{}.xcframework", &self.ffi_module_name)) + } + + pub fn swift_wrapper_dir(&self) -> Utf8PathBuf { + self.cargo_metadata + .target_directory + .join(&self.ffi_module_name) + .join("swift-wrapper") + } +} + +#[derive(Debug)] +pub struct UniffiPackage { + pub name: String, + pub manifest_path: Utf8PathBuf, + pub dependencies: Vec, +} + +impl UniffiPackage { + pub fn new(package: &Package, all_uniffi_packages: &Vec<&Package>) -> Self { + let dependencies: Vec<_> = package + .dependencies + .iter() + .filter_map(|d| { + all_uniffi_packages + .iter() + .find(|p| p.name == d.name) + .map(|p| Self::new(p, all_uniffi_packages)) + }) + .collect(); + + UniffiPackage { + name: package.name.clone(), + manifest_path: package.manifest_path.clone(), + dependencies, + } + } + + pub fn depends_on(&self, other: &str) -> bool { + self.dependencies.iter().any(|d| d.name == other) + } + + pub fn swift_wrapper_file_name(&self) -> String { + format!("{}.swift", self.name) + } + + pub fn iter(&self) -> impl Iterator { + let mut result: Vec<&UniffiPackage> = vec![]; + + let mut queue: Vec<&UniffiPackage> = vec![]; + queue.push(self); + while let Some(package) = queue.pop() { + result.push(package); + + for dep in &package.dependencies { + queue.push(dep); + } + } + + result.into_iter() + } +} diff --git a/src/spm.rs b/src/spm.rs index e0b6d23..6da1efd 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -7,10 +7,10 @@ use std::{ }; use anyhow::{Context, Result}; -use cargo_metadata::{camino::Utf8PathBuf, DependencyKind, Metadata, MetadataCommand, Package}; -use pathdiff::diff_paths; +use cargo_metadata::{camino::Utf8PathBuf, MetadataCommand}; use rinja::Template; +use crate::project::*; use crate::utils::{fs, ExecuteCommand}; #[derive(Template)] @@ -50,18 +50,6 @@ where Ok(subdirs[0].path()) } -fn relative_path(path: P, base: B) -> String -where - P: AsRef, - B: AsRef, -{ - diff_paths(path, base) - .as_ref() - .and_then(|s| s.to_str()) - .map(|s| s.to_string()) - .unwrap() -} - struct InternalTarget { name: String, swift_wrapper_dir: String, @@ -69,146 +57,45 @@ struct InternalTarget { dependencies: Vec, } -#[derive(Debug)] -struct UniffiPackage { - name: String, - manifest_path: Utf8PathBuf, - dependencies: Vec, +pub struct SPMResolver { + pub project: Project, + pub cargo_package_to_spm_target_map: HashMap, } -pub fn generate_swift_package2( - ffi_module_name: String, - project_name: String, - packages: HashMap, -) -> Result<()> { - let metadata = MetadataCommand::new() - .exec() - .with_context(|| "Can't get cargo metadata")?; - - if metadata.workspace_root.as_std_path() != std::env::current_dir()? { - anyhow::bail!("The current directory is not the cargo root directory") - } - - let uniffi_packages = metadata - .packages - .iter() - .filter(|p| is_uniffi_package(p)) - .collect::>(); - for package in &uniffi_packages { - if !package.id.repr.starts_with("git+") && !package.id.repr.starts_with("path+") { - anyhow::bail!("Unsupported package id: {}. We can only find Swift source code when package is integrated as a git repo or a local path.", package.id.repr) - } - } - - let uniffi_packages = uniffi_packages - .iter() - .map(|p| UniffiPackage::new(p, &uniffi_packages)) - .collect::>(); - let top_level_package = uniffi_packages - .iter() - .find(|p| { - !uniffi_packages - .iter() - .any(|other| other.depends_on(&p.name)) - }) - .unwrap(); - - let resolver = SPMResolver { - metadata: ProjectMetadata { - ffi_module_name, - cargo_metadata: metadata.clone(), - }, - cargo_package_to_spm_target_map: packages, - }; - - let targets = uniffi_packages - .iter() - .map(|p| resolver.public_target(p)) - .collect::>>()?; - let internal_targets = uniffi_packages - .iter() - .map(|p| resolver.internal_target(p)) - .collect::>>()?; - - let template = PackageTemplate { - package_name: resolver.spm_target_name(&top_level_package.name), - ffi_module_name: resolver.metadata.ffi_module_name.clone(), - project_name, - targets, - internal_targets, - }; - let content = template.render()?; - let dest = resolver.swift_package_manifest_file_path(); - File::create(&dest)?.write_all(content.as_bytes())?; - - Command::new("swift") - .args(["format", "--in-place"]) - .arg(&dest) - .successful_output()?; - - Ok(()) -} - -fn is_uniffi_package(package: &Package) -> bool { - let depends_on_uniffi = package - .dependencies - .iter() - .any(|d| d.name == "uniffi" && !d.optional && d.kind == DependencyKind::Normal); - let has_uniffi_toml = package.manifest_path.with_file_name("uniffi.toml").exists(); - depends_on_uniffi && has_uniffi_toml -} +impl SPMResolver { + pub fn generate_swift_package(&self, project_name: String) -> Result<()> { + let top_level_package = self.project.uniffi_package()?; -impl UniffiPackage { - fn new(package: &Package, all_uniffi_packages: &Vec<&Package>) -> Self { - let dependencies: Vec<_> = package - .dependencies + let targets = top_level_package .iter() - .filter_map(|d| { - all_uniffi_packages - .iter() - .find(|p| p.name == d.name) - .map(|p| Self::new(p, all_uniffi_packages)) - }) - .collect(); - - UniffiPackage { - name: package.name.clone(), - manifest_path: package.manifest_path.clone(), - dependencies, - } - } - - fn depends_on(&self, other: &str) -> bool { - self.dependencies.iter().any(|d| d.name == other) - } - - fn swift_wrapper_file_name(&self) -> String { - format!("{}.swift", self.name) - } -} - -struct ProjectMetadata { - ffi_module_name: String, - cargo_metadata: Metadata, -} - -impl ProjectMetadata { - fn swift_wrapper_dir(&self) -> Utf8PathBuf { - self.cargo_metadata - .target_directory - .join(&self.ffi_module_name) - .join("swift-wrapper") + .map(|p| self.public_target(p)) + .collect::>>()?; + let internal_targets = top_level_package + .iter() + .map(|p| self.internal_target(p)) + .collect::>>()?; + + let template = PackageTemplate { + package_name: self.spm_target_name(&top_level_package.name), + ffi_module_name: self.project.ffi_module_name.clone(), + project_name, + targets, + internal_targets, + }; + let content = template.render()?; + let dest = self.swift_package_manifest_file_path(); + File::create(&dest)?.write_all(content.as_bytes())?; + + Command::new("swift") + .args(["format", "--in-place"]) + .arg(&dest) + .successful_output()?; + + Ok(()) } -} -struct SPMResolver { - metadata: ProjectMetadata, - cargo_package_to_spm_target_map: HashMap, -} - -impl SPMResolver { fn swift_package_manifest_file_path(&self) -> Utf8PathBuf { - self.metadata + self.project .cargo_metadata .workspace_root .join("Package.swift") @@ -235,7 +122,7 @@ impl SPMResolver { } fn internal_target(&self, package: &UniffiPackage) -> Result { - let swift_wrapper_dir = self.metadata.swift_wrapper_dir(); + let swift_wrapper_dir = self.project.swift_wrapper_dir(); let source_file_name = package.swift_wrapper_file_name(); let binding_file = swift_wrapper_dir.join(&source_file_name); if !binding_file.exists() { @@ -253,9 +140,9 @@ impl SPMResolver { Ok(InternalTarget { name: self.internal_target_name(package), - swift_wrapper_dir: relative_path( + swift_wrapper_dir: fs::relative_path( swift_wrapper_dir, - &self.metadata.cargo_metadata.workspace_root, + &self.project.cargo_metadata.workspace_root, ), source_file: source_file_name, dependencies, @@ -270,9 +157,9 @@ impl SPMResolver { let sources_dir = get_only_subdir(swift_code_dir.join("Sources"))?; let tests_dir = get_only_subdir(swift_code_dir.join("Tests"))?; - let root_dir = &self.metadata.cargo_metadata.workspace_root; - let library_source_path = relative_path(&sources_dir, root_dir); - let test_source_path = relative_path(&tests_dir, root_dir); + let root_dir = &self.project.cargo_metadata.workspace_root; + let library_source_path = fs::relative_path(&sources_dir, root_dir); + let test_source_path = fs::relative_path(&tests_dir, root_dir); let dependencies = package .dependencies @@ -290,7 +177,7 @@ impl SPMResolver { } fn vend_swift_source_code(&self, package: &UniffiPackage) -> Result { - let root_dir = &self.metadata.cargo_metadata.workspace_root; + let root_dir = &self.project.cargo_metadata.workspace_root; if !root_dir.is_absolute() { anyhow::bail!( "Cargo workspace root dir is not an absolute path: {}", diff --git a/src/utils.rs b/src/utils.rs index 363fabf..9b6ff70 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -33,7 +33,7 @@ pub(crate) trait FileSystemExtensions { fn files_with_extension(&self, ext: &str) -> Result>; } -impl FileSystemExtensions for Path { +impl FileSystemExtensions for T where T: AsRef { fn files_with_extension(&self, ext: &str) -> Result> { let files = std::fs::read_dir(self)? .filter_map(|f| f.ok()) @@ -50,7 +50,10 @@ pub(crate) mod fs { use super::*; - pub fn recreate_dir

(dir: P) -> Result<()> where P: AsRef { + pub fn recreate_dir

(dir: P) -> Result<()> + where + P: AsRef, + { let dir = dir.as_ref(); if dir.exists() { @@ -78,7 +81,10 @@ pub(crate) mod fs { Ok(destination) } - pub fn copy_dir

(src: P, dst: P) -> std::io::Result<()> where P: AsRef { + pub fn copy_dir

(src: P, dst: P) -> std::io::Result<()> + where + P: AsRef, + { let src = src.as_ref(); let dst = dst.as_ref(); @@ -100,4 +106,16 @@ pub(crate) mod fs { Ok(()) } + + pub fn relative_path(path: P, base: B) -> String + where + P: AsRef, + B: AsRef, + { + pathdiff::diff_paths(path, base) + .as_ref() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + .unwrap() + } } diff --git a/src/xcframework.rs b/src/xcframework.rs index 80fd094..db4adf7 100644 --- a/src/xcframework.rs +++ b/src/xcframework.rs @@ -12,21 +12,17 @@ pub fn create_xcframework( targets: Vec, profile: String, name: &str, + xcframework: &Path, + swift_wrapper: &Path, ) -> Result<()> { let temp_dir = cargo_target_dir.join("tmp/wp-rs-xcframework"); fs::recreate_dir(&temp_dir)?; - - let output = cargo_target_dir.join(name); - fs::recreate_dir(&output)?; - - let xcframework = output.join(format!("{}.xcframework", name,)); - let swift_wrapper = output.join("swift-wrapper"); XCFramework::new(&targets, &profile)?.create( cargo_target_dir, name, &temp_dir, - &xcframework, - &swift_wrapper, + xcframework, + swift_wrapper, )?; Ok(()) From 4de5fe16c0922cd7f8859514c5dbdc755cd1790c Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 18 Dec 2024 22:47:01 +1300 Subject: [PATCH 08/19] Set deployment targets --- src/apple_platform.rs | 18 +++++++++++++++++- src/build.rs | 29 +++++++++++++++-------------- src/spm.rs | 29 +++++++++++++++++++++++++++++ templates/Package.swift | 8 ++++---- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/apple_platform.rs b/src/apple_platform.rs index 99e23e8..a17c8cf 100644 --- a/src/apple_platform.rs +++ b/src/apple_platform.rs @@ -1,4 +1,6 @@ -use std::fmt::Display; +use std::{fmt::Display, process::Command}; + +use crate::spm::DeploymentTargets; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ApplePlatform { @@ -34,6 +36,20 @@ impl ApplePlatform { pub fn requires_nightly_toolchain(&self) -> bool { matches!(self, Self::TvOS | Self::WatchOS) } + + pub fn set_deployment_target_env(&self, command: &mut Command) { + let (key, value) = self.deployment_targets_env(); + command.env(key, value); + } + + fn deployment_targets_env(&self) -> (&'static str, &'static str) { + match self { + Self::IOS => ("IOS_DEPLOYMENT_TARGET", DeploymentTargets::ios()), + Self::MacOS => ("MACOSX_DEPLOYMENT_TARGET", DeploymentTargets::macos()), + Self::TvOS => ("TVOS_DEPLOYMENT_TARGET", DeploymentTargets::tvos()), + Self::WatchOS => ("WATCHOS_DEPLOYMENT_TARGET", DeploymentTargets::watchos()), + } + } } impl TryFrom<&str> for ApplePlatform { diff --git a/src/build.rs b/src/build.rs index 355fd3e..72aca20 100644 --- a/src/build.rs +++ b/src/build.rs @@ -100,23 +100,13 @@ impl Project { build.extend(["build", "--package", package, "--profile", profile]); let cargo_target_dir = &self.cargo_metadata.target_directory; - let targets = platform.as_ref().map_or(vec![], |p| p.target_triples()); - if targets.is_empty() { - let mut cmd = Command::new(build[0]); - cmd.args(&build[1..]); - - println!("$ {:?}", cmd); - if !cmd.spawn()?.wait()?.success() { - anyhow::bail!("Failed to build package {}", package) - } - - let target_dir = cargo_target_dir.join(profile_dirname); - Ok(vec![target_dir]) - } else { - targets + if let Some(platform) = platform { + platform + .target_triples() .into_iter() .map(|target| { let mut cmd = Command::new(build[0]); + platform.set_deployment_target_env(&mut cmd); cmd.args(&build[1..]); cmd.args(["--target", target]); @@ -130,6 +120,17 @@ impl Project { Ok(target_dir) }) .collect() + } else { + let mut cmd = Command::new(build[0]); + cmd.args(&build[1..]); + + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!("Failed to build package {}", package) + } + + let target_dir = cargo_target_dir.join(profile_dirname); + Ok(vec![target_dir]) } } diff --git a/src/spm.rs b/src/spm.rs index 6da1efd..073b7e4 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -13,6 +13,26 @@ use rinja::Template; use crate::project::*; use crate::utils::{fs, ExecuteCommand}; +pub struct DeploymentTargets; + +impl DeploymentTargets { + pub fn ios() -> &'static str { + "13.0" + } + + pub fn macos() -> &'static str { + "11.0" + } + + pub fn tvos() -> &'static str { + "13.0" + } + + pub fn watchos() -> &'static str { + "8.0" + } +} + #[derive(Template)] #[template(path = "Package.swift", escape = "none")] struct PackageTemplate { @@ -21,6 +41,11 @@ struct PackageTemplate { project_name: String, targets: Vec, internal_targets: Vec, + + ios_version: &'static str, + macos_version: &'static str, + tvos_version: &'static str, + watchos_version: &'static str, } struct Target { @@ -81,6 +106,10 @@ impl SPMResolver { project_name, targets, internal_targets, + ios_version: DeploymentTargets::ios(), + macos_version: DeploymentTargets::macos(), + tvos_version: DeploymentTargets::tvos(), + watchos_version: DeploymentTargets::watchos(), }; let content = template.render()?; let dest = self.swift_package_manifest_file_path(); diff --git a/templates/Package.swift b/templates/Package.swift index 3335bc0..6a5ad17 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -30,10 +30,10 @@ let ffiTarget: Target = ffiVersion.target var package = Package( name: packageName, platforms: [ - .iOS(.v13), - .macOS(.v11), - .tvOS(.v13), - .watchOS(.v8) + .iOS("{{ ios_version }}"), + .macOS("{{ macos_version }}"), + .tvOS("{{ tvos_version }}"), + .watchOS("{{ watchos_version }}"), ], products: [ .library( From ae0ec96b69604a2fa155397def7673ad9f301756 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 00:00:02 +1300 Subject: [PATCH 09/19] Parse ffi_module_name --- Cargo.lock | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 ++-- src/build.rs | 16 +++++----- src/cli.rs | 8 ++--- src/project.rs | 65 ++++++++++++++++++++++++++++++++--------- src/spm.rs | 6 ++-- 6 files changed, 144 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 349cd7e..f1b867f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.10" @@ -274,6 +280,12 @@ dependencies = [ "scroll", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" @@ -289,6 +301,16 @@ dependencies = [ "libm", ] +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -538,6 +560,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -644,6 +675,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicase" version = "2.8.0" @@ -680,6 +745,7 @@ dependencies = [ "rinja", "serde_json", "tempfile", + "toml 0.8.19", "uniffi", "uniffi_bindgen", ] @@ -702,7 +768,7 @@ dependencies = [ "paste", "serde", "textwrap", - "toml", + "toml 0.5.11", "uniffi_meta", "uniffi_udl", ] @@ -745,7 +811,7 @@ dependencies = [ "quote", "serde", "syn", - "toml", + "toml 0.5.11", "uniffi_meta", ] @@ -874,3 +940,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index c984a57..d8cd73e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ pathdiff = "0.2" rinja = "0.3" serde_json = "1.0" tempfile = "3.14" -uniffi = "0.28.3" -uniffi_bindgen = "0.28.3" +toml = { version = "0.8", features = ["parse"] } +uniffi = "0.28" +uniffi_bindgen = "0.28" diff --git a/src/build.rs b/src/build.rs index 72aca20..444b115 100644 --- a/src/build.rs +++ b/src/build.rs @@ -25,14 +25,14 @@ impl BuildExtensions for Project { ) } - let package = self.uniffi_package()?.name; + let package = &self.package.name; let target_dirs: Vec<_> = if apple_platforms.is_empty() { - self.build_uniffi_package(&package, &profile, None)? + self.build_uniffi_package(package, &profile, None)? } else { apple_platforms .iter() - .map(|platform| self.build_uniffi_package(&package, &profile, Some(*platform))) + .map(|platform| self.build_uniffi_package(package, &profile, Some(*platform))) .collect::>>()? .into_iter() .flatten() @@ -60,9 +60,9 @@ impl BuildExtensions for Project { .map(|s| s.to_string()) .collect(), profile.to_string(), - &self.ffi_module_name, - self.xcframework_path().as_std_path(), - self.swift_wrapper_dir().as_std_path(), + &self.ffi_module_name()?, + self.xcframework_path()?.as_std_path(), + self.swift_wrapper_dir()?.as_std_path(), ) } } @@ -178,7 +178,7 @@ impl Project { } let template = ModuleMapTemplate { - ffi_module_name: self.ffi_module_name.clone(), + ffi_module_name: self.ffi_module_name()?, header_files, }; let content = template.render()?; @@ -198,7 +198,7 @@ impl Project { ffi_module_name: String, } let prefix = PrefixTemplate { - ffi_module_name: self.ffi_module_name.clone(), + ffi_module_name: self.ffi_module_name()?, } .render()?; diff --git a/src/cli.rs b/src/cli.rs index 3736c58..3345ca1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -29,14 +29,10 @@ struct BuildArgs { only_macos: bool, #[arg(long)] profile: String, - #[arg(long)] - ffi_module_name: String, } #[derive(Parser)] struct GeneratePackageArgs { - #[arg(long)] - ffi_module_name: String, #[arg(long)] project_name: String, #[arg(long)] @@ -64,7 +60,7 @@ fn build(args: BuildArgs) -> Result<()> { vec![] }; - let project = Project::new(args.ffi_module_name)?; + let project = Project::new()?; project.build(args.profile, apple_platforms) } @@ -81,7 +77,7 @@ fn generate_package(args: GeneratePackageArgs) -> Result<()> { .collect::>(); let resolver = SPMResolver { - project: Project::new(args.ffi_module_name)?, + project: Project::new()?, cargo_package_to_spm_target_map: map, }; resolver.generate_swift_package(args.project_name) diff --git a/src/project.rs b/src/project.rs index 129a05d..8b1ff17 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,13 +1,16 @@ +use std::str::FromStr; + use anyhow::{Context, Result}; use cargo_metadata::{camino::Utf8PathBuf, DependencyKind, Metadata, MetadataCommand, Package}; +use toml::Table; pub struct Project { - pub ffi_module_name: String, + pub package: UniffiPackage, pub cargo_metadata: Metadata, } impl Project { - pub fn new(ffi_module_name: String) -> Result { + pub fn new() -> Result { let cargo_metadata = MetadataCommand::new() .exec() .with_context(|| "Can't get cargo metadata")?; @@ -17,12 +20,12 @@ impl Project { } Ok(Self { - ffi_module_name, + package: Self::uniffi_package(&cargo_metadata)?, cargo_metadata, }) } - pub fn uniffi_package(&self) -> Result { + fn uniffi_package(metadata: &Metadata) -> Result { let is_uniffi_package = |package: &Package| { let depends_on_uniffi = package .dependencies @@ -31,8 +34,7 @@ impl Project { let has_uniffi_toml = package.manifest_path.with_file_name("uniffi.toml").exists(); depends_on_uniffi && has_uniffi_toml }; - let uniffi_packages = self - .cargo_metadata + let uniffi_packages = metadata .packages .iter() .filter(|p| is_uniffi_package(p)) @@ -72,18 +74,25 @@ impl Project { Ok(uniffi_packages.remove(index)) } - pub fn xcframework_path(&self) -> Utf8PathBuf { - self.cargo_metadata + pub fn ffi_module_name(&self) -> Result { + self.package.ffi_module_name() + } + + pub fn xcframework_path(&self) -> Result { + let ffi_module_name = self.ffi_module_name()?; + Ok(self + .cargo_metadata .target_directory - .join(&self.ffi_module_name) - .join(format!("{}.xcframework", &self.ffi_module_name)) + .join(&ffi_module_name) + .join(format!("{}.xcframework", &ffi_module_name))) } - pub fn swift_wrapper_dir(&self) -> Utf8PathBuf { - self.cargo_metadata + pub fn swift_wrapper_dir(&self) -> Result { + Ok(self + .cargo_metadata .target_directory - .join(&self.ffi_module_name) - .join("swift-wrapper") + .join(self.ffi_module_name()?) + .join("swift-wrapper")) } } @@ -137,4 +146,32 @@ impl UniffiPackage { result.into_iter() } + + pub fn ffi_module_name(&self) -> Result { + self.uniffi_toml()? + .get("bindings") + .and_then(|t| t.get("swift")) + .and_then(|t| t.get("ffi_module_name")) + .and_then(|t| t.as_str()) + .map(|s| s.to_string()) + .context(format!( + "ffi_module_name not found in the uniffi.toml of package {}", + self.name + )) + } + + fn uniffi_toml(&self) -> Result { + let uniffi_toml_path = self.manifest_path.with_file_name("uniffi.toml"); + let content = std::fs::read(uniffi_toml_path) + .with_context(|| format!("Can't read the uniffi.toml of package {}", self.name))?; + let str = String::from_utf8(content).with_context(|| { + format!( + "The uniffi.toml of package {} is not uft-8 encoded", + self.name + ) + })?; + let table = Table::from_str(&str) + .with_context(|| format!("The uniffi.toml of package {} is invalid", self.name))?; + Ok(table) + } } diff --git a/src/spm.rs b/src/spm.rs index 073b7e4..ef2e915 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -89,7 +89,7 @@ pub struct SPMResolver { impl SPMResolver { pub fn generate_swift_package(&self, project_name: String) -> Result<()> { - let top_level_package = self.project.uniffi_package()?; + let top_level_package = &self.project.package; let targets = top_level_package .iter() @@ -102,7 +102,7 @@ impl SPMResolver { let template = PackageTemplate { package_name: self.spm_target_name(&top_level_package.name), - ffi_module_name: self.project.ffi_module_name.clone(), + ffi_module_name: self.project.ffi_module_name()?, project_name, targets, internal_targets, @@ -151,7 +151,7 @@ impl SPMResolver { } fn internal_target(&self, package: &UniffiPackage) -> Result { - let swift_wrapper_dir = self.project.swift_wrapper_dir(); + let swift_wrapper_dir = self.project.swift_wrapper_dir()?; let source_file_name = package.swift_wrapper_file_name(); let binding_file = swift_wrapper_dir.join(&source_file_name); if !binding_file.exists() { From d537beea26d2f9b9d40e2579ebaad9c6a2f9559d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 00:13:12 +1300 Subject: [PATCH 10/19] Introduce `wp_spm_public_module_name` uniffi.toml config --- src/cli.rs | 21 +++-------------- src/project.rs | 36 +++++++++++++++++++++-------- src/spm.rs | 62 ++++++++++++++++++++------------------------------ 3 files changed, 54 insertions(+), 65 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3345ca1..206db0f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::env; use anyhow::Result; @@ -7,7 +6,7 @@ use clap::{Parser, Subcommand}; use crate::apple_platform::ApplePlatform; use crate::build::BuildExtensions; use crate::project::Project; -use crate::spm::SPMResolver; +use crate::spm::*; #[derive(Parser)] pub(crate) struct Cli { @@ -65,20 +64,6 @@ fn build(args: BuildArgs) -> Result<()> { } fn generate_package(args: GeneratePackageArgs) -> Result<()> { - let map = args - .package_name_map - .split(',') - .map(|pair| { - let mut iter = pair.split(':'); - let key = iter.next().unwrap(); - let value = iter.next().unwrap(); - (key.to_string(), value.to_string()) - }) - .collect::>(); - - let resolver = SPMResolver { - project: Project::new()?, - cargo_package_to_spm_target_map: map, - }; - resolver.generate_swift_package(args.project_name) + let project = Project::new()?; + project.generate_swift_package(args.project_name) } diff --git a/src/project.rs b/src/project.rs index 8b1ff17..a1fe697 100644 --- a/src/project.rs +++ b/src/project.rs @@ -74,6 +74,14 @@ impl Project { Ok(uniffi_packages.remove(index)) } + pub fn packages_iter(&self) -> impl Iterator { + self.package.iter() + } + + pub fn package(&self, name: &str) -> Option<&UniffiPackage> { + self.packages_iter().find(|p| p.name == name) + } + pub fn ffi_module_name(&self) -> Result { self.package.ffi_module_name() } @@ -148,16 +156,11 @@ impl UniffiPackage { } pub fn ffi_module_name(&self) -> Result { - self.uniffi_toml()? - .get("bindings") - .and_then(|t| t.get("swift")) - .and_then(|t| t.get("ffi_module_name")) - .and_then(|t| t.as_str()) - .map(|s| s.to_string()) - .context(format!( - "ffi_module_name not found in the uniffi.toml of package {}", - self.name - )) + self.uniffi_toml_swift_configuration("ffi_module_name") + } + + pub fn public_module_name(&self) -> Result { + self.uniffi_toml_swift_configuration("wp_spm_public_module_name") } fn uniffi_toml(&self) -> Result
{ @@ -174,4 +177,17 @@ impl UniffiPackage { .with_context(|| format!("The uniffi.toml of package {} is invalid", self.name))?; Ok(table) } + + fn uniffi_toml_swift_configuration(&self, key: &str) -> Result { + self.uniffi_toml()? + .get("bindings") + .and_then(|t| t.get("swift")) + .and_then(|t| t.get(key)) + .and_then(|t| t.as_str()) + .map(|s| s.to_string()) + .context(format!( + "{} not found in the uniffi.toml of package {}", + key, self.name + )) + } } diff --git a/src/spm.rs b/src/spm.rs index ef2e915..8bde6d5 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashMap, fs::File, io::Write, path::{Path, PathBuf}, @@ -82,14 +81,13 @@ struct InternalTarget { dependencies: Vec, } -pub struct SPMResolver { - pub project: Project, - pub cargo_package_to_spm_target_map: HashMap, +pub trait SPMExtension { + fn generate_swift_package(&self, project_name: String) -> Result<()>; } -impl SPMResolver { - pub fn generate_swift_package(&self, project_name: String) -> Result<()> { - let top_level_package = &self.project.package; +impl SPMExtension for Project { + fn generate_swift_package(&self, project_name: String) -> Result<()> { + let top_level_package = &self.package; let targets = top_level_package .iter() @@ -101,8 +99,8 @@ impl SPMResolver { .collect::>>()?; let template = PackageTemplate { - package_name: self.spm_target_name(&top_level_package.name), - ffi_module_name: self.project.ffi_module_name()?, + package_name: top_level_package.public_module_name()?, + ffi_module_name: self.ffi_module_name()?, project_name, targets, internal_targets, @@ -122,36 +120,26 @@ impl SPMResolver { Ok(()) } +} +impl Project { fn swift_package_manifest_file_path(&self) -> Utf8PathBuf { - self.project - .cargo_metadata - .workspace_root - .join("Package.swift") - } - - fn spm_target_name(&self, cargo_package_name: &str) -> String { - self.cargo_package_to_spm_target_map - .get(cargo_package_name) - .unwrap_or_else(|| { - panic!( - "No SPM target name specified for cargo package {}", - cargo_package_name - ) - }) - .to_string() + self.cargo_metadata.workspace_root.join("Package.swift") } - fn public_target_name(&self, package: &UniffiPackage) -> String { - self.spm_target_name(&package.name) + fn spm_target_name(&self, cargo_package_name: &str) -> Result { + self.package(cargo_package_name) + .context(format!("Can't find package {}", cargo_package_name))? + .public_module_name() } - fn internal_target_name(&self, package: &UniffiPackage) -> String { - format!("{}Internal", self.spm_target_name(&package.name)) + fn internal_target_name(&self, package: &UniffiPackage) -> Result { + let name = package.public_module_name()?; + Ok(format!("{}Internal", name)) } fn internal_target(&self, package: &UniffiPackage) -> Result { - let swift_wrapper_dir = self.project.swift_wrapper_dir()?; + let swift_wrapper_dir = self.swift_wrapper_dir()?; let source_file_name = package.swift_wrapper_file_name(); let binding_file = swift_wrapper_dir.join(&source_file_name); if !binding_file.exists() { @@ -165,13 +153,13 @@ impl SPMResolver { .dependencies .iter() .map(|p| self.internal_target_name(p)) - .collect::>(); + .collect::>>()?; Ok(InternalTarget { - name: self.internal_target_name(package), + name: self.internal_target_name(package)?, swift_wrapper_dir: fs::relative_path( swift_wrapper_dir, - &self.project.cargo_metadata.workspace_root, + &self.cargo_metadata.workspace_root, ), source_file: source_file_name, dependencies, @@ -186,7 +174,7 @@ impl SPMResolver { let sources_dir = get_only_subdir(swift_code_dir.join("Sources"))?; let tests_dir = get_only_subdir(swift_code_dir.join("Tests"))?; - let root_dir = &self.project.cargo_metadata.workspace_root; + let root_dir = &self.cargo_metadata.workspace_root; let library_source_path = fs::relative_path(&sources_dir, root_dir); let test_source_path = fs::relative_path(&tests_dir, root_dir); @@ -194,10 +182,10 @@ impl SPMResolver { .dependencies .iter() .map(|p| self.spm_target_name(&p.name)) - .collect(); + .collect::>>()?; Ok(Target { - name: self.public_target_name(package), + name: package.public_module_name()?, library_source_path, test_source_path, dependencies, @@ -206,7 +194,7 @@ impl SPMResolver { } fn vend_swift_source_code(&self, package: &UniffiPackage) -> Result { - let root_dir = &self.project.cargo_metadata.workspace_root; + let root_dir = &self.cargo_metadata.workspace_root; if !root_dir.is_absolute() { anyhow::bail!( "Cargo workspace root dir is not an absolute path: {}", From aa9949ac4602cec03df60176f65cf9ce0801b23d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 10:27:19 +1300 Subject: [PATCH 11/19] Add additional imports to Swift binding files --- Cargo.lock | 67 ----------------------- Cargo.toml | 1 - src/build.rs | 98 +++++++++++++++++++++------------- src/cli.rs | 4 +- src/project.rs | 19 +++++++ src/spm.rs | 9 +--- src/xcframework.rs | 2 + templates/binding-prefix.swift | 11 ++-- 8 files changed, 93 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1b867f..1ba2acd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,12 +122,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "bytes" version = "1.9.0" @@ -180,12 +174,6 @@ dependencies = [ "thiserror 2.0.7", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "clap" version = "4.5.23" @@ -238,22 +226,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "fs-err" version = "2.11.0" @@ -323,24 +295,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" -[[package]] -name = "libc" -version = "0.2.168" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" - [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "log" version = "0.4.22" @@ -480,19 +440,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" -[[package]] -name = "rustix" -version = "0.38.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "ryu" version = "1.0.18" @@ -604,19 +551,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys", -] - [[package]] name = "textwrap" version = "0.16.1" @@ -744,7 +678,6 @@ dependencies = [ "pathdiff", "rinja", "serde_json", - "tempfile", "toml 0.8.19", "uniffi", "uniffi_bindgen", diff --git a/Cargo.toml b/Cargo.toml index d8cd73e..f4ad362 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ clap = { version = "4.5", features = ["derive"] } pathdiff = "0.2" rinja = "0.3" serde_json = "1.0" -tempfile = "3.14" toml = { version = "0.8", features = ["parse"] } uniffi = "0.28" uniffi_bindgen = "0.28" diff --git a/src/build.rs b/src/build.rs index 444b115..d325960 100644 --- a/src/build.rs +++ b/src/build.rs @@ -6,9 +6,9 @@ use std::process::Command; use anyhow::Result; use cargo_metadata::camino::Utf8PathBuf; use rinja::Template; -use tempfile::tempdir; use uniffi_bindgen::bindings::SwiftBindingsOptions; +use crate::project::UniffiPackage; use crate::utils::*; use crate::{apple_platform::ApplePlatform, project::Project}; @@ -63,8 +63,12 @@ impl BuildExtensions for Project { &self.ffi_module_name()?, self.xcframework_path()?.as_std_path(), self.swift_wrapper_dir()?.as_std_path(), - ) + )?; } + + self.update_swift_wrappers()?; + + Ok(()) } } @@ -94,8 +98,8 @@ impl Project { // Include debug symbols. let config_debug = format!("profile.{}.debug=true", profile); // Abort on panic to include Rust backtrace in crash reports. - let panic_config = format!(r#"profile.{}.panic="abort""#, profile); - build.extend(["--config", &config_debug, "--config", &panic_config]); + let config_panic = format!(r#"profile.{}.panic="abort""#, profile); + build.extend(["--config", &config_debug, "--config", &config_panic]); build.extend(["build", "--package", package, "--profile", profile]); @@ -115,9 +119,7 @@ impl Project { anyhow::bail!("Failed to build package {} for target {}", package, target) } - let target_dir = cargo_target_dir.join(target).join(profile_dirname); - - Ok(target_dir) + Ok(cargo_target_dir.join(target).join(profile_dirname)) }) .collect() } else { @@ -129,8 +131,7 @@ impl Project { anyhow::bail!("Failed to build package {}", package) } - let target_dir = cargo_target_dir.join(profile_dirname); - Ok(vec![target_dir]) + Ok(vec![cargo_target_dir.join(profile_dirname)]) } } @@ -152,7 +153,6 @@ impl Project { uniffi_bindgen::bindings::generate_swift_bindings(options)?; self.reorganize_binding_files(&out_dir)?; - self.fix_swift_bindings(&out_dir)?; Ok(out_dir) } @@ -188,42 +188,68 @@ impl Project { Ok(()) } - fn fix_swift_bindings(&self, dir: &Path) -> Result<()> { - let swift_files = dir.files_with_extension("swift")?; - let tempdir = tempdir()?; - - #[derive(Template)] - #[template(path = "binding-prefix.swift", escape = "none")] - struct PrefixTemplate { - ffi_module_name: String, + fn update_swift_wrappers(&self) -> Result<()> { + for item in self.swift_wrapper_files_iter() { + let (path, package) = item?; + self.update_swift_wrapper(path, package)?; } - let prefix = PrefixTemplate { - ffi_module_name: self.ffi_module_name()?, + + Ok(()) + } + + fn update_swift_wrapper(&self, path: Utf8PathBuf, package: &UniffiPackage) -> Result<()> { + let tempdir = self.cargo_metadata.target_directory.join("tmp"); + if !tempdir.exists() { + std::fs::create_dir(&tempdir)?; } - .render()?; - for path in swift_files { - let reader = BufReader::new(File::open(&path)?); - let tempfile_path = tempdir.path().join("temp.swift"); - let mut tempfile = File::create(&tempfile_path)?; + let tempfile_path = tempdir.join("temp.swift"); + if tempfile_path.exists() { + std::fs::remove_file(&tempfile_path)?; + } - writeln!(tempfile, "{}\n", prefix)?; + let mut tempfile = File::create_new(&tempfile_path)?; - for line in reader.lines() { - let mut line = line?; - if line == "protocol UniffiForeignFutureTask {" { - line = "fileprivate protocol UniffiForeignFutureTask {".to_string() - } + let content = self.swift_wrapper_prefix(package)?; + writeln!(tempfile, "{}\n", content)?; - writeln!(tempfile, "{}", line)?; + let original = BufReader::new(File::open(&path)?); + for line in original.lines() { + let mut line = line?; + if line == "protocol UniffiForeignFutureTask {" { + line = "fileprivate protocol UniffiForeignFutureTask {".to_string() } - tempfile.sync_all()?; - std::mem::drop(tempfile); - - std::fs::rename(tempfile_path, path)? + writeln!(tempfile, "{}", line)?; } + tempfile.sync_all()?; + std::mem::drop(tempfile); + + std::fs::rename(tempfile_path, path)?; + Ok(()) } + + fn swift_wrapper_prefix(&self, package: &UniffiPackage) -> Result { + let mut modules_to_import: Vec = vec![]; + + package + .iter() + .filter(|p| p.name != package.name) + .for_each(|p| modules_to_import.push(p.internal_module_name().unwrap())); + + let project_ffi_module_name = self.ffi_module_name()?; + if package.ffi_module_name()? != project_ffi_module_name { + modules_to_import.push(project_ffi_module_name); + } + + Ok(PrefixTemplate { modules_to_import }.render()?) + } +} + +#[derive(Template)] +#[template(path = "binding-prefix.swift", escape = "none")] +struct PrefixTemplate { + modules_to_import: Vec, } diff --git a/src/cli.rs b/src/cli.rs index 206db0f..086393e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,7 +4,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use crate::apple_platform::ApplePlatform; -use crate::build::BuildExtensions; +use crate::build::*; use crate::project::Project; use crate::spm::*; @@ -34,8 +34,6 @@ struct BuildArgs { struct GeneratePackageArgs { #[arg(long)] project_name: String, - #[arg(long)] - package_name_map: String, } impl Cli { diff --git a/src/project.rs b/src/project.rs index a1fe697..03903f2 100644 --- a/src/project.rs +++ b/src/project.rs @@ -102,6 +102,21 @@ impl Project { .join(self.ffi_module_name()?) .join("swift-wrapper")) } + + pub fn swift_wrapper_files_iter( + &self, + ) -> impl Iterator> { + self.packages_iter() + .map(|pkg| { + let file_name = format!("{}.swift", pkg.name); + let path = self.swift_wrapper_dir()?.join(file_name); + if path.exists() { + Ok((path, pkg)) + } else { + anyhow::bail!("Swift wrapper file {} not found. Please run the build command first", path); + } + }) + } } #[derive(Debug)] @@ -163,6 +178,10 @@ impl UniffiPackage { self.uniffi_toml_swift_configuration("wp_spm_public_module_name") } + pub fn internal_module_name(&self) -> Result { + Ok(format!("{}Internal", self.public_module_name()?)) + } + fn uniffi_toml(&self) -> Result
{ let uniffi_toml_path = self.manifest_path.with_file_name("uniffi.toml"); let content = std::fs::read(uniffi_toml_path) diff --git a/src/spm.rs b/src/spm.rs index 8bde6d5..fe2c915 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -133,11 +133,6 @@ impl Project { .public_module_name() } - fn internal_target_name(&self, package: &UniffiPackage) -> Result { - let name = package.public_module_name()?; - Ok(format!("{}Internal", name)) - } - fn internal_target(&self, package: &UniffiPackage) -> Result { let swift_wrapper_dir = self.swift_wrapper_dir()?; let source_file_name = package.swift_wrapper_file_name(); @@ -152,11 +147,11 @@ impl Project { let dependencies = package .dependencies .iter() - .map(|p| self.internal_target_name(p)) + .map(|p| p.internal_module_name()) .collect::>>()?; Ok(InternalTarget { - name: self.internal_target_name(package)?, + name: package.internal_module_name()?, swift_wrapper_dir: fs::relative_path( swift_wrapper_dir, &self.cargo_metadata.workspace_root, diff --git a/src/xcframework.rs b/src/xcframework.rs index db4adf7..34b6cfc 100644 --- a/src/xcframework.rs +++ b/src/xcframework.rs @@ -25,6 +25,8 @@ pub fn create_xcframework( swift_wrapper, )?; + std::fs::remove_dir_all(&temp_dir).ok(); + Ok(()) } diff --git a/templates/binding-prefix.swift b/templates/binding-prefix.swift index 94ba637..d4fbaa4 100644 --- a/templates/binding-prefix.swift +++ b/templates/binding-prefix.swift @@ -1,5 +1,8 @@ -// This is a harmless import statement. It is used to ensure the FFI xcframework -// module is imported, when multiple uniffi packages are built into one xcframework. -#if canImport({{ ffi_module_name }}) -import {{ ffi_module_name }}; +// swiftlint:disable all +{%- for module in modules_to_import %} +#if canImport({{ module }}) +import {{ module }}; #endif +{%- endfor %} + +// ==== Below are the original content generated by uniffi-rs ==== From dea8a44077fab77d4f2857f98adb1fb92e8cda12 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 11:22:05 +1300 Subject: [PATCH 12/19] Support build and test on linux --- src/build.rs | 19 +++++++++++++++++-- src/project.rs | 9 +++++++++ templates/Package.swift | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/build.rs b/src/build.rs index d325960..5517c46 100644 --- a/src/build.rs +++ b/src/build.rs @@ -39,7 +39,7 @@ impl BuildExtensions for Project { .collect() }; - for target_dir in target_dirs { + for target_dir in &target_dirs { let libraries = target_dir.files_with_extension("a")?; if libraries.len() != 1 { anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) @@ -50,7 +50,22 @@ impl BuildExtensions for Project { if apple_platforms.is_empty() { // TODO: Linux - unimplemented!("Not implemented for Linux yet") + let mut static_lib = target_dirs[0].files_with_extension("a")?; + if static_lib.len() != 1 { + anyhow::bail!("Expected 1 static library, found {:?}", static_lib) + } + let static_lib = static_lib.pop().unwrap(); + + let headers_dir = target_dirs[0].join("swift-bindings/Headers"); + if !headers_dir.exists() { + anyhow::bail!("Headers directory not found: {}", &headers_dir) + } + + let linux_library_dir = self.linux_library_path()?; + fs::copy_dir(&headers_dir, &linux_library_dir)?; + + let static_lib_dest = linux_library_dir.join(format!("{}.a", self.ffi_module_name()?)); + std::fs::copy(&static_lib, &static_lib_dest)?; } else { crate::xcframework::create_xcframework( self.cargo_metadata.target_directory.as_std_path(), diff --git a/src/project.rs b/src/project.rs index 03903f2..fc2b87d 100644 --- a/src/project.rs +++ b/src/project.rs @@ -86,6 +86,15 @@ impl Project { self.package.ffi_module_name() } + pub fn linux_library_path(&self) -> Result { + let ffi_module_name = self.ffi_module_name()?; + Ok(self + .cargo_metadata + .target_directory + .join(&ffi_module_name) + .join("linux")) + } + pub fn xcframework_path(&self) -> Result { let ffi_module_name = self.ffi_module_name()?; Ok(self diff --git a/templates/Package.swift b/templates/Package.swift index 6a5ad17..1b8298a 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -15,13 +15,14 @@ let testTargetName = "\(packageName)Tests" // Source code paths let ffiSwiftWrapperSourcePath = "target/\(ffiModuleName)/swift-wrapper" let ffiXCFrameworkPath = "target/\(ffiModuleName)/\(ffiModuleName).xcframework" +let ffiLinuxLibraryPath = "target/\(ffiModuleName)/linux" let ffiVersion: FFIVersion = .local #if os(Linux) let ffiTarget: Target = .systemLibrary( name: ffiModuleName, - path: "target/release/\(ffiModuleName)-linux/" + path: ffiLinuxLibraryPath ) #elseif os(macOS) let ffiTarget: Target = ffiVersion.target From 8756f878fd9ca8ba0fc71a1c6f11559b8a8b5df7 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 13:47:52 +1300 Subject: [PATCH 13/19] Refactor build uniffi package --- src/build.rs | 373 +++++++++++++++++++++++++++------------------ src/cli.rs | 2 +- src/xcframework.rs | 18 +-- 3 files changed, 228 insertions(+), 165 deletions(-) diff --git a/src/build.rs b/src/build.rs index 5517c46..b55c42d 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,6 +1,6 @@ use std::fs::File; use std::io::{BufRead, BufReader, Write}; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::Command; use anyhow::Result; @@ -12,60 +12,47 @@ use crate::project::UniffiPackage; use crate::utils::*; use crate::{apple_platform::ApplePlatform, project::Project}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CargoProfile { + Dev, + Release, +} + pub trait BuildExtensions { - fn build(&self, profile: String, apple_platforms: Vec) -> Result<()>; + fn build(&self, profile: CargoProfile, apple_platforms: Vec) -> Result<()>; } impl BuildExtensions for Project { - fn build(&self, profile: String, apple_platforms: Vec) -> Result<()> { - if profile != "release" && profile != "dev" { - anyhow::bail!( - "Profile must be either 'release' or 'dev', found {}", - profile - ) - } - + fn build(&self, profile: CargoProfile, apple_platforms: Vec) -> Result<()> { let package = &self.package.name; - let target_dirs: Vec<_> = if apple_platforms.is_empty() { - self.build_uniffi_package(package, &profile, None)? + let targets = if apple_platforms.is_empty() { + vec![PlatformTarget { + package: package.clone(), + profile, + platform: None, + }] } else { apple_platforms .iter() - .map(|platform| self.build_uniffi_package(package, &profile, Some(*platform))) - .collect::>>()? - .into_iter() - .flatten() + .map(|platform| PlatformTarget { + package: package.clone(), + profile, + platform: Some(*platform), + }) .collect() }; - - for target_dir in &target_dirs { - let libraries = target_dir.files_with_extension("a")?; - if libraries.len() != 1 { - anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) - } - - self.generate_bindings(&libraries[0])?; + for target in &targets { + target.build_uniffi_package()?; + target.generate_bindings( + &self.cargo_metadata.target_directory, + self.ffi_module_name()?, + )?; } if apple_platforms.is_empty() { - // TODO: Linux - let mut static_lib = target_dirs[0].files_with_extension("a")?; - if static_lib.len() != 1 { - anyhow::bail!("Expected 1 static library, found {:?}", static_lib) - } - let static_lib = static_lib.pop().unwrap(); - - let headers_dir = target_dirs[0].join("swift-bindings/Headers"); - if !headers_dir.exists() { - anyhow::bail!("Headers directory not found: {}", &headers_dir) - } - - let linux_library_dir = self.linux_library_path()?; - fs::copy_dir(&headers_dir, &linux_library_dir)?; - - let static_lib_dest = linux_library_dir.join(format!("{}.a", self.ffi_module_name()?)); - std::fs::copy(&static_lib, &static_lib_dest)?; + let target_dir = &targets[0].built_dirs(&self.cargo_metadata.target_directory)[0]; + self.create_linux_library(target_dir)?; } else { crate::xcframework::create_xcframework( self.cargo_metadata.target_directory.as_std_path(), @@ -74,7 +61,7 @@ impl BuildExtensions for Project { .flat_map(|p| p.target_triples()) .map(|s| s.to_string()) .collect(), - profile.to_string(), + profile, &self.ffi_module_name()?, self.xcframework_path()?.as_std_path(), self.swift_wrapper_dir()?.as_std_path(), @@ -88,21 +75,99 @@ impl BuildExtensions for Project { } impl Project { - fn build_uniffi_package( - &self, - package: &str, - profile: &str, - platform: Option, - ) -> Result> { - let profile_dirname = match profile { - "release" => "release", - "dev" => "debug", - _ => anyhow::bail!("Invalid profile: {}", profile), - }; + fn update_swift_wrappers(&self) -> Result<()> { + for item in self.swift_wrapper_files_iter() { + let (path, package) = item?; + self.update_swift_wrapper(path, package)?; + } + + Ok(()) + } + + fn update_swift_wrapper(&self, path: Utf8PathBuf, package: &UniffiPackage) -> Result<()> { + let tempdir = self.cargo_metadata.target_directory.join("tmp"); + if !tempdir.exists() { + std::fs::create_dir(&tempdir)?; + } + let tempfile_path = tempdir.join("temp.swift"); + if tempfile_path.exists() { + std::fs::remove_file(&tempfile_path)?; + } + + let mut tempfile = File::create_new(&tempfile_path)?; + + let content = self.swift_wrapper_prefix(package)?; + writeln!(tempfile, "{}\n", content)?; + + let original = BufReader::new(File::open(&path)?); + for line in original.lines() { + let mut line = line?; + if line == "protocol UniffiForeignFutureTask {" { + line = "fileprivate protocol UniffiForeignFutureTask {".to_string() + } + + writeln!(tempfile, "{}", line)?; + } + + tempfile.sync_all()?; + std::mem::drop(tempfile); + + std::fs::rename(tempfile_path, path)?; + + Ok(()) + } + + fn swift_wrapper_prefix(&self, package: &UniffiPackage) -> Result { + let mut modules_to_import: Vec = vec![]; + + package + .iter() + .filter(|p| p.name != package.name) + .for_each(|p| modules_to_import.push(p.internal_module_name().unwrap())); + + let project_ffi_module_name = self.ffi_module_name()?; + if package.ffi_module_name()? != project_ffi_module_name { + modules_to_import.push(project_ffi_module_name); + } + + Ok(PrefixTemplate { modules_to_import }.render()?) + } + + fn create_linux_library(&self, target_dir: &Utf8PathBuf) -> Result<()> { + let mut static_lib = target_dir.files_with_extension("a")?; + if static_lib.len() != 1 { + anyhow::bail!("Expected 1 static library, found {:?}", static_lib) + } + let static_lib = static_lib.pop().unwrap(); + + let headers_dir = target_dir.join("swift-bindings/Headers"); + if !headers_dir.exists() { + anyhow::bail!("Headers directory not found: {}", &headers_dir) + } + + let linux_library_dir = self.linux_library_path()?; + fs::copy_dir(&headers_dir, &linux_library_dir)?; + + let static_lib_dest = linux_library_dir.join(format!("{}.a", self.ffi_module_name()?)); + std::fs::copy(&static_lib, &static_lib_dest)?; + + Ok(()) + } +} + +struct PlatformTarget { + package: String, + profile: CargoProfile, + platform: Option, +} + +impl PlatformTarget { + fn build_uniffi_package(&self) -> Result<()> { let mut build = vec!["cargo"]; - if platform + if self + .platform .as_ref() .map_or(false, |p| p.requires_nightly_toolchain()) { @@ -111,68 +176,95 @@ impl Project { } // Include debug symbols. - let config_debug = format!("profile.{}.debug=true", profile); + let config_debug = format!("profile.{}.debug=true", self.profile.as_str()); // Abort on panic to include Rust backtrace in crash reports. - let config_panic = format!(r#"profile.{}.panic="abort""#, profile); + let config_panic = format!(r#"profile.{}.panic="abort""#, self.profile.as_str()); build.extend(["--config", &config_debug, "--config", &config_panic]); - build.extend(["build", "--package", package, "--profile", profile]); - - let cargo_target_dir = &self.cargo_metadata.target_directory; - if let Some(platform) = platform { - platform - .target_triples() - .into_iter() - .map(|target| { - let mut cmd = Command::new(build[0]); - platform.set_deployment_target_env(&mut cmd); - cmd.args(&build[1..]); - cmd.args(["--target", target]); - - println!("$ {:?}", cmd); - if !cmd.spawn()?.wait()?.success() { - anyhow::bail!("Failed to build package {} for target {}", package, target) - } - - Ok(cargo_target_dir.join(target).join(profile_dirname)) - }) - .collect() + build.extend([ + "build", + "--package", + self.package.as_str(), + "--profile", + self.profile.as_str(), + ]); + + if let Some(platform) = self.platform { + for target_triple in platform.target_triples() { + let mut cmd = Command::new(build[0]); + platform.set_deployment_target_env(&mut cmd); + cmd.args(&build[1..]); + cmd.args(["--target", target_triple]); + + println!("$ {:?}", cmd); + if !cmd.spawn()?.wait()?.success() { + anyhow::bail!( + "Failed to build package {} for target {}", + self.package, + target_triple + ) + } + } } else { let mut cmd = Command::new(build[0]); cmd.args(&build[1..]); println!("$ {:?}", cmd); if !cmd.spawn()?.wait()?.success() { - anyhow::bail!("Failed to build package {}", package) + anyhow::bail!("Failed to build package {}", self.package) } + } + + Ok(()) + } - Ok(vec![cargo_target_dir.join(profile_dirname)]) + fn built_dirs(&self, cargo_target_dir: &Utf8PathBuf) -> Vec { + if let Some(platform) = self.platform { + platform + .target_triples() + .into_iter() + .map(|target| cargo_target_dir.join(target).join(self.profile.dir_name())) + .collect() + } else { + vec![cargo_target_dir.join(self.profile.as_str())] } } - fn generate_bindings(&self, library_path: &Path) -> Result { - let out_dir = library_path.parent().unwrap().join("swift-bindings"); - fs::recreate_dir(&out_dir)?; - - let options = SwiftBindingsOptions { - generate_swift_sources: true, - generate_headers: true, - generate_modulemap: false, - library_path: library_path.to_path_buf().try_into()?, - out_dir: out_dir.clone().try_into()?, - xcframework: false, - module_name: None, - modulemap_filename: None, - metadata_no_deps: false, - }; - uniffi_bindgen::bindings::generate_swift_bindings(options)?; + fn generate_bindings( + &self, + cargo_target_dir: &Utf8PathBuf, + ffi_module_name: String, + ) -> Result<()> { + for target_dir in self.built_dirs(cargo_target_dir) { + let libraries = target_dir.files_with_extension("a")?; + if libraries.len() != 1 { + anyhow::bail!("Expected 1 library in target dir, found {:?}", libraries) + } - self.reorganize_binding_files(&out_dir)?; + let library_path = &libraries[0]; + let out_dir = library_path.parent().unwrap().join("swift-bindings"); + fs::recreate_dir(&out_dir)?; + + let options = SwiftBindingsOptions { + generate_swift_sources: true, + generate_headers: true, + generate_modulemap: false, + library_path: library_path.to_path_buf().try_into()?, + out_dir: out_dir.clone().try_into()?, + xcframework: false, + module_name: None, + modulemap_filename: None, + metadata_no_deps: false, + }; + uniffi_bindgen::bindings::generate_swift_bindings(options)?; + + self.reorganize_binding_files(&out_dir, ffi_module_name.clone())?; + } - Ok(out_dir) + Ok(()) } - fn reorganize_binding_files(&self, bindings_dir: &Path) -> Result<()> { + fn reorganize_binding_files(&self, bindings_dir: &Path, ffi_module_name: String) -> Result<()> { #[derive(Template)] #[template(path = "module.modulemap", escape = "none")] struct ModuleMapTemplate { @@ -193,7 +285,7 @@ impl Project { } let template = ModuleMapTemplate { - ffi_module_name: self.ffi_module_name()?, + ffi_module_name, header_files, }; let content = template.render()?; @@ -202,69 +294,46 @@ impl Project { Ok(()) } +} - fn update_swift_wrappers(&self) -> Result<()> { - for item in self.swift_wrapper_files_iter() { - let (path, package) = item?; - self.update_swift_wrapper(path, package)?; - } - - Ok(()) - } - - fn update_swift_wrapper(&self, path: Utf8PathBuf, package: &UniffiPackage) -> Result<()> { - let tempdir = self.cargo_metadata.target_directory.join("tmp"); - if !tempdir.exists() { - std::fs::create_dir(&tempdir)?; - } +#[derive(Template)] +#[template(path = "binding-prefix.swift", escape = "none")] +struct PrefixTemplate { + modules_to_import: Vec, +} - let tempfile_path = tempdir.join("temp.swift"); - if tempfile_path.exists() { - std::fs::remove_file(&tempfile_path)?; +impl CargoProfile { + pub fn as_str(&self) -> &str { + match self { + CargoProfile::Dev => "dev", + CargoProfile::Release => "release", } + } - let mut tempfile = File::create_new(&tempfile_path)?; - - let content = self.swift_wrapper_prefix(package)?; - writeln!(tempfile, "{}\n", content)?; - - let original = BufReader::new(File::open(&path)?); - for line in original.lines() { - let mut line = line?; - if line == "protocol UniffiForeignFutureTask {" { - line = "fileprivate protocol UniffiForeignFutureTask {".to_string() - } - - writeln!(tempfile, "{}", line)?; + pub fn dir_name(&self) -> &str { + match self { + CargoProfile::Dev => "debug", + CargoProfile::Release => "release", } - - tempfile.sync_all()?; - std::mem::drop(tempfile); - - std::fs::rename(tempfile_path, path)?; - - Ok(()) } +} - fn swift_wrapper_prefix(&self, package: &UniffiPackage) -> Result { - let mut modules_to_import: Vec = vec![]; - - package - .iter() - .filter(|p| p.name != package.name) - .for_each(|p| modules_to_import.push(p.internal_module_name().unwrap())); +impl TryFrom<&str> for CargoProfile { + type Error = anyhow::Error; - let project_ffi_module_name = self.ffi_module_name()?; - if package.ffi_module_name()? != project_ffi_module_name { - modules_to_import.push(project_ffi_module_name); + fn try_from(value: &str) -> std::result::Result { + match value { + "dev" => Ok(CargoProfile::Dev), + "release" => Ok(CargoProfile::Release), + _ => anyhow::bail!("Invalid profile: {}", value), } - - Ok(PrefixTemplate { modules_to_import }.render()?) } } -#[derive(Template)] -#[template(path = "binding-prefix.swift", escape = "none")] -struct PrefixTemplate { - modules_to_import: Vec, +impl TryFrom for CargoProfile { + type Error = anyhow::Error; + + fn try_from(value: String) -> std::result::Result { + Self::try_from(value.as_str()) + } } diff --git a/src/cli.rs b/src/cli.rs index 086393e..0c89b0f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -58,7 +58,7 @@ fn build(args: BuildArgs) -> Result<()> { }; let project = Project::new()?; - project.build(args.profile, apple_platforms) + project.build(args.profile.try_into()?, apple_platforms) } fn generate_package(args: GeneratePackageArgs) -> Result<()> { diff --git a/src/xcframework.rs b/src/xcframework.rs index 34b6cfc..6f0248a 100644 --- a/src/xcframework.rs +++ b/src/xcframework.rs @@ -5,19 +5,20 @@ use std::path::{Path, PathBuf}; use std::process::Command; use crate::apple_platform::ApplePlatform; +use crate::build::CargoProfile; use crate::utils::*; pub fn create_xcframework( cargo_target_dir: &Path, targets: Vec, - profile: String, + profile: CargoProfile, name: &str, xcframework: &Path, swift_wrapper: &Path, ) -> Result<()> { let temp_dir = cargo_target_dir.join("tmp/wp-rs-xcframework"); fs::recreate_dir(&temp_dir)?; - XCFramework::new(&targets, &profile)?.create( + XCFramework::new(&targets, profile)?.create( cargo_target_dir, name, &temp_dir, @@ -48,11 +49,11 @@ struct LibraryGroup { // Represent a thin static library which is built with `cargo build --target --profile ` struct Slice { target: String, - profile: String, + profile: CargoProfile, } impl XCFramework { - fn new(targets: &Vec, profile: &str) -> Result { + fn new(targets: &Vec, profile: CargoProfile) -> Result { let mut groups = HashMap::::new(); for target in targets { let id = LibraryGroupId::from_target(target)?; @@ -276,14 +277,7 @@ impl Slice { /// Returns the directory where the built static libraries are located. fn built_product_dir(&self, cargo_target_dir: &Path) -> PathBuf { - let mut target_dir: PathBuf = cargo_target_dir.join(&self.target); - if self.profile == "dev" { - target_dir.push("debug"); - } else { - target_dir.push(&self.profile); - } - - target_dir + cargo_target_dir.join(&self.target).join(self.profile.dir_name()) } fn built_libraries(&self, cargo_target_dir: &Path) -> Result> { From ca7d6996b26af0acd6a0f2e8f466555d8ca33409 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 14:09:26 +1300 Subject: [PATCH 14/19] Simplify API --- src/build.rs | 21 +++++++++---------- src/project.rs | 57 +++++++++++++++++++++----------------------------- src/spm.rs | 4 ++-- 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/build.rs b/src/build.rs index b55c42d..8fb92a9 100644 --- a/src/build.rs +++ b/src/build.rs @@ -46,7 +46,7 @@ impl BuildExtensions for Project { target.build_uniffi_package()?; target.generate_bindings( &self.cargo_metadata.target_directory, - self.ffi_module_name()?, + &self.ffi_module_name, )?; } @@ -62,9 +62,9 @@ impl BuildExtensions for Project { .map(|s| s.to_string()) .collect(), profile, - &self.ffi_module_name()?, - self.xcframework_path()?.as_std_path(), - self.swift_wrapper_dir()?.as_std_path(), + &self.ffi_module_name, + self.xcframework_path().as_std_path(), + self.swift_wrapper_dir().as_std_path(), )?; } @@ -76,8 +76,7 @@ impl BuildExtensions for Project { impl Project { fn update_swift_wrappers(&self) -> Result<()> { - for item in self.swift_wrapper_files_iter() { - let (path, package) = item?; + for (path, package) in self.swift_wrapper_files_iter() { self.update_swift_wrapper(path, package)?; } @@ -126,7 +125,7 @@ impl Project { .filter(|p| p.name != package.name) .for_each(|p| modules_to_import.push(p.internal_module_name().unwrap())); - let project_ffi_module_name = self.ffi_module_name()?; + let project_ffi_module_name = self.ffi_module_name.clone(); if package.ffi_module_name()? != project_ffi_module_name { modules_to_import.push(project_ffi_module_name); } @@ -146,10 +145,10 @@ impl Project { anyhow::bail!("Headers directory not found: {}", &headers_dir) } - let linux_library_dir = self.linux_library_path()?; + let linux_library_dir = self.linux_library_path(); fs::copy_dir(&headers_dir, &linux_library_dir)?; - let static_lib_dest = linux_library_dir.join(format!("{}.a", self.ffi_module_name()?)); + let static_lib_dest = linux_library_dir.join(format!("{}.a", self.ffi_module_name)); std::fs::copy(&static_lib, &static_lib_dest)?; Ok(()) @@ -233,7 +232,7 @@ impl PlatformTarget { fn generate_bindings( &self, cargo_target_dir: &Utf8PathBuf, - ffi_module_name: String, + ffi_module_name: &str, ) -> Result<()> { for target_dir in self.built_dirs(cargo_target_dir) { let libraries = target_dir.files_with_extension("a")?; @@ -258,7 +257,7 @@ impl PlatformTarget { }; uniffi_bindgen::bindings::generate_swift_bindings(options)?; - self.reorganize_binding_files(&out_dir, ffi_module_name.clone())?; + self.reorganize_binding_files(&out_dir, ffi_module_name.to_string())?; } Ok(()) diff --git a/src/project.rs b/src/project.rs index fc2b87d..5238436 100644 --- a/src/project.rs +++ b/src/project.rs @@ -6,6 +6,7 @@ use toml::Table; pub struct Project { pub package: UniffiPackage, + pub ffi_module_name: String, pub cargo_metadata: Metadata, } @@ -19,8 +20,12 @@ impl Project { anyhow::bail!("The current directory is not the cargo root directory") } + let package = Self::uniffi_package(&cargo_metadata)?; + let ffi_module_name = package.ffi_module_name()?; + Ok(Self { - package: Self::uniffi_package(&cargo_metadata)?, + package, + ffi_module_name, cargo_metadata, }) } @@ -82,49 +87,35 @@ impl Project { self.packages_iter().find(|p| p.name == name) } - pub fn ffi_module_name(&self) -> Result { - self.package.ffi_module_name() - } - - pub fn linux_library_path(&self) -> Result { - let ffi_module_name = self.ffi_module_name()?; - Ok(self - .cargo_metadata + pub fn linux_library_path(&self) -> Utf8PathBuf { + self.cargo_metadata .target_directory - .join(&ffi_module_name) - .join("linux")) + .join(&self.ffi_module_name) + .join("linux") } - pub fn xcframework_path(&self) -> Result { - let ffi_module_name = self.ffi_module_name()?; - Ok(self - .cargo_metadata + pub fn xcframework_path(&self) -> Utf8PathBuf { + self.cargo_metadata .target_directory - .join(&ffi_module_name) - .join(format!("{}.xcframework", &ffi_module_name))) + .join(&self.ffi_module_name) + .join(format!("{}.xcframework", &self.ffi_module_name)) } - pub fn swift_wrapper_dir(&self) -> Result { - Ok(self - .cargo_metadata + pub fn swift_wrapper_dir(&self) -> Utf8PathBuf { + self.cargo_metadata .target_directory - .join(self.ffi_module_name()?) - .join("swift-wrapper")) + .join(&self.ffi_module_name) + .join("swift-wrapper") } pub fn swift_wrapper_files_iter( &self, - ) -> impl Iterator> { - self.packages_iter() - .map(|pkg| { - let file_name = format!("{}.swift", pkg.name); - let path = self.swift_wrapper_dir()?.join(file_name); - if path.exists() { - Ok((path, pkg)) - } else { - anyhow::bail!("Swift wrapper file {} not found. Please run the build command first", path); - } - }) + ) -> impl Iterator { + self.packages_iter().map(|pkg| { + let file_name = format!("{}.swift", pkg.name); + let path = self.swift_wrapper_dir().join(file_name); + (path, pkg) + }) } } diff --git a/src/spm.rs b/src/spm.rs index fe2c915..57bc6cd 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -100,7 +100,7 @@ impl SPMExtension for Project { let template = PackageTemplate { package_name: top_level_package.public_module_name()?, - ffi_module_name: self.ffi_module_name()?, + ffi_module_name: self.ffi_module_name.clone(), project_name, targets, internal_targets, @@ -134,7 +134,7 @@ impl Project { } fn internal_target(&self, package: &UniffiPackage) -> Result { - let swift_wrapper_dir = self.swift_wrapper_dir()?; + let swift_wrapper_dir = self.swift_wrapper_dir(); let source_file_name = package.swift_wrapper_file_name(); let binding_file = swift_wrapper_dir.join(&source_file_name); if !binding_file.exists() { From 835cd277cf20c1718a8ab5139e57e7f226365b95 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 14:41:09 +1300 Subject: [PATCH 15/19] Fix warnings in Package.swift --- src/spm.rs | 16 +++++++++++++--- templates/Package.swift | 7 +++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/spm.rs b/src/spm.rs index 57bc6cd..224873e 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -1,4 +1,5 @@ use std::{ + ffi::OsStr, fs::File, io::Write, path::{Path, PathBuf}, @@ -10,7 +11,7 @@ use cargo_metadata::{camino::Utf8PathBuf, MetadataCommand}; use rinja::Template; use crate::project::*; -use crate::utils::{fs, ExecuteCommand}; +use crate::utils::*; pub struct DeploymentTargets; @@ -78,6 +79,7 @@ struct InternalTarget { name: String, swift_wrapper_dir: String, source_file: String, + excluded_source_files: Vec, dependencies: Vec, } @@ -144,6 +146,13 @@ impl Project { ) } + let excluded_source_files = swift_wrapper_dir + .files_with_extension("swift")? + .iter() + .filter(|f| f.file_name() != Some(OsStr::new(&source_file_name))) + .map(|f| f.file_name().unwrap().to_str().unwrap().to_string()) + .collect::>(); + let dependencies = package .dependencies .iter() @@ -153,10 +162,11 @@ impl Project { Ok(InternalTarget { name: package.internal_module_name()?, swift_wrapper_dir: fs::relative_path( - swift_wrapper_dir, + &swift_wrapper_dir, &self.cargo_metadata.workspace_root, ), - source_file: source_file_name, + source_file: source_file_name.clone(), + excluded_source_files, dependencies, }) } diff --git a/templates/Package.swift b/templates/Package.swift index 1b8298a..3231c0c 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -70,6 +70,13 @@ var package = Package( .target(name: ffiTarget.name) ], path: "{{ target.swift_wrapper_dir }}", + {% if !target.excluded_source_files.is_empty() %} + exclude: [ + {% for file in target.excluded_source_files %} + "{{ file }}", + {% endfor %} + ], + {% endif %} sources: ["{{ target.source_file }}"], swiftSettings: [ .swiftLanguageMode(.v5) From 365db420fc3168f8d4dda3a81b274e4ff847eafe Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 19:48:05 +1300 Subject: [PATCH 16/19] Remove empty lines from Package.swift --- src/xcframework.rs | 6 +++--- templates/Package.swift | 32 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/xcframework.rs b/src/xcframework.rs index 6f0248a..d5b346a 100644 --- a/src/xcframework.rs +++ b/src/xcframework.rs @@ -87,7 +87,7 @@ impl XCFramework { self.preview(); let temp_dest = self.create_xcframework(cargo_target_dir, library_file_name, temp_dir)?; - self.patch_xcframework(&temp_dest)?; + self.patch_xcframework(&temp_dest, library_file_name)?; fs::recreate_dir(dest)?; std::fs::rename(temp_dest, dest).with_context(|| "Failed to move xcframework")?; @@ -156,7 +156,7 @@ impl XCFramework { } // Fixes an issue including the XCFramework in an Xcode project that already contains an XCFramework: https://github.com/jessegrosjean/module-map-error - fn patch_xcframework(&self, temp_dir: &Path) -> Result<()> { + fn patch_xcframework(&self, temp_dir: &Path, module_name: &str) -> Result<()> { println!("Patching XCFramework to have a unique header directory"); for dir_entry in std::fs::read_dir(temp_dir)? { @@ -174,7 +174,7 @@ impl XCFramework { }) .collect(); - let new_headers_dir = headers_dir.join("libwordpressFFI"); + let new_headers_dir = headers_dir.join(module_name); fs::recreate_dir(&new_headers_dir)?; for file in non_lib_files { diff --git a/templates/Package.swift b/templates/Package.swift index 3231c0c..fe6692e 100644 --- a/templates/Package.swift +++ b/templates/Package.swift @@ -44,13 +44,13 @@ var package = Package( ], dependencies: [], targets: [ - {% for target in targets %} + {%- for target in targets %} .target( name: "{{ target.name }}", dependencies: [ - {% for dep in target.dependencies %} + {%- for dep in target.dependencies %} .target(name: "{{ dep }}"), - {% endfor %} + {%- endfor %} .target(name: "{{ target.name }}Internal") ], path: "{{ target.library_source_path }}", @@ -58,35 +58,33 @@ var package = Package( .enableExperimentalFeature("StrictConcurrency"), ] ), - {% endfor %} + {%- endfor %} - {% for target in internal_targets %} + {%- for target in internal_targets %} .target( name: "{{ target.name }}", dependencies: [ - {% for dep in target.dependencies %} + {%- for dep in target.dependencies %} .target(name: "{{ dep }}"), - {% endfor %} + {%- endfor %} .target(name: ffiTarget.name) ], path: "{{ target.swift_wrapper_dir }}", - {% if !target.excluded_source_files.is_empty() %} + {%- if !target.excluded_source_files.is_empty() %} exclude: [ - {% for file in target.excluded_source_files %} + {%- for file in target.excluded_source_files %} "{{ file }}", - {% endfor %} + {%- endfor %} ], - {% endif %} + {%- endif %} sources: ["{{ target.source_file }}"], - swiftSettings: [ - .swiftLanguageMode(.v5) - ] + swiftSettings: [.swiftLanguageMode(.v5)] ), - {% endfor %} + {%- endfor %} ffiTarget, - {% for target in targets %} + {%- for target in targets %} .testTarget( name: "{{ target.name }}Tests", dependencies: [ @@ -100,7 +98,7 @@ var package = Package( {% endif %} ] ), - {% endfor %} + {%- endfor %} ] ) From 29ca37ebad2fce5b54eff6387739b746630d7996 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 19:56:37 +1300 Subject: [PATCH 17/19] Mark vended source files as read-only --- src/spm.rs | 1 + src/utils.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/spm.rs b/src/spm.rs index 224873e..3439f12 100644 --- a/src/spm.rs +++ b/src/spm.rs @@ -245,6 +245,7 @@ impl Project { println!(" - to: {}", new_path); fs::copy_dir(&swift_code_dir, &new_path)?; + fs::read_only_files(&new_path)?; swift_code_dir = new_path; diff --git a/src/utils.rs b/src/utils.rs index 9b6ff70..41a8d96 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -107,6 +107,23 @@ pub(crate) mod fs { Ok(()) } + pub fn read_only_files>(path: P) -> Result<()> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + read_only_files(&path)?; + continue; + } + + let mut permissions = std::fs::metadata(&path)?.permissions(); + permissions.set_readonly(true); + std::fs::set_permissions(&path, permissions)?; + } + + Ok(()) + } + pub fn relative_path(path: P, base: B) -> String where P: AsRef, From a5215f3849a1c63bdd4da139e87ce625e89711c3 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 20:08:43 +1300 Subject: [PATCH 18/19] Format code --- src/build.rs | 6 ++---- src/project.rs | 4 +--- src/utils.rs | 5 ++++- src/xcframework.rs | 4 +++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/build.rs b/src/build.rs index 8fb92a9..4a41090 100644 --- a/src/build.rs +++ b/src/build.rs @@ -44,10 +44,8 @@ impl BuildExtensions for Project { }; for target in &targets { target.build_uniffi_package()?; - target.generate_bindings( - &self.cargo_metadata.target_directory, - &self.ffi_module_name, - )?; + target + .generate_bindings(&self.cargo_metadata.target_directory, &self.ffi_module_name)?; } if apple_platforms.is_empty() { diff --git a/src/project.rs b/src/project.rs index 5238436..c65723a 100644 --- a/src/project.rs +++ b/src/project.rs @@ -108,9 +108,7 @@ impl Project { .join("swift-wrapper") } - pub fn swift_wrapper_files_iter( - &self, - ) -> impl Iterator { + pub fn swift_wrapper_files_iter(&self) -> impl Iterator { self.packages_iter().map(|pkg| { let file_name = format!("{}.swift", pkg.name); let path = self.swift_wrapper_dir().join(file_name); diff --git a/src/utils.rs b/src/utils.rs index 41a8d96..f9799a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -33,7 +33,10 @@ pub(crate) trait FileSystemExtensions { fn files_with_extension(&self, ext: &str) -> Result>; } -impl FileSystemExtensions for T where T: AsRef { +impl FileSystemExtensions for T +where + T: AsRef, +{ fn files_with_extension(&self, ext: &str) -> Result> { let files = std::fs::read_dir(self)? .filter_map(|f| f.ok()) diff --git a/src/xcframework.rs b/src/xcframework.rs index d5b346a..6fc8a27 100644 --- a/src/xcframework.rs +++ b/src/xcframework.rs @@ -277,7 +277,9 @@ impl Slice { /// Returns the directory where the built static libraries are located. fn built_product_dir(&self, cargo_target_dir: &Path) -> PathBuf { - cargo_target_dir.join(&self.target).join(self.profile.dir_name()) + cargo_target_dir + .join(&self.target) + .join(self.profile.dir_name()) } fn built_libraries(&self, cargo_target_dir: &Path) -> Result> { From c7415bd3616370934f0d0940a5fdd9b9643070e3 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 19 Dec 2024 20:55:07 +1300 Subject: [PATCH 19/19] Fix modulemap format --- templates/module.modulemap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/module.modulemap b/templates/module.modulemap index 810290d..d9b1b4b 100644 --- a/templates/module.modulemap +++ b/templates/module.modulemap @@ -1,7 +1,7 @@ module {{ ffi_module_name }} { - {%- for file in header_files -%} + {%- for file in header_files %} header "{{ file }}" - {%- endfor -%} + {%- endfor %} export * }