diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1ba2acd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,884 @@ +# 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 = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[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 = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +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 = "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" +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 = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[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 = "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" +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 = "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" +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 = "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" +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 = "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" +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-helper" +version = "0.1.0" +dependencies = [ + "anyhow", + "cargo_metadata 0.19.1", + "clap", + "pathdiff", + "rinja", + "serde_json", + "toml 0.8.19", + "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 0.5.11", + "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 0.5.11", + "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" + +[[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 new file mode 100644 index 0000000..f4ad362 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "uniffi-swift-helper" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +cargo_metadata = "0.19" +clap = { version = "4.5", features = ["derive"] } +pathdiff = "0.2" +rinja = "0.3" +serde_json = "1.0" +toml = { version = "0.8", features = ["parse"] } +uniffi = "0.28" +uniffi_bindgen = "0.28" diff --git a/src/apple_platform.rs b/src/apple_platform.rs new file mode 100644 index 0000000..a17c8cf --- /dev/null +++ b/src/apple_platform.rs @@ -0,0 +1,79 @@ +use std::{fmt::Display, process::Command}; + +use crate::spm::DeploymentTargets; + +#[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) + } + + 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 { + 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..4a41090 --- /dev/null +++ b/src/build.rs @@ -0,0 +1,336 @@ +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::Path; +use std::process::Command; + +use anyhow::Result; +use cargo_metadata::camino::Utf8PathBuf; +use rinja::Template; +use uniffi_bindgen::bindings::SwiftBindingsOptions; + +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: CargoProfile, apple_platforms: Vec) -> Result<()>; +} + +impl BuildExtensions for Project { + fn build(&self, profile: CargoProfile, apple_platforms: Vec) -> Result<()> { + let package = &self.package.name; + + let targets = if apple_platforms.is_empty() { + vec![PlatformTarget { + package: package.clone(), + profile, + platform: None, + }] + } else { + apple_platforms + .iter() + .map(|platform| PlatformTarget { + package: package.clone(), + profile, + platform: Some(*platform), + }) + .collect() + }; + 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() { + 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(), + apple_platforms + .iter() + .flat_map(|p| p.target_triples()) + .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.update_swift_wrappers()?; + + Ok(()) + } +} + +impl Project { + fn update_swift_wrappers(&self) -> Result<()> { + for (path, package) in self.swift_wrapper_files_iter() { + 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.clone(); + 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 self + .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", self.profile.as_str()); + // Abort on panic to include Rust backtrace in crash reports. + let config_panic = format!(r#"profile.{}.panic="abort""#, self.profile.as_str()); + build.extend(["--config", &config_debug, "--config", &config_panic]); + + 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 {}", self.package) + } + } + + Ok(()) + } + + 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, + cargo_target_dir: &Utf8PathBuf, + ffi_module_name: &str, + ) -> 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) + } + + 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.to_string())?; + } + + Ok(()) + } + + fn reorganize_binding_files(&self, bindings_dir: &Path, ffi_module_name: String) -> 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 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)?; + } + } + + let template = ModuleMapTemplate { + ffi_module_name, + header_files, + }; + let content = template.render()?; + let mut modulemap = File::create_new(headers_dir.join("module.modulemap"))?; + modulemap.write_all(content.as_bytes())?; + + Ok(()) + } +} + +#[derive(Template)] +#[template(path = "binding-prefix.swift", escape = "none")] +struct PrefixTemplate { + modules_to_import: Vec, +} + +impl CargoProfile { + pub fn as_str(&self) -> &str { + match self { + CargoProfile::Dev => "dev", + CargoProfile::Release => "release", + } + } + + pub fn dir_name(&self) -> &str { + match self { + CargoProfile::Dev => "debug", + CargoProfile::Release => "release", + } + } +} + +impl TryFrom<&str> for CargoProfile { + type Error = anyhow::Error; + + fn try_from(value: &str) -> std::result::Result { + match value { + "dev" => Ok(CargoProfile::Dev), + "release" => Ok(CargoProfile::Release), + _ => anyhow::bail!("Invalid profile: {}", value), + } + } +} + +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 new file mode 100644 index 0000000..0c89b0f --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,67 @@ +use std::env; + +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use crate::apple_platform::ApplePlatform; +use crate::build::*; +use crate::project::Project; +use crate::spm::*; + +#[derive(Parser)] +pub(crate) struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + Build(BuildArgs), + GeneratePackage(GeneratePackageArgs), +} + +#[derive(Parser)] +struct BuildArgs { + #[arg(long)] + only_ios: bool, + #[arg(long)] + only_macos: bool, + #[arg(long)] + profile: String, +} + +#[derive(Parser)] +struct GeneratePackageArgs { + #[arg(long)] + project_name: 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), + } + } +} + +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![] + }; + + let project = Project::new()?; + project.build(args.profile.try_into()?, apple_platforms) +} + +fn generate_package(args: GeneratePackageArgs) -> Result<()> { + let project = Project::new()?; + project.generate_swift_package(args.project_name) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..faa1d82 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod apple_platform; +mod build; +mod cli; +mod project; +mod spm; +mod utils; +mod xcframework; + +pub fn cli_main() -> anyhow::Result<()> { + cli::Cli::execute() +} diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..c65723a --- /dev/null +++ b/src/project.rs @@ -0,0 +1,210 @@ +use std::str::FromStr; + +use anyhow::{Context, Result}; +use cargo_metadata::{camino::Utf8PathBuf, DependencyKind, Metadata, MetadataCommand, Package}; +use toml::Table; + +pub struct Project { + pub package: UniffiPackage, + pub ffi_module_name: String, + pub cargo_metadata: Metadata, +} + +impl Project { + pub fn new() -> 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") + } + + let package = Self::uniffi_package(&cargo_metadata)?; + let ffi_module_name = package.ffi_module_name()?; + + Ok(Self { + package, + ffi_module_name, + cargo_metadata, + }) + } + + fn uniffi_package(metadata: &Metadata) -> 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 = 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 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 linux_library_path(&self) -> Utf8PathBuf { + self.cargo_metadata + .target_directory + .join(&self.ffi_module_name) + .join("linux") + } + + 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") + } + + 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); + (path, pkg) + }) + } +} + +#[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() + } + + pub fn ffi_module_name(&self) -> Result { + 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") + } + + 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) + .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) + } + + 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 new file mode 100644 index 0000000..3439f12 --- /dev/null +++ b/src/spm.rs @@ -0,0 +1,254 @@ +use std::{ + ffi::OsStr, + fs::File, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{Context, Result}; +use cargo_metadata::{camino::Utf8PathBuf, MetadataCommand}; +use rinja::Template; + +use crate::project::*; +use crate::utils::*; + +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 { + package_name: String, + ffi_module_name: String, + 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 { + name: String, + library_source_path: String, + test_source_path: String, + dependencies: Vec, + has_test_resources: bool, +} + +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()) +} + +struct InternalTarget { + name: String, + swift_wrapper_dir: String, + source_file: String, + excluded_source_files: Vec, + dependencies: Vec, +} + +pub trait SPMExtension { + fn generate_swift_package(&self, project_name: String) -> Result<()>; +} + +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() + .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: top_level_package.public_module_name()?, + ffi_module_name: self.ffi_module_name.clone(), + 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(); + File::create(&dest)?.write_all(content.as_bytes())?; + + Command::new("swift") + .args(["format", "--in-place"]) + .arg(&dest) + .successful_output()?; + + Ok(()) + } +} + +impl Project { + fn swift_package_manifest_file_path(&self) -> Utf8PathBuf { + self.cargo_metadata.workspace_root.join("Package.swift") + } + + 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(&self, package: &UniffiPackage) -> Result { + 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() { + anyhow::bail!( + "Swift wrapper file is not found at {}. Need to build xcframework first.", + binding_file + ) + } + + 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() + .map(|p| p.internal_module_name()) + .collect::>>()?; + + Ok(InternalTarget { + name: package.internal_module_name()?, + swift_wrapper_dir: fs::relative_path( + &swift_wrapper_dir, + &self.cargo_metadata.workspace_root, + ), + source_file: source_file_name.clone(), + excluded_source_files, + dependencies, + }) + } + + 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.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 + .iter() + .map(|p| self.spm_target_name(&p.name)) + .collect::>>()?; + + Ok(Target { + name: package.public_module_name()?, + 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.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); + } + + 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)?; + fs::read_only_files(&new_path)?; + + swift_code_dir = new_path; + + Ok(swift_code_dir) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..f9799a9 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,141 @@ +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 T +where + T: AsRef, +{ + 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: 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))?; + } + + 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) + } + + 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)?; + } + + 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(()) + } + + 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, + 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 new file mode 100644 index 0000000..6fc8a27 --- /dev/null +++ b/src/xcframework.rs @@ -0,0 +1,368 @@ +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::build::CargoProfile; +use crate::utils::*; + +pub fn create_xcframework( + cargo_target_dir: &Path, + targets: Vec, + 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( + cargo_target_dir, + name, + &temp_dir, + xcframework, + swift_wrapper, + )?; + + std::fs::remove_dir_all(&temp_dir).ok(); + + 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: CargoProfile, +} + +impl XCFramework { + fn new(targets: &Vec, profile: CargoProfile) -> 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, library_file_name)?; + + 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, module_name: &str) -> 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(module_name); + 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 { + cargo_target_dir + .join(&self.target) + .join(self.profile.dir_name()) + } + + 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 + ) + } + } +} diff --git a/templates/Package.swift b/templates/Package.swift new file mode 100644 index 0000000..fe6692e --- /dev/null +++ b/templates/Package.swift @@ -0,0 +1,168 @@ +// 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 ffiLinuxLibraryPath = "target/\(ffiModuleName)/linux" + +let ffiVersion: FFIVersion = .local + +#if os(Linux) +let ffiTarget: Target = .systemLibrary( + name: ffiModuleName, + path: ffiLinuxLibraryPath + ) +#elseif os(macOS) +let ffiTarget: Target = ffiVersion.target +#endif + +var package = Package( + name: packageName, + platforms: [ + .iOS("{{ ios_version }}"), + .macOS("{{ macos_version }}"), + .tvOS("{{ tvos_version }}"), + .watchOS("{{ watchos_version }}"), + ], + products: [ + .library( + name: packageName, + targets: [libraryTargetName] + ) + ], + dependencies: [], + targets: [ + {%- for target in targets %} + .target( + name: "{{ target.name }}", + dependencies: [ + {%- for dep in target.dependencies %} + .target(name: "{{ dep }}"), + {%- endfor %} + .target(name: "{{ target.name }}Internal") + ], + path: "{{ target.library_source_path }}", + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + ] + ), + {%- endfor %} + + {%- for target in internal_targets %} + .target( + name: "{{ target.name }}", + dependencies: [ + {%- for dep in target.dependencies %} + .target(name: "{{ dep }}"), + {%- endfor %} + .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)] + ), + {%- endfor %} + + 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 +} diff --git a/templates/binding-prefix.swift b/templates/binding-prefix.swift new file mode 100644 index 0000000..d4fbaa4 --- /dev/null +++ b/templates/binding-prefix.swift @@ -0,0 +1,8 @@ +// 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 ==== diff --git a/templates/module.modulemap b/templates/module.modulemap new file mode 100644 index 0000000..d9b1b4b --- /dev/null +++ b/templates/module.modulemap @@ -0,0 +1,7 @@ +module {{ ffi_module_name }} { + {%- for file in header_files %} + header "{{ file }}" + {%- endfor %} + + export * +}