qt/bundle -> qt/launcher

- remove more of the old bundling code
- handle app icon
This commit is contained in:
Damien Elmes 2025-06-14 18:38:53 +07:00
parent 726318f016
commit 90e1264895
68 changed files with 126 additions and 1848 deletions

View file

@ -20,7 +20,6 @@
"ftl/usage", "ftl/usage",
"licenses.json", "licenses.json",
".dmypy.json", ".dmypy.json",
"qt/bundle/PyOxidizer",
"target", "target",
".mypy_cache", ".mypy_cache",
"extra", "extra",

View file

@ -2,4 +2,3 @@
py_version=39 py_version=39
known_first_party=anki,aqt,tests known_first_party=anki,aqt,tests
profile=black profile=black
extend_skip=qt/bundle

View file

@ -18,7 +18,7 @@ mypy_path =
ftl, ftl,
pylib/tools, pylib/tools,
python python
exclude = (qt/bundle/PyOxidizer|pylib/anki/_vendor) exclude = (pylib/anki/_vendor)
[mypy-anki.*] [mypy-anki.*]
disallow_untyped_defs = True disallow_untyped_defs = True

View file

@ -1,2 +1,2 @@
target-version = "py39" target-version = "py39"
extend-exclude = ["qt/bundle"] extend-exclude = []

View file

@ -31,8 +31,7 @@
"rust-analyzer.rustfmt.extraArgs": ["+nightly"], "rust-analyzer.rustfmt.extraArgs": ["+nightly"],
"search.exclude": { "search.exclude": {
"**/node_modules": true, "**/node_modules": true,
".bazel/**": true, ".bazel/**": true
"qt/bundle/PyOxidizer": true
}, },
"rust-analyzer.cargo.buildScripts.enable": true, "rust-analyzer.cargo.buildScripts.enable": true,
"python.analysis.typeCheckingMode": "off", "python.analysis.typeCheckingMode": "off",

418
Cargo.lock generated
View file

@ -292,18 +292,6 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "apple-bundles"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716b8a7bacf7325eb3e7a1a7f5ead4da91e1e16d9b56a25edea0e1e4ba21fd8e"
dependencies = [
"anyhow",
"plist",
"simple-file-manifest",
"walkdir",
]
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
version = "1.4.1" version = "1.4.1"
@ -539,12 +527,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.7" version = "0.21.7"
@ -631,15 +613,6 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.0" version = "1.12.0"
@ -1002,15 +975,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.20" version = "1.2.20"
@ -1073,16 +1037,6 @@ dependencies = [
"half", "half",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.37" version = "4.5.37"
@ -1839,15 +1793,6 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "des"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "difflib" name = "difflib"
version = "0.4.0" version = "0.4.0"
@ -1942,18 +1887,6 @@ dependencies = [
"dtoa", "dtoa",
] ]
[[package]]
name = "duct"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c"
dependencies = [
"libc",
"once_cell",
"os_pipe",
"shared_child",
]
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@ -2123,17 +2056,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "find-winsdk"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8cbf17b871570c1f8612b763bac3e86290602bcf5dc3c5ce657e0e1e9071d9e"
dependencies = [
"serde",
"serde_derive",
"winreg 0.5.1",
]
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
version = "0.5.7" version = "0.5.7"
@ -2273,16 +2195,6 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -3187,20 +3099,6 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.32",
"rustls 0.21.12",
"tokio",
"tokio-rustls 0.24.1",
]
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-rustls"
version = "0.27.5" version = "0.27.5"
@ -3211,13 +3109,13 @@ dependencies = [
"http 1.3.1", "http 1.3.1",
"hyper 1.6.0", "hyper 1.6.0",
"hyper-util", "hyper-util",
"rustls 0.23.26", "rustls",
"rustls-native-certs", "rustls-native-certs",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
"tokio-rustls 0.26.2", "tokio-rustls",
"tower-service", "tower-service",
"webpki-roots 0.26.8", "webpki-roots",
] ]
[[package]] [[package]]
@ -3505,16 +3403,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"block-padding",
"generic-array",
]
[[package]] [[package]]
name = "intl-memoizer" name = "intl-memoizer"
version = "0.5.2" version = "0.5.2"
@ -3883,33 +3771,6 @@ dependencies = [
"syn 2.0.101", "syn 2.0.101",
] ]
[[package]]
name = "makeapp"
version = "0.0.0"
dependencies = [
"anyhow",
"apple-bundles",
"camino",
"clap",
"glob",
"plist",
"serde",
"serde_json",
"simple-file-manifest",
"walkdir",
]
[[package]]
name = "makeexe"
version = "0.0.0"
dependencies = [
"anyhow",
"camino",
"clap",
"tugger-windows-codesign",
"walkdir",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -4623,39 +4484,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "os_pipe"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p12"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224"
dependencies = [
"cbc",
"cipher",
"des",
"getrandom 0.2.16",
"hmac",
"lazy_static",
"rc2",
"sha1",
"yasna",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@ -4720,15 +4554,6 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "pem"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
dependencies = [
"base64 0.13.1",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -4923,19 +4748,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed"
dependencies = [
"base64 0.22.1",
"indexmap",
"quick-xml",
"serde",
"time",
]
[[package]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.7" version = "0.3.7"
@ -5254,15 +5066,6 @@ dependencies = [
"syn 2.0.101", "syn 2.0.101",
] ]
[[package]]
name = "quick-xml"
version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.7" version = "0.11.7"
@ -5275,7 +5078,7 @@ dependencies = [
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"rustls 0.23.26", "rustls",
"socket2", "socket2",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
@ -5292,9 +5095,9 @@ dependencies = [
"bytes", "bytes",
"getrandom 0.3.2", "getrandom 0.3.2",
"rand 0.9.1", "rand 0.9.1",
"ring 0.17.14", "ring",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"rustls 0.23.26", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -5467,27 +5270,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "rc2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd"
dependencies = [
"cipher",
]
[[package]]
name = "rcgen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b"
dependencies = [
"pem",
"ring 0.16.20",
"time",
"yasna",
]
[[package]] [[package]]
name = "reborrow" name = "reborrow"
version = "0.5.5" version = "0.5.5"
@ -5596,7 +5378,6 @@ dependencies = [
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.32", "hyper 0.14.32",
"hyper-rustls 0.24.2",
"hyper-tls 0.5.0", "hyper-tls 0.5.0",
"ipnet", "ipnet",
"js-sys", "js-sys",
@ -5606,7 +5387,6 @@ dependencies = [
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls 0.21.12",
"rustls-pemfile 1.0.4", "rustls-pemfile 1.0.4",
"serde", "serde",
"serde_json", "serde_json",
@ -5615,14 +5395,12 @@ dependencies = [
"system-configuration", "system-configuration",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.24.1",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots 0.25.4", "winreg",
"winreg 0.50.0",
] ]
[[package]] [[package]]
@ -5640,7 +5418,7 @@ dependencies = [
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper 1.6.0", "hyper 1.6.0",
"hyper-rustls 0.27.5", "hyper-rustls",
"hyper-tls 0.6.0", "hyper-tls 0.6.0",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@ -5653,7 +5431,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn", "quinn",
"rustls 0.23.26", "rustls",
"rustls-native-certs", "rustls-native-certs",
"rustls-pemfile 2.2.0", "rustls-pemfile 2.2.0",
"rustls-pki-types", "rustls-pki-types",
@ -5663,7 +5441,7 @@ dependencies = [
"sync_wrapper 1.0.2", "sync_wrapper 1.0.2",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.26.2", "tokio-rustls",
"tokio-socks", "tokio-socks",
"tokio-util", "tokio-util",
"tower", "tower",
@ -5673,25 +5451,10 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams", "wasm-streams",
"web-sys", "web-sys",
"webpki-roots 0.26.8", "webpki-roots",
"windows-registry", "windows-registry",
] ]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@ -5702,7 +5465,7 @@ dependencies = [
"cfg-if", "cfg-if",
"getrandom 0.2.16", "getrandom 0.2.16",
"libc", "libc",
"untrusted 0.9.0", "untrusted",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -5855,18 +5618,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "rustls"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring 0.17.14",
"rustls-webpki 0.101.7",
"sct",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.26" version = "0.23.26"
@ -5874,9 +5625,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"ring 0.17.14", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki 0.103.1", "rustls-webpki",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@ -5920,25 +5671,15 @@ dependencies = [
"web-time", "web-time",
] ]
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring 0.17.14",
"untrusted 0.9.0",
]
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.1" version = "0.103.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
dependencies = [ dependencies = [
"ring 0.17.14", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted 0.9.0", "untrusted",
] ]
[[package]] [[package]]
@ -6012,16 +5753,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring 0.17.14",
"untrusted 0.9.0",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.11.1" version = "2.11.1"
@ -6233,16 +5964,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shared_child"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -6258,12 +5979,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simple-file-manifest"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@ -6337,12 +6052,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.8" version = "0.9.8"
@ -6824,23 +6533,13 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
"tokio",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.2" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [ dependencies = [
"rustls 0.23.26", "rustls",
"tokio", "tokio",
] ]
@ -7038,58 +6737,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tugger-common"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90d950380afdb1a6bbe74f29485a04e821869dfad11f5929ff1c5b1dac09d02"
dependencies = [
"anyhow",
"fs2",
"glob",
"hex",
"log",
"once_cell",
"reqwest 0.11.27",
"sha2",
"tempfile",
"url",
"zip 0.6.6",
]
[[package]]
name = "tugger-windows"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f181ac4fc7f8facfd418824d13045cd068ee73de44319a6116868c22789782"
dependencies = [
"anyhow",
"duct",
"find-winsdk",
"glob",
"once_cell",
"semver",
"tugger-common",
"winapi",
]
[[package]]
name = "tugger-windows-codesign"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3f09f8bdace495373cec3fc607bc39f00720a984ba82e310cc9382462fd364"
dependencies = [
"anyhow",
"duct",
"log",
"p12",
"rcgen",
"time",
"tugger-common",
"tugger-windows",
"yasna",
]
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.21.0" version = "0.21.0"
@ -7290,12 +6937,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@ -7566,12 +7207,6 @@ dependencies = [
"string_cache_codegen", "string_cache_codegen",
] ]
[[package]]
name = "webpki-roots"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.8" version = "0.26.8"
@ -8233,16 +7868,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winreg"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
dependencies = [
"serde",
"winapi",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.50.0" version = "0.50.0"
@ -8346,15 +7971,6 @@ dependencies = [
"lzma-sys", "lzma-sys",
] ]
[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.5" version = "0.7.5"

View file

@ -12,9 +12,7 @@ members = [
"build/runner", "build/runner",
"ftl", "ftl",
"pylib/rsbridge", "pylib/rsbridge",
"qt/bundle/launcher", "qt/launcher",
"qt/bundle/mac",
"qt/bundle/win",
"rslib", "rslib",
"rslib/i18n", "rslib/i18n",
"rslib/io", "rslib/io",

View file

@ -371,11 +371,7 @@ fn build_wheel(build: &mut Build) -> Result<()> {
} }
fn check_python(build: &mut Build) -> Result<()> { fn check_python(build: &mut Build) -> Result<()> {
python_format( python_format(build, "qt", inputs![glob!("qt/**/*.py")])?;
build,
"qt",
inputs![glob!("qt/**/*.py", "qt/bundle/PyOxidizer/**")],
)?;
build.add_action( build.add_action(
"check:pytest:aqt", "check:pytest:aqt",

View file

@ -1,445 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
#![allow(dead_code)]
#![allow(unused_imports)]
use std::env;
use anyhow::Result;
use ninja_gen::action::BuildAction;
use ninja_gen::archives::download_and_extract;
use ninja_gen::archives::empty_manifest;
use ninja_gen::archives::with_exe;
use ninja_gen::archives::OnlineArchive;
use ninja_gen::archives::Platform;
use ninja_gen::build::BuildProfile;
use ninja_gen::cargo::CargoBuild;
use ninja_gen::cargo::RustOutput;
use ninja_gen::git::SyncSubmodule;
use ninja_gen::glob;
use ninja_gen::input::BuildInput;
use ninja_gen::inputs;
use ninja_gen::python::PythonEnvironment;
use ninja_gen::Build;
use ninja_gen::Utf8Path;
use crate::anki_version;
use crate::platform::overriden_python_target_platform;
use crate::platform::overriden_rust_target_triple;
#[derive(Debug, PartialEq, Eq)]
enum DistKind {
Standard,
}
impl DistKind {
fn folder_name(&self) -> &'static str {
match self {
DistKind::Standard => "std",
}
}
fn name(&self) -> &'static str {
match self {
DistKind::Standard => "standard",
}
}
}
pub fn build_bundle(_build: &mut Build) -> Result<()> {
// install into venv
// setup_primary_venv(build)?;
// install_anki_wheels(build)?;
// // bundle venv into output binary + extra_files
// build_pyoxidizer(build)?;
// build_artifacts(build)?;
// build_binary(build)?;
// // package up outputs with Qt/other deps
// download_dist_folder_deps(build)?;
// build_dist_folder(build, DistKind::Standard)?;
// build_packages(build)?;
Ok(())
}
fn targetting_macos_arm() -> bool {
cfg!(all(target_os = "macos", target_arch = "aarch64"))
&& overriden_python_target_platform().is_none()
}
const WIN_AUDIO: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/audio-win-amd64.tar.gz",
sha256: "0815a601baba05e03bc36b568cdc2332b1cf4aa17125fc33c69de125f8dd687f",
};
const MAC_ARM_AUDIO: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-05-26/audio-mac-arm64.tar.gz",
sha256: "f6c4af9be59ae1c82a16f5c6307f13cbf31b49ad7b69ce1cb6e0e7b403cfdb8f",
};
const MAC_AMD_AUDIO: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-05-26/audio-mac-amd64.tar.gz",
sha256: "ecbb3c878805cdd58b1a0b8e3fd8c753b8ce3ad36c8b5904a79111f9db29ff42",
};
const MAC_ARM_QT6: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2024-02-29/pyqt6.6-mac-arm64.tar.zst",
sha256: "9b2ade4ae9b80506689062845e83e8c60f7fa9843545bf7bb2d11d3e2f105878",
};
const MAC_AMD_QT6: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2024-02-29/pyqt6.6-mac-amd64.tar.zst",
sha256: "dbd0871e4da22820d1fa9ab29220d631467d1178038dcab4b15169ad7f499b1b",
};
const LINUX_QT_PLUGINS: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2023-05-02/qt-plugins-linux-amd64.tar.gz",
sha256: "66bb568aca7242bc55ad419bf5c96755ca15d2a743e1c3a09cba8b83230b138b",
};
const NSIS_PLUGINS: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2023-05-19/nsis.tar.zst",
sha256: "6133f730ece699de19714d0479c73bc848647d277e9cc80dda9b9ebe532b40a8",
};
fn download_dist_folder_deps(build: &mut Build) -> Result<()> {
let mut bundle_deps = vec![":wheels"];
if cfg!(windows) {
download_and_extract(build, "win_amd64_audio", WIN_AUDIO, empty_manifest())?;
download_and_extract(build, "nsis_plugins", NSIS_PLUGINS, empty_manifest())?;
bundle_deps.extend([":extract:win_amd64_audio", ":extract:nsis_plugins"]);
} else if cfg!(target_os = "macos") {
if targetting_macos_arm() {
download_and_extract(build, "mac_arm_audio", MAC_ARM_AUDIO, empty_manifest())?;
download_and_extract(build, "mac_arm_qt6", MAC_ARM_QT6, empty_manifest())?;
bundle_deps.extend([":extract:mac_arm_audio", ":extract:mac_arm_qt6"]);
} else {
download_and_extract(build, "mac_amd_audio", MAC_AMD_AUDIO, empty_manifest())?;
download_and_extract(build, "mac_amd_qt6", MAC_AMD_QT6, empty_manifest())?;
bundle_deps.extend([":extract:mac_amd_audio", ":extract:mac_amd_qt6"]);
}
} else {
download_and_extract(
build,
"linux_qt_plugins",
LINUX_QT_PLUGINS,
empty_manifest(),
)?;
bundle_deps.extend([":extract:linux_qt_plugins"]);
}
build.add_dependency(
"bundle:deps",
inputs![bundle_deps
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()],
);
Ok(())
}
struct Venv {
label: &'static str,
path_without_builddir: &'static str,
}
impl Venv {
fn label_as_target(&self, suffix: &str) -> String {
format!(":{}{suffix}", self.label)
}
}
const PRIMARY_VENV: Venv = Venv {
label: "bundle:pyenv",
path_without_builddir: "bundle/pyenv",
};
// fn setup_primary_venv(build: &mut Build) -> Result<()> {
// let mut qt6_reqs = inputs![
// "python/requirements.bundle.txt",
// "python/requirements.qt6_6.txt",
// ];
// if cfg!(windows) {
// qt6_reqs = inputs![qt6_reqs, "python/requirements.win.txt"];
// }
// build.add_action(
// PRIMARY_VENV.label,
// PythonEnvironment {
// venv_folder: PRIMARY_VENV.path_without_builddir,
// base_requirements_txt: "python/requirements.base.txt".into(),
// requirements_txt: qt6_reqs,
// extra_binary_exports: &[],
// },
// )?;
// Ok(())
// }
struct InstallAnkiWheels {
venv: Venv,
}
impl BuildAction for InstallAnkiWheels {
fn command(&self) -> &str {
"$pip install --force-reinstall --no-deps $in"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
build.add_inputs("pip", inputs![self.venv.label_as_target(":pip")]);
build.add_inputs("in", inputs![":wheels"]);
build.add_output_stamp("bundle/wheels.stamp");
}
}
fn install_anki_wheels(build: &mut Build) -> Result<()> {
build.add_action(
"bundle:add_wheels:qt6",
InstallAnkiWheels { venv: PRIMARY_VENV },
)?;
Ok(())
}
fn build_pyoxidizer(build: &mut Build) -> Result<()> {
let offline_build = env::var("OFFLINE_BUILD").is_ok();
build.add_action(
"bundle:pyoxidizer:repo",
SyncSubmodule {
path: "qt/bundle/PyOxidizer",
offline_build,
},
)?;
let target =
overriden_rust_target_triple().unwrap_or_else(|| Platform::current().as_rust_triple());
let output_bin = format!("bundle/rust/{target}/release/pyoxidizer",);
build.add_action(
"bundle:pyoxidizer:bin",
CargoBuild {
inputs: inputs![
":bundle:pyoxidizer:repo",
"out/env",
glob!["qt/bundle/PyOxidizer/**"]
],
// can't use ::Binary() here, as we're in a separate workspace
outputs: &[RustOutput::Data("bin", &with_exe(&output_bin))],
target: Some(target),
extra_args: &format!(
"--manifest-path={} --target-dir={} -p pyoxidizer",
"qt/bundle/PyOxidizer/Cargo.toml", "$builddir/bundle/rust"
),
release_override: Some(BuildProfile::Release),
},
)?;
Ok(())
}
struct BuildArtifacts {}
impl BuildAction for BuildArtifacts {
fn command(&self) -> &str {
"$runner build-artifacts $bundle_root $pyoxidizer_bin"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
build.add_inputs("pyoxidizer_bin", inputs![":bundle:pyoxidizer:bin"]);
build.add_inputs("", inputs![PRIMARY_VENV.label_as_target("")]);
build.add_inputs("", inputs![":bundle:add_wheels:qt6", glob!["qt/bundle/**"]]);
build.add_variable("bundle_root", "$builddir/bundle");
build.add_outputs_ext(
"pyo3_config",
vec!["bundle/artifacts/pyo3-build-config-file.txt"],
true,
);
}
fn check_output_timestamps(&self) -> bool {
true
}
}
fn build_artifacts(build: &mut Build) -> Result<()> {
build.add_action("bundle:artifacts", BuildArtifacts {})
}
struct BuildBundle {}
impl BuildAction for BuildBundle {
fn command(&self) -> &str {
"$runner build-bundle-binary"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
build.add_inputs("", inputs![":bundle:artifacts", glob!["qt/bundle/**"]]);
build.add_outputs(
"",
vec![RustOutput::Binary("anki").path(
Utf8Path::new("$builddir/bundle/rust"),
Some(
overriden_rust_target_triple()
.unwrap_or_else(|| Platform::current().as_rust_triple()),
),
// our pyoxidizer bin uses lto on the release profile
BuildProfile::Release,
)],
);
}
}
fn build_binary(build: &mut Build) -> Result<()> {
build.add_action("bundle:binary", BuildBundle {})
}
struct BuildDistFolder {
kind: DistKind,
deps: BuildInput,
}
impl BuildAction for BuildDistFolder {
fn command(&self) -> &str {
"$runner build-dist-folder $kind $out_folder "
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
build.add_inputs("", &self.deps);
build.add_variable("kind", self.kind.name());
let folder = match self.kind {
DistKind::Standard => "bundle/std",
};
build.add_outputs("out_folder", vec![folder]);
build.add_outputs("stamp", vec![format!("{folder}.stamp")]);
}
fn check_output_timestamps(&self) -> bool {
true
}
}
fn build_dist_folder(build: &mut Build, kind: DistKind) -> Result<()> {
let deps = inputs![":bundle:deps", ":bundle:binary", glob!["qt/bundle/**"]];
let group = match kind {
DistKind::Standard => "bundle:folder:std",
};
build.add_action(group, BuildDistFolder { kind, deps })
}
fn build_packages(build: &mut Build) -> Result<()> {
if cfg!(windows) {
build_windows_installers(build)
} else if cfg!(target_os = "macos") {
build_mac_app(build, DistKind::Standard)?;
build_dmgs(build)
} else {
build_tarball(build, DistKind::Standard)
}
}
struct BuildTarball {
kind: DistKind,
}
impl BuildAction for BuildTarball {
fn command(&self) -> &str {
"chmod -R a+r $folder && tar -I '$zstd' --transform $transform -cf $tarball -C $folder ."
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
let input_folder_name = self.kind.folder_name();
let input_folder_target = format!(":bundle:folder:{input_folder_name}");
let input_folder_path = format!("$builddir/bundle/{input_folder_name}");
let version = anki_version();
let qt = match self.kind {
DistKind::Standard => "qt6",
};
let output_folder_base = format!("anki-{version}-linux-{qt}");
let output_tarball = format!("bundle/package/{output_folder_base}.tar.zst");
build.add_inputs("", inputs![input_folder_target]);
build.add_variable("zstd", "zstd -c --long -T0 -18");
build.add_variable("transform", format!("s%^.%{output_folder_base}%S"));
build.add_variable("folder", input_folder_path);
build.add_outputs("tarball", vec![output_tarball]);
}
}
fn build_tarball(build: &mut Build, kind: DistKind) -> Result<()> {
let name = kind.folder_name();
build.add_action(format!("bundle:package:{name}"), BuildTarball { kind })
}
struct BuildWindowsInstallers {}
impl BuildAction for BuildWindowsInstallers {
fn command(&self) -> &str {
"cargo run -p makeexe --target-dir=out/rust -- $version $src_root $bundle_root $out"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
let version = anki_version();
let outputs = ["qt6"].iter().map(|qt| {
let output_base = format!("anki-{version}-windows-{qt}");
format!("bundle/package/{output_base}.exe")
});
build.add_inputs("", inputs![":bundle:folder:std"]);
build.add_variable("version", &version);
build.add_variable("bundle_root", "$builddir/bundle");
build.add_outputs("out", outputs);
}
}
fn build_windows_installers(build: &mut Build) -> Result<()> {
build.add_action("bundle:package", BuildWindowsInstallers {})
}
struct BuildMacApp {
kind: DistKind,
}
impl BuildAction for BuildMacApp {
fn command(&self) -> &str {
"cargo run -p makeapp --target-dir=out/rust -- build-app $version $kind $stamp"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
let folder_name = self.kind.folder_name();
build.add_inputs("", inputs![format!(":bundle:folder:{folder_name}")]);
build.add_variable("version", anki_version());
build.add_variable("kind", self.kind.name());
build.add_outputs("stamp", vec![format!("bundle/app/{folder_name}.stamp")]);
}
}
fn build_mac_app(build: &mut Build, kind: DistKind) -> Result<()> {
build.add_action(format!("bundle:app:{}", kind.name()), BuildMacApp { kind })
}
struct BuildDmgs {}
impl BuildAction for BuildDmgs {
fn command(&self) -> &str {
"cargo run -p makeapp --target-dir=out/rust -- build-dmgs $dmgs"
}
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
let version = anki_version();
let platform = if targetting_macos_arm() {
"apple"
} else {
"intel"
};
let qt = &["qt6"][..];
let dmgs = qt
.iter()
.map(|qt| format!("bundle/dmg/anki-{version}-mac-{platform}-{qt}.dmg"));
build.add_inputs("", inputs![":bundle:app"]);
build.add_outputs("dmgs", dmgs);
}
}
fn build_dmgs(build: &mut Build) -> Result<()> {
build.add_action("bundle:dmg", BuildDmgs {})
}

View file

@ -0,0 +1,60 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
#![allow(dead_code)]
#![allow(unused_imports)]
use std::env;
use anyhow::Result;
use ninja_gen::action::BuildAction;
use ninja_gen::archives::download_and_extract;
use ninja_gen::archives::empty_manifest;
use ninja_gen::archives::with_exe;
use ninja_gen::archives::OnlineArchive;
use ninja_gen::archives::Platform;
use ninja_gen::build::BuildProfile;
use ninja_gen::cargo::CargoBuild;
use ninja_gen::cargo::RustOutput;
use ninja_gen::command::RunCommand;
use ninja_gen::git::SyncSubmodule;
use ninja_gen::glob;
use ninja_gen::hashmap;
use ninja_gen::input::BuildInput;
use ninja_gen::inputs;
use ninja_gen::python::PythonEnvironment;
use ninja_gen::Build;
use ninja_gen::Utf8Path;
use crate::anki_version;
use crate::platform::overriden_python_target_platform;
use crate::platform::overriden_rust_target_triple;
pub fn setup_uv_universal(build: &mut Build) -> Result<()> {
build.add_action(
"launcher:uv_universal",
RunCommand {
command: "/usr/bin/lipo",
args: "-create -output $out $arm_bin $x86_bin",
inputs: hashmap! {
"arm_bin" => inputs![":extract:uv:bin"],
"x86_bin" => inputs![":extract:uv_mac_x86:bin"],
},
outputs: hashmap! {
"out" => vec!["launcher/uv"],
},
},
)
}
pub fn build_launcher(build: &mut Build) -> Result<()> {
setup_uv_universal(build)?;
download_and_extract(build, "nsis_plugins", NSIS_PLUGINS, empty_manifest())?;
Ok(())
}
const NSIS_PLUGINS: OnlineArchive = OnlineArchive {
url: "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2023-05-19/nsis.tar.zst",
sha256: "6133f730ece699de19714d0479c73bc848647d277e9cc80dda9b9ebe532b40a8",
};

View file

@ -2,7 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod aqt; mod aqt;
mod bundle; mod launcher;
mod platform; mod platform;
mod pylib; mod pylib;
mod python; mod python;
@ -13,13 +13,12 @@ use std::env;
use anyhow::Result; use anyhow::Result;
use aqt::build_and_check_aqt; use aqt::build_and_check_aqt;
use bundle::build_bundle; use launcher::build_launcher;
use ninja_gen::glob; use ninja_gen::glob;
use ninja_gen::inputs; use ninja_gen::inputs;
use ninja_gen::protobuf::check_proto; use ninja_gen::protobuf::check_proto;
use ninja_gen::protobuf::setup_protoc; use ninja_gen::protobuf::setup_protoc;
use ninja_gen::python::setup_uv; use ninja_gen::python::setup_uv;
use ninja_gen::python::setup_uv_universal;
use ninja_gen::Build; use ninja_gen::Build;
use platform::overriden_python_target_platform; use platform::overriden_python_target_platform;
use pylib::build_pylib; use pylib::build_pylib;
@ -62,8 +61,7 @@ fn main() -> Result<()> {
build_and_check_aqt(build)?; build_and_check_aqt(build)?;
if env::var("OFFLINE_BUILD").is_err() { if env::var("OFFLINE_BUILD").is_err() {
setup_uv_universal(build)?; build_launcher(build)?;
build_bundle(build)?;
} }
setup_sphinx(build)?; setup_sphinx(build)?;

View file

@ -12,7 +12,6 @@ use crate::archives::download_and_extract;
use crate::archives::with_exe; use crate::archives::with_exe;
use crate::archives::OnlineArchive; use crate::archives::OnlineArchive;
use crate::archives::Platform; use crate::archives::Platform;
use crate::command::RunCommand;
use crate::hash::simple_hash; use crate::hash::simple_hash;
use crate::input::BuildInput; use crate::input::BuildInput;
use crate::inputs; use crate::inputs;
@ -286,20 +285,3 @@ impl BuildAction for PythonTest {
true true
} }
} }
pub fn setup_uv_universal(build: &mut Build) -> Result<()> {
build.add_action(
"bundle:uv_universal",
RunCommand {
command: "/usr/bin/lipo",
args: "-create -output $out $arm_bin $x86_bin",
inputs: hashmap! {
"arm_bin" => inputs![":extract:uv:bin"],
"x86_bin" => inputs![":extract:uv_mac_x86:bin"],
},
outputs: hashmap! {
"out" => vec!["bundle/uv"],
},
},
)
}

View file

@ -1,62 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use std::fs;
use std::process::Command;
use camino::Utf8PathBuf;
use clap::Args;
use crate::run::run_command;
#[derive(Args, Debug)]
pub struct BuildArtifactsArgs {
bundle_root: Utf8PathBuf,
pyoxidizer_bin: String,
}
pub fn build_artifacts(args: BuildArtifactsArgs) {
// build.rs doesn't declare inputs from venv, so we need to force a rebuild to
// ensure changes to our libs/the venv get included
let artifacts = args.bundle_root.join("artifacts");
if artifacts.exists() {
fs::remove_dir_all(&artifacts).unwrap();
}
let bundle_root = args.bundle_root.canonicalize_utf8().unwrap();
let build_folder = bundle_root.join("build");
if build_folder.exists() {
fs::remove_dir_all(&build_folder).unwrap();
}
run_command(
Command::new(&args.pyoxidizer_bin)
.args([
"--system-rust",
"run-build-script",
"qt/bundle/build.rs",
"--var",
"venv",
"out/bundle/pyenv",
"--var",
"build",
build_folder.as_str(),
])
.env("CARGO_MANIFEST_DIR", "qt/bundle")
.env("CARGO_TARGET_DIR", "out/bundle/rust")
.env("PROFILE", "release")
.env("OUT_DIR", &artifacts)
.env("TARGET", env!("TARGET"))
.env("SDKROOT", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk")
.env("MACOSX_DEPLOYMENT_TARGET", macos_deployment_target())
.env("CARGO_BUILD_TARGET", env!("TARGET")),
);
}
pub fn macos_deployment_target() -> &'static str {
if env!("TARGET") == "x86_64-apple-darwin" {
"10.13.4"
} else {
"11"
}
}

View file

@ -1,53 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::process::Command;
use anki_process::CommandExt;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use super::artifacts::macos_deployment_target;
use crate::run::run_command;
pub fn build_bundle_binary() {
let mut features = String::from("build-mode-prebuilt-artifacts");
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
features.push_str(",global-allocator-jemalloc,allocator-jemalloc");
}
let mut command = Command::new("cargo");
command
.args([
"build",
"--manifest-path=qt/bundle/Cargo.toml",
"--target-dir=out/bundle/rust",
"--release",
"--no-default-features",
])
.arg(format!("--features={features}"))
.env(
"DEFAULT_PYTHON_CONFIG_RS",
// included in main.rs, so relative to qt/bundle/src
"../../../out/bundle/artifacts/",
)
.env(
"PYO3_CONFIG_FILE",
Utf8Path::new("out/bundle/artifacts/pyo3-build-config-file.txt")
.canonicalize_utf8()
.unwrap(),
)
.env("MACOSX_DEPLOYMENT_TARGET", macos_deployment_target())
.env("SDKROOT", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk")
.env("CARGO_BUILD_TARGET", env!("TARGET"));
if env!("TARGET") == "x86_64-apple-darwin" {
let xcode_path = Command::run_with_output(["xcode-select", "-p"]).unwrap();
let ld_classic = Utf8PathBuf::from(xcode_path.stdout.trim())
.join("Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic");
if ld_classic.exists() {
// work around XCode 15's default linker not supporting macOS 10.15-12.
command.env("RUSTFLAGS", format!("-Clink-arg=-fuse-ld={ld_classic}"));
}
}
run_command(&mut command);
}

View file

@ -1,156 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use std::fs;
use std::process::Command;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use clap::Args;
use clap::ValueEnum;
use crate::paths::absolute_msys_path;
use crate::paths::unix_path;
use crate::run::run_command;
#[derive(Clone, Copy, ValueEnum, Debug)]
enum DistKind {
Standard,
Alternate,
}
#[derive(Args, Debug)]
pub struct BuildDistFolderArgs {
kind: DistKind,
folder_root: Utf8PathBuf,
}
pub fn build_dist_folder(args: BuildDistFolderArgs) {
let BuildDistFolderArgs { kind, folder_root } = args;
fs::create_dir_all(&folder_root).unwrap();
// Start with Qt, as it's the largest, and we use --delete to ensure there are
// no stale files in lib/. Skipped on macOS as Qt is handled later.
if !cfg!(target_os = "macos") {
copy_qt_from_venv(kind, &folder_root);
}
clean_top_level_files(&folder_root);
copy_binary_and_pylibs(&folder_root);
if cfg!(target_os = "linux") {
copy_linux_extras(kind, &folder_root);
} else if cfg!(windows) {
copy_windows_extras(&folder_root);
}
fs::write(folder_root.with_extension("stamp"), b"").unwrap();
}
fn copy_qt_from_venv(kind: DistKind, folder_root: &Utf8Path) {
let python39 = if cfg!(windows) { "" } else { "python3.9/" };
let qt_root = match kind {
DistKind::Standard => {
folder_root.join(format!("../pyenv/lib/{python39}site-packages/PyQt6"))
}
DistKind::Alternate => {
folder_root.join(format!("../pyenv-qt5/lib/{python39}site-packages/PyQt5"))
}
};
let src_path = absolute_msys_path(&qt_root);
let lib_path = folder_root.join("lib");
fs::create_dir_all(&lib_path).unwrap();
let dst_path = with_slash(absolute_msys_path(&lib_path));
run_command(Command::new("rsync").args([
"-a",
"--delete",
"--exclude-from",
"qt/bundle/qt.exclude",
&src_path,
&dst_path,
]));
}
fn copy_linux_extras(kind: DistKind, folder_root: &Utf8Path) {
// add README, installer, etc
run_command(Command::new("rsync").args(["-a", "qt/bundle/lin/", &with_slash(folder_root)]));
// add extra IME plugins from download
let lib_path = folder_root.join("lib");
let src_path = folder_root
.join("../../extracted/linux_qt_plugins")
.join(match kind {
DistKind::Standard => "qt6",
DistKind::Alternate => "qt5",
});
let dst_path = lib_path.join(match kind {
DistKind::Standard => "PyQt6/Qt6/plugins",
DistKind::Alternate => "PyQt5/Qt5/plugins",
});
run_command(Command::new("rsync").args(["-a", &with_slash(src_path), &with_slash(dst_path)]));
}
fn copy_windows_extras(folder_root: &Utf8Path) {
run_command(Command::new("rsync").args([
"-a",
"out/extracted/win_amd64_audio/",
&with_slash(folder_root),
]));
}
fn clean_top_level_files(folder_root: &Utf8Path) {
let mut to_remove = vec![];
for entry in fs::read_dir(folder_root).unwrap() {
let entry = entry.unwrap();
if entry.file_name() == "lib" {
continue;
} else {
to_remove.push(entry.path());
}
}
for path in to_remove {
if path.is_dir() {
fs::remove_dir_all(path).unwrap()
} else {
fs::remove_file(path).unwrap()
}
}
}
fn with_slash<P>(path: P) -> String
where
P: AsRef<str>,
{
format!("{}/", path.as_ref())
}
fn copy_binary_and_pylibs(folder_root: &Utf8Path) {
let binary = folder_root
.join("../rust")
.join(env!("TARGET"))
.join("release")
.join(if cfg!(windows) { "anki.exe" } else { "anki" });
let extra_files = folder_root
.join("../build")
.join(env!("TARGET"))
.join("release/resources/extra_files");
run_command(Command::new("rsync").args([
"-a",
"--exclude",
"PyQt6",
// misleading, as it misses the GPL PyQt, and our Rust/JS
// dependencies
"--exclude",
"COPYING.txt",
&unix_path(&binary),
&with_slash(unix_path(&extra_files)),
&with_slash(unix_path(folder_root)),
]));
let google_py = if cfg!(windows) {
folder_root.join("../pyenv/lib/site-packages/google")
} else {
folder_root.join("../pyenv/lib/python3.9/site-packages/google")
};
run_command(Command::new("rsync").args([
"-a",
&unix_path(&google_py),
&with_slash(unix_path(&folder_root.join("lib"))),
]));
}

View file

@ -1,6 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
pub mod artifacts;
pub mod binary;
pub mod folder;

View file

@ -7,7 +7,6 @@
mod archive; mod archive;
mod build; mod build;
mod bundle;
mod paths; mod paths;
mod pyenv; mod pyenv;
mod rsync; mod rsync;
@ -19,11 +18,6 @@ use archive::archive_command;
use archive::ArchiveArgs; use archive::ArchiveArgs;
use build::run_build; use build::run_build;
use build::BuildArgs; use build::BuildArgs;
use bundle::artifacts::build_artifacts;
use bundle::artifacts::BuildArtifactsArgs;
use bundle::binary::build_bundle_binary;
use bundle::folder::build_dist_folder;
use bundle::folder::BuildDistFolderArgs;
use clap::Parser; use clap::Parser;
use clap::Subcommand; use clap::Subcommand;
use pyenv::setup_pyenv; use pyenv::setup_pyenv;
@ -48,9 +42,6 @@ enum Command {
Rsync(RsyncArgs), Rsync(RsyncArgs),
Run(RunArgs), Run(RunArgs),
Build(BuildArgs), Build(BuildArgs),
BuildArtifacts(BuildArtifactsArgs),
BuildBundleBinary,
BuildDistFolder(BuildDistFolderArgs),
#[clap(subcommand)] #[clap(subcommand)]
Archive(ArchiveArgs), Archive(ArchiveArgs),
} }
@ -62,9 +53,6 @@ fn main() -> Result<()> {
Command::Rsync(args) => rsync_files(args), Command::Rsync(args) => rsync_files(args),
Command::Yarn(args) => setup_yarn(args), Command::Yarn(args) => setup_yarn(args),
Command::Build(args) => run_build(args), Command::Build(args) => run_build(args),
Command::BuildArtifacts(args) => build_artifacts(args),
Command::BuildBundleBinary => build_bundle_binary(),
Command::BuildDistFolder(args) => build_dist_folder(args),
Command::Archive(args) => archive_command(args)?, Command::Archive(args) => archive_command(args)?,
}; };
Ok(()) Ok(())

View file

@ -16,8 +16,3 @@ pub fn absolute_msys_path(path: &Utf8Path) -> String {
// and \ -> / // and \ -> /
format!("/{drive}/{}", path[7..].replace('\\', "/")) format!("/{drive}/{}", path[7..].replace('\\', "/"))
} }
/// Converts backslashes to forward slashes
pub fn unix_path(path: &Utf8Path) -> String {
path.as_str().replace('\\', "/")
}

View file

@ -1,44 +0,0 @@
#!/bin/bash
set -e
# Define output path
OUTPUT_DIR="../../out/bundle"
APP_BUNDLE="$OUTPUT_DIR/Anki.app"
# Build rust binary in debug mode
cargo build -p launcher
(cd ../.. && ./ninja bundle:uv_universal)
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
# Remove existing app bundle
rm -rf "$APP_BUNDLE"
# Create app bundle structure
mkdir -p "$APP_BUNDLE/Contents/MacOS" "$APP_BUNDLE/Contents/Resources"
# Copy binaries
TARGET_DIR=${CARGO_TARGET_DIR:-target}
cp $TARGET_DIR/debug/launcher "$APP_BUNDLE/Contents/MacOS/"
cp "$OUTPUT_DIR/uv" "$APP_BUNDLE/Contents/MacOS/"
# Copy support files
cp launcher/Info.plist "$APP_BUNDLE/Contents/"
cp launcher/pyproject.toml "$APP_BUNDLE/Contents/Resources/"
# Codesign
for i in "$APP_BUNDLE/Contents/MacOS/uv" "$APP_BUNDLE/Contents/MacOS/launcher" "$APP_BUNDLE"; do
codesign --force -vvvv -o runtime -s "Developer ID Application:" \
--entitlements $c/desktop/anki/qt/bundle/mac/entitlements.python.xml \
"$i"
done
# Check
codesign -vvv "$APP_BUNDLE"
spctl -a "$APP_BUNDLE"
# Mark as quarantined
#xattr -w com.apple.quarantine "0181;$(date +%s);Safari;" "$APP_BUNDLE"

View file

@ -1,20 +0,0 @@
[package]
name = "makeapp"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish = false
rust-version.workspace = true
[dependencies]
anyhow.workspace = true
apple-bundles.workspace = true
camino.workspace = true
clap.workspace = true
glob.workspace = true
plist.workspace = true
serde.workspace = true
serde_json.workspace = true
simple-file-manifest.workspace = true
walkdir.workspace = true

View file

@ -1,43 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use std::process::Command;
use anyhow::bail;
use anyhow::Result;
use camino::Utf8Path;
use camino::Utf8PathBuf;
const CODESIGN_ARGS: &[&str] = &["-vvvv", "-o", "runtime", "-s", "Developer ID Application:"];
pub fn codesign_python_libs(bundle_dir: &Utf8PathBuf) -> Result<()> {
for entry in glob::glob(bundle_dir.join("Contents/MacOS/lib/**/*.so").as_str())? {
let entry = entry?;
let entry = Utf8PathBuf::from_path_buf(entry).unwrap();
codesign_file(&entry, &[])?;
}
codesign_file(&bundle_dir.join("Contents/MacOS/libankihelper.dylib"), &[])
}
pub fn codesign_app(bundle_dir: &Utf8PathBuf) -> Result<()> {
codesign_file(
bundle_dir,
&["--entitlements", "qt/bundle/mac/entitlements.python.xml"],
)
}
fn codesign_file(path: &Utf8Path, extra_args: &[&str]) -> Result<()> {
if env::var("ANKI_CODESIGN").is_ok() {
let status = Command::new("codesign")
.args(CODESIGN_ARGS)
.args(extra_args)
.arg(path.as_str())
.status()?;
if !status.success() {
bail!("codesign failed");
}
}
Ok(())
}

View file

@ -1,51 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::fs;
use std::process::Command;
use anyhow::Context;
use anyhow::Result;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use clap::Args;
use crate::notarize::wait_then_staple_app;
#[derive(Args)]
pub struct BuildDmgsArgs {
qt6_dmg: Utf8PathBuf,
qt5_dmg: Option<Utf8PathBuf>,
}
pub fn make_dmgs(args: BuildDmgsArgs) -> Result<()> {
let root = Utf8Path::new("out/bundle/app");
let mut variants = vec![("std", args.qt6_dmg)];
if let Some(alt) = args.qt5_dmg {
variants.push(("alt", alt));
}
for (variant, dmg) in variants {
let app = root.join(variant).join("Anki.app");
if std::env::var("ANKI_CODESIGN").is_ok() {
let uuid = fs::read_to_string(app.with_extension("uuid")).context("read uuid")?;
wait_then_staple_app(&app, uuid)?;
}
make_dmg(&app, &dmg)?;
}
Ok(())
}
fn make_dmg(app_folder: &Utf8Path, dmg: &Utf8Path) -> Result<()> {
assert!(
Command::new("qt/bundle/mac/dmg/build.sh")
.args([app_folder.parent().unwrap().as_str(), dmg.as_str()])
.status()
.context("dmg")?
.success(),
"dmg"
);
Ok(())
}

View file

@ -1,239 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
#![cfg(unix)]
//! Munge the output of PyOxidizer into a macOS app bundle, and combine it
//! with our other runtime dependencies.
mod codesign;
mod dmg;
mod notarize;
use std::env;
use std::fs;
use std::os::unix::prelude::PermissionsExt;
use std::process::Command;
use anyhow::bail;
use anyhow::Result;
use apple_bundles::MacOsApplicationBundleBuilder;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use clap::Parser;
use clap::Subcommand;
use clap::ValueEnum;
use codesign::codesign_app;
use codesign::codesign_python_libs;
use dmg::make_dmgs;
use dmg::BuildDmgsArgs;
use notarize::notarize_app;
use plist::Value;
use simple_file_manifest::FileEntry;
use walkdir::WalkDir;
#[derive(Clone, ValueEnum)]
enum DistKind {
Standard,
Alternate,
}
impl DistKind {
fn folder_name(&self) -> &'static str {
match self {
DistKind::Standard => "std",
DistKind::Alternate => "alt",
}
}
fn input_folder(&self) -> Utf8PathBuf {
Utf8Path::new("out/bundle").join(self.folder_name())
}
fn output_folder(&self) -> Utf8PathBuf {
Utf8Path::new("out/bundle/app")
.join(self.folder_name())
.join("Anki.app")
}
fn macos_min(&self) -> &str {
match self {
DistKind::Standard => {
if env::var("MAC_X86").is_ok() {
"11"
} else {
"12"
}
}
DistKind::Alternate => "10.13.4",
}
}
fn qt_repo(&self) -> &Utf8Path {
Utf8Path::new(match self {
DistKind::Standard => {
if cfg!(target_arch = "aarch64") && env::var("MAC_X86").is_err() {
"out/extracted/mac_arm_qt6"
} else {
"out/extracted/mac_amd_qt6"
}
}
DistKind::Alternate => "out/extracted/mac_amd_qt5",
})
}
}
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
BuildApp {
version: String,
kind: DistKind,
stamp: Utf8PathBuf,
},
BuildDmgs(BuildDmgsArgs),
}
fn main() -> Result<()> {
match Cli::parse().command {
Commands::BuildApp {
version,
kind,
stamp,
} => {
let plist = get_plist(&version);
make_app(kind, plist, &stamp)
}
Commands::BuildDmgs(args) => make_dmgs(args),
}
}
fn make_app(kind: DistKind, mut plist: plist::Dictionary, stamp: &Utf8Path) -> Result<()> {
let input_folder = kind.input_folder();
let output_folder = kind.output_folder();
let output_variant = output_folder.parent().unwrap();
if output_variant.exists() {
fs::remove_dir_all(output_variant)?;
}
fs::create_dir_all(&output_folder)?;
let mut builder = MacOsApplicationBundleBuilder::new("Anki")?;
plist.insert(
"LSMinimumSystemVersion".into(),
Value::from(kind.macos_min()),
);
builder.set_info_plist_from_dictionary(plist)?;
builder.add_file_resources("Assets.car", &include_bytes!("../icon/Assets.car")[..])?;
for entry in WalkDir::new(&input_folder)
.into_iter()
.map(Result::unwrap)
.filter(|e| !e.file_type().is_dir())
{
let path = entry.path();
let entry = FileEntry::try_from(path)?;
let relative_path = path.strip_prefix(&input_folder)?;
let path_str = relative_path.to_str().unwrap();
if path_str.contains("libankihelper") {
builder.add_file_macos("libankihelper.dylib", entry)?;
} else if path_str.contains("aqt/data")
|| path_str.contains("certifi")
|| path_str.contains("google/protobuf")
{
builder.add_file_resources(relative_path.strip_prefix("lib").unwrap(), entry)?;
} else {
if path_str.contains("__pycache__") {
continue;
}
builder.add_file_macos(relative_path, entry)?;
}
}
builder.files().materialize_files(&output_folder)?;
fix_rpath(output_folder.join("Contents/MacOS/anki"))?;
codesign_python_libs(&output_folder)?;
copy_in_audio(&output_folder)?;
copy_in_qt(&output_folder, kind)?;
codesign_app(&output_folder)?;
fixup_perms(&output_folder)?;
notarize_app(&output_folder)?;
fs::write(stamp, b"")?;
Ok(())
}
/// The bundle builder writes some files without world read/execute perms,
/// which prevents them from being opened by a non-admin user.
fn fixup_perms(dir: &Utf8Path) -> Result<()> {
let status = Command::new("find")
.arg(dir)
.args(["-not", "-perm", "-a=r", "-exec", "chmod", "a+r", "{}", ";"])
.status()?;
if !status.success() {
bail!("error setting perms");
}
fs::set_permissions(
dir.join("Contents/MacOS/anki"),
PermissionsExt::from_mode(0o755),
)?;
Ok(())
}
/// Copy everything at the provided path into the Contents/ folder of our app.
fn extend_app_contents(source: &Utf8Path, target_dir: &Utf8Path) -> Result<()> {
let status = Command::new("rsync")
.arg("-a")
.arg(format!("{}/", source.as_str()))
.arg(target_dir)
.status()?;
if !status.success() {
bail!("error syncing {source:?}");
}
Ok(())
}
fn copy_in_audio(bundle_dir: &Utf8Path) -> Result<()> {
println!("Copying in audio...");
let src_folder = Utf8Path::new(
if cfg!(target_arch = "aarch64") && env::var("MAC_X86").is_err() {
"out/extracted/mac_arm_audio"
} else {
"out/extracted/mac_amd_audio"
},
);
extend_app_contents(src_folder, &bundle_dir.join("Contents/Resources"))
}
fn copy_in_qt(bundle_dir: &Utf8Path, kind: DistKind) -> Result<()> {
println!("Copying in Qt...");
extend_app_contents(kind.qt_repo(), &bundle_dir.join("Contents"))
}
fn fix_rpath(exe_path: Utf8PathBuf) -> Result<()> {
let status = Command::new("install_name_tool")
.arg("-add_rpath")
.arg("@executable_path/../Frameworks")
.arg(exe_path.as_str())
.status()?;
assert!(status.success());
Ok(())
}
fn get_plist(anki_version: &str) -> plist::Dictionary {
let reader = std::io::Cursor::new(include_bytes!("Info.plist"));
let mut plist = Value::from_reader(reader)
.unwrap()
.into_dictionary()
.unwrap();
plist.insert(
"CFBundleShortVersionString".into(),
Value::from(anki_version),
);
plist
}

View file

@ -1,103 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use std::fs;
use std::process::Command;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use camino::Utf8Path;
use serde::Deserialize;
#[derive(Deserialize)]
struct NotarySubmitOutput {
id: String,
}
pub fn notarize_app(output_folder: &Utf8Path) -> Result<()> {
if env::var("ANKI_CODESIGN").is_err() {
return Ok(());
}
if env::var("ANKI_NO_NOTARIZE").is_ok() {
return Ok(());
}
let zip_file = output_folder.with_extension("zip");
assert!(
Command::new("ditto")
.args([
"-c",
"-k",
"--keepParent",
output_folder.as_str(),
zip_file.as_str(),
])
.status()
.unwrap()
.success(),
"zip build"
);
let output = Command::new("xcrun")
.args([
"notarytool",
"submit",
zip_file.as_str(),
"-f",
"json",
"-p",
"default",
])
.output()
.expect("notarytool");
if !output.status.success() {
panic!(
"notarytool submit failed: {} {}",
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout)
)
}
let output: NotarySubmitOutput = match serde_json::from_slice(&output.stdout) {
Ok(out) => out,
Err(err) => panic!(
"unable to parse notary output: {err} {} {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
),
};
let uuid_path = output_folder.with_extension("uuid");
fs::write(uuid_path, output.id).expect("write uuid");
Ok(())
}
#[derive(Deserialize)]
struct NotaryWaitOutput {
status: String,
}
pub fn wait_then_staple_app(app: &Utf8Path, uuid: String) -> Result<()> {
let output = Command::new("xcrun")
.args(["notarytool", "wait", &uuid, "-p", "default", "-f", "json"])
.output()
.context("notary wait")?;
let output: NotaryWaitOutput = serde_json::from_slice(&output.stdout)
.with_context(|| String::from_utf8_lossy(&output.stderr).to_string())?;
if output.status != "Accepted" {
bail!("unexpected status: {}", output.status);
}
assert!(
Command::new("xcrun")
.args(["stapler", "staple", app.as_str()])
.status()
.context("staple")?
.success(),
"staple"
);
// clean up temporary files
fs::remove_file(app.with_extension("zip")).context("app.zip")?;
fs::remove_file(app.with_extension("uuid")).context("app.uuid")?;
Ok(())
}

View file

@ -1,15 +0,0 @@
[package]
name = "makeexe"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
publish = false
rust-version.workspace = true
[dependencies]
anyhow.workspace = true
camino.workspace = true
clap.workspace = true
tugger-windows-codesign.workspace = true
walkdir.workspace = true

View file

@ -1,153 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::fs;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use clap::Parser;
use tugger_windows_codesign::CodeSigningCertificate;
use tugger_windows_codesign::SigntoolSign;
use tugger_windows_codesign::SystemStore;
use tugger_windows_codesign::TimestampServer;
use walkdir::WalkDir;
#[derive(Parser)]
struct Args {
version: String,
bundle_root: Utf8PathBuf,
qt6_setup_path: Utf8PathBuf,
}
fn main() -> Result<()> {
let args = Args::parse();
let src_win_folder = Utf8Path::new("qt/bundle/win");
let std_dist_folder = args.bundle_root.join("std");
// folder->installer
let dists = [(&std_dist_folder, &args.qt6_setup_path)];
for (folder, _) in dists {
fs::copy(
src_win_folder.join("anki-console.bat"),
folder.join("anki-console.bat"),
)
.context("anki-console")?;
}
println!("--- Build uninstaller");
build_installer(
&args.bundle_root,
&std_dist_folder,
&args.qt6_setup_path,
&args.version,
true,
)
.context("uninstaller")?;
// sign the anki.exe and uninstaller.exe in std
println!("--- Sign binaries");
codesign([
&std_dist_folder.join("anki.exe"),
&std_dist_folder.join("uninstall.exe"),
])?;
println!("--- Build manifest");
for (folder, _) in dists {
build_manifest(folder).context("manifest")?;
}
for (folder, installer) in dists {
println!("--- Build {}", installer);
build_installer(&args.bundle_root, folder, installer, &args.version, false)?;
}
println!("--- Sign installers");
codesign(dists.iter().map(|tup| tup.1))?;
Ok(())
}
fn build_installer(
bundle_root: &Utf8Path,
dist_folder: &Utf8Path,
installer: &Utf8Path,
version: &str,
uninstaller: bool,
) -> Result<()> {
let rendered_nsi = include_str!("../anki.template.nsi")
.replace("@@SRC@@", dist_folder.as_str())
.replace("@@INSTALLER@@", installer.as_str())
.replace("@@VERSION@@", version);
let rendered_nsi_path = bundle_root.join("anki.nsi");
fs::write(&rendered_nsi_path, rendered_nsi).context("anki.nsi")?;
fs::write(
bundle_root.join("fileassoc.nsh"),
include_str!("../fileassoc.nsh"),
)?;
fs::copy(
"out/extracted/nsis_plugins/nsProcess.dll",
bundle_root.join("nsProcess.dll"),
)?;
let mut cmd = Command::new("c:/program files (x86)/nsis/makensis.exe");
cmd.arg("-V3");
if uninstaller {
cmd.arg("-DWRITE_UNINSTALLER");
};
if option_env!("RELEASE").is_none() {
cmd.arg("-DNO_COMPRESS");
}
cmd.arg(rendered_nsi_path);
let status = cmd.status()?;
if !status.success() {
bail!("makensis failed");
}
Ok(())
}
fn codesign(paths: impl IntoIterator<Item = impl AsRef<Path>>) -> Result<()> {
if option_env!("ANKI_CODESIGN").is_none() {
return Ok(());
}
let cert = CodeSigningCertificate::Sha1Thumbprint(
SystemStore::My,
"dccfc6d312fc0432197bb7be951478e5866eebf8".into(),
);
let mut sign = SigntoolSign::new(cert);
sign.file_digest_algorithm("sha256")
.timestamp_server(TimestampServer::Rfc3161(
"http://time.certum.pl".into(),
"sha256".into(),
))
.verbose();
paths.into_iter().for_each(|path| {
sign.sign_file(path);
});
sign.run()
}
fn build_manifest(base_path: &Utf8Path) -> Result<()> {
let mut buf = vec![];
for entry in WalkDir::new(base_path)
.min_depth(1)
.sort_by_file_name()
.into_iter()
{
let entry = entry?;
let path = entry.path();
let relative_path = path.strip_prefix(base_path)?;
write!(
&mut buf,
"{}\r\n",
relative_path.to_str().context("relative_path utf8")?
)?;
}
fs::write(base_path.join("anki.install-manifest"), buf)?;
Ok(())
}

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

41
qt/launcher/mac/build.sh Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
set -e
# Define output path
OUTPUT_DIR="../../../out/launcher"
APP_LAUNCHER="$OUTPUT_DIR/Anki.app"
# Build rust binary in debug mode
cargo build -p launcher
(cd ../../.. && ./ninja launcher:uv_universal)
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
# Remove existing app launcher
rm -rf "$APP_LAUNCHER"
# Create app launcher structure
mkdir -p "$APP_LAUNCHER/Contents/MacOS" "$APP_LAUNCHER/Contents/Resources"
# Copy binaries
TARGET_DIR=${CARGO_TARGET_DIR:-target}
cp $TARGET_DIR/debug/launcher "$APP_LAUNCHER/Contents/MacOS/"
cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/"
# Copy support files
cp Info.plist "$APP_LAUNCHER/Contents/"
cp icon/Assets.car "$APP_LAUNCHER/Contents/Resources/"
cp ../pyproject.toml "$APP_LAUNCHER/Contents/Resources/"
# Codesign
for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do
codesign --force -vvvv -o runtime -s "Developer ID Application:" \
--entitlements entitlements.python.xml \
"$i"
done
# Check
codesign -vvv "$APP_LAUNCHER"
spctl -a "$APP_LAUNCHER"

View file

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View file

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -29,8 +29,6 @@ const NONSTANDARD_HEADER: &[&str] = &[
"./python/write_wheel.py", "./python/write_wheel.py",
"./qt/aqt/mpv.py", "./qt/aqt/mpv.py",
"./qt/aqt/winpaths.py", "./qt/aqt/winpaths.py",
"./qt/bundle/build.rs",
"./qt/bundle/src/main.rs",
]; ];
const IGNORED_FOLDERS: &[&str] = &[ const IGNORED_FOLDERS: &[&str] = &[
@ -38,7 +36,6 @@ const IGNORED_FOLDERS: &[&str] = &[
"./node_modules", "./node_modules",
"./qt/aqt/forms", "./qt/aqt/forms",
"./tools/workspace-hack", "./tools/workspace-hack",
"./qt/bundle/PyOxidizer",
"./target", "./target",
".mypy_cache", ".mypy_cache",
"./extra", "./extra",