mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00
Extend launcher to other platforms + more
- Switch to Qt 6.8 for repo default, as 6.7 depends on an older libwebp/tiff which is unavailable on newer installs - Drop tools/mac-x86, as we no longer need to test against Qt 5 - Add flags to cross compile wheels on Mac and Linux - Bump glibc target to 2_36, building on Debian Stable - Increase mpv timeout on macOS to allow for initial gatekeeper checks - Ship both arm64 and amd64 uv on Linux, with a bash stub to pick the appropriate arch.
This commit is contained in:
parent
cefb1a2997
commit
b32a972f1f
39 changed files with 2178 additions and 1123 deletions
|
@ -35,6 +35,9 @@
|
||||||
},
|
},
|
||||||
"rust-analyzer.cargo.buildScripts.enable": true,
|
"rust-analyzer.cargo.buildScripts.enable": true,
|
||||||
"python.analysis.typeCheckingMode": "off",
|
"python.analysis.typeCheckingMode": "off",
|
||||||
|
"python.analysis.exclude": [
|
||||||
|
"out/launcher/**"
|
||||||
|
],
|
||||||
"terminal.integrated.env.windows": {
|
"terminal.integrated.env.windows": {
|
||||||
"PATH": "c:\\msys64\\usr\\bin;${env:Path}"
|
"PATH": "c:\\msys64\\usr\\bin;${env:Path}"
|
||||||
}
|
}
|
||||||
|
|
98
Cargo.lock
generated
98
Cargo.lock
generated
|
@ -1936,6 +1936,20 @@ 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 = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067"
|
checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embed-resource"
|
||||||
|
version = "2.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"memchr",
|
||||||
|
"rustc_version",
|
||||||
|
"toml 0.8.21",
|
||||||
|
"vswhom",
|
||||||
|
"winreg 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
|
@ -3579,7 +3593,14 @@ dependencies = [
|
||||||
name = "launcher"
|
name = "launcher"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anki_io",
|
||||||
|
"anki_process",
|
||||||
|
"anyhow",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
|
"embed-resource",
|
||||||
|
"libc",
|
||||||
|
"libc-stdhandle",
|
||||||
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3594,6 +3615,16 @@ version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc-stdhandle"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dac2473dc28934c5e0b82250dab231c9d3b94160d91fe9ff483323b05797551"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdbus-sys"
|
name = "libdbus-sys"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -3906,7 +3937,7 @@ dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml 0.5.11",
|
||||||
"topological-sort",
|
"topological-sort",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"warp",
|
"warp",
|
||||||
|
@ -5400,7 +5431,7 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winreg",
|
"winreg 0.50.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5900,6 +5931,15 @@ dependencies = [
|
||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_tuple"
|
name = "serde_tuple"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -6589,11 +6629,26 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.9"
|
version = "0.6.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
|
@ -6602,10 +6657,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485"
|
checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
"toml_write",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_write"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "topological-sort"
|
name = "topological-sort"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -7020,6 +7084,26 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vswhom"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"vswhom-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vswhom-sys"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -7878,6 +7962,16 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wiremock"
|
name = "wiremock"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
|
|
|
@ -74,6 +74,7 @@ data-encoding = "2.6.0"
|
||||||
difflib = "0.4.0"
|
difflib = "0.4.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
|
embed-resource = "2.4"
|
||||||
envy = "0.4.2"
|
envy = "0.4.2"
|
||||||
flate2 = "1.0.34"
|
flate2 = "1.0.34"
|
||||||
fluent = "0.16.1"
|
fluent = "0.16.1"
|
||||||
|
@ -92,6 +93,8 @@ intl-memoizer = "0.5.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
junction = "1.2.0"
|
junction = "1.2.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
libc = "0.2"
|
||||||
|
libc-stdhandle = "0.1"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
|
@ -141,6 +144,7 @@ unic-ucd-category = "0.9.0"
|
||||||
unicode-normalization = "0.1.24"
|
unicode-normalization = "0.1.24"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
which = "5.0.0"
|
which = "5.0.0"
|
||||||
|
winapi = { version = "0.3", features = ["wincon"] }
|
||||||
wiremock = "0.6.2"
|
wiremock = "0.6.2"
|
||||||
xz2 = "0.1.7"
|
xz2 = "0.1.7"
|
||||||
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"] }
|
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"] }
|
||||||
|
|
|
@ -1,36 +1,20 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// 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
|
||||||
|
|
||||||
#![allow(dead_code)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ninja_gen::action::BuildAction;
|
|
||||||
use ninja_gen::archives::download_and_extract;
|
use ninja_gen::archives::download_and_extract;
|
||||||
use ninja_gen::archives::empty_manifest;
|
use ninja_gen::archives::empty_manifest;
|
||||||
use ninja_gen::archives::with_exe;
|
|
||||||
use ninja_gen::archives::OnlineArchive;
|
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::command::RunCommand;
|
||||||
use ninja_gen::git::SyncSubmodule;
|
|
||||||
use ninja_gen::glob;
|
|
||||||
use ninja_gen::hashmap;
|
use ninja_gen::hashmap;
|
||||||
use ninja_gen::input::BuildInput;
|
|
||||||
use ninja_gen::inputs;
|
use ninja_gen::inputs;
|
||||||
use ninja_gen::python::PythonEnvironment;
|
|
||||||
use ninja_gen::Build;
|
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<()> {
|
pub fn setup_uv_universal(build: &mut Build) -> Result<()> {
|
||||||
|
if !cfg!(target_arch = "aarch64") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"launcher:uv_universal",
|
"launcher:uv_universal",
|
||||||
RunCommand {
|
RunCommand {
|
||||||
|
|
|
@ -20,7 +20,7 @@ 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::Build;
|
use ninja_gen::Build;
|
||||||
use platform::overriden_python_target_platform;
|
use platform::overriden_python_venv_platform;
|
||||||
use pylib::build_pylib;
|
use pylib::build_pylib;
|
||||||
use pylib::check_pylib;
|
use pylib::check_pylib;
|
||||||
use python::check_python;
|
use python::check_python;
|
||||||
|
@ -50,7 +50,7 @@ fn main() -> Result<()> {
|
||||||
if env::var("OFFLINE_BUILD").is_err() {
|
if env::var("OFFLINE_BUILD").is_err() {
|
||||||
setup_uv(
|
setup_uv(
|
||||||
build,
|
build,
|
||||||
overriden_python_target_platform().unwrap_or(build.host_platform),
|
overriden_python_venv_platform().unwrap_or(build.host_platform),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
setup_venv(build)?;
|
setup_venv(build)?;
|
||||||
|
|
|
@ -7,18 +7,28 @@ use ninja_gen::archives::Platform;
|
||||||
|
|
||||||
/// Please see [`overriden_python_target_platform()`] for details.
|
/// Please see [`overriden_python_target_platform()`] for details.
|
||||||
pub fn overriden_rust_target_triple() -> Option<&'static str> {
|
pub fn overriden_rust_target_triple() -> Option<&'static str> {
|
||||||
overriden_python_target_platform().map(|p| p.as_rust_triple())
|
overriden_python_wheel_platform().map(|p| p.as_rust_triple())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Usually None to use the host architecture, except on Windows which
|
/// Usually None to use the host architecture, except on Windows which
|
||||||
/// always uses x86_64.
|
/// always uses x86_64, since WebEngine is unavailable for ARM64.
|
||||||
/// On a Mac, set MAC_X86 to build for x86_64 on Apple Silicon.
|
pub fn overriden_python_venv_platform() -> Option<Platform> {
|
||||||
pub fn overriden_python_target_platform() -> Option<Platform> {
|
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
Some(Platform::WindowsX64)
|
Some(Platform::WindowsX64)
|
||||||
} else if env::var("MAC_X86").is_ok() {
|
|
||||||
Some(Platform::MacX64)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [`overriden_python_venv_platform`], but:
|
||||||
|
/// If MAC_X86 is set, an X86 wheel will be built on macOS ARM.
|
||||||
|
/// If LIN_ARM64 is set, an ARM64 wheel will be built on Linux AMD64.
|
||||||
|
pub fn overriden_python_wheel_platform() -> Option<Platform> {
|
||||||
|
if env::var("MAC_X86").is_ok() {
|
||||||
|
Some(Platform::MacX64)
|
||||||
|
} else if env::var("LIN_ARM64").is_ok() {
|
||||||
|
Some(Platform::LinuxArm)
|
||||||
|
} else {
|
||||||
|
overriden_python_venv_platform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use ninja_gen::python::PythonTest;
|
||||||
use ninja_gen::Build;
|
use ninja_gen::Build;
|
||||||
|
|
||||||
use crate::anki_version;
|
use crate::anki_version;
|
||||||
use crate::platform::overriden_python_target_platform;
|
use crate::platform::overriden_python_wheel_platform;
|
||||||
use crate::python::BuildWheel;
|
use crate::python::BuildWheel;
|
||||||
use crate::python::GenPythonProto;
|
use crate::python::GenPythonProto;
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ pub fn build_pylib(build: &mut Build) -> Result<()> {
|
||||||
BuildWheel {
|
BuildWheel {
|
||||||
name: "anki",
|
name: "anki",
|
||||||
version: anki_version(),
|
version: anki_version(),
|
||||||
platform: overriden_python_target_platform().or(Some(build.host_platform)),
|
platform: overriden_python_wheel_platform().or(Some(build.host_platform)),
|
||||||
deps: inputs![
|
deps: inputs![
|
||||||
":pylib:anki",
|
":pylib:anki",
|
||||||
glob!("pylib/anki/**"),
|
glob!("pylib/anki/**"),
|
||||||
|
|
|
@ -113,8 +113,8 @@ impl BuildAction for BuildWheel {
|
||||||
// Calculate the wheel filename that uv will generate
|
// Calculate the wheel filename that uv will generate
|
||||||
let tag = if let Some(platform) = self.platform {
|
let tag = if let Some(platform) = self.platform {
|
||||||
let platform_tag = match platform {
|
let platform_tag = match platform {
|
||||||
Platform::LinuxX64 => "manylinux_2_35_x86_64",
|
Platform::LinuxX64 => "manylinux_2_36_x86_64",
|
||||||
Platform::LinuxArm => "manylinux_2_35_aarch64",
|
Platform::LinuxArm => "manylinux_2_36_aarch64",
|
||||||
Platform::MacX64 => "macosx_12_0_x86_64",
|
Platform::MacX64 => "macosx_12_0_x86_64",
|
||||||
Platform::MacArm => "macosx_12_0_arm64",
|
Platform::MacArm => "macosx_12_0_arm64",
|
||||||
Platform::WindowsX64 => "win_amd64",
|
Platform::WindowsX64 => "win_amd64",
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub fn setup_uv(build: &mut Build, platform: Platform) -> Result<()> {
|
||||||
build.add_dependency("uv_binary", uv_binary);
|
build.add_dependency("uv_binary", uv_binary);
|
||||||
|
|
||||||
// Our macOS packaging needs access to the x86 binary on ARM.
|
// Our macOS packaging needs access to the x86 binary on ARM.
|
||||||
|
if cfg!(target_arch = "aarch64") {
|
||||||
download_and_extract(
|
download_and_extract(
|
||||||
build,
|
build,
|
||||||
"uv_mac_x86",
|
"uv_mac_x86",
|
||||||
|
@ -95,6 +96,18 @@ pub fn setup_uv(build: &mut Build, platform: Platform) -> Result<()> {
|
||||||
with_exe("uv")
|
with_exe("uv")
|
||||||
] },
|
] },
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
// Our Linux packaging needs access to the ARM binary on x86
|
||||||
|
if cfg!(target_arch = "x86_64") {
|
||||||
|
download_and_extract(
|
||||||
|
build,
|
||||||
|
"uv_lin_arm",
|
||||||
|
uv_archive(Platform::LinuxArm),
|
||||||
|
hashmap! { "bin" => [
|
||||||
|
with_exe("uv")
|
||||||
|
] },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3905,6 +3905,15 @@
|
||||||
"license_file": null,
|
"license_file": null,
|
||||||
"description": "Derive Serialize and Deserialize that delegates to the underlying repr of a C-like enum."
|
"description": "Derive Serialize and Deserialize that delegates to the underlying repr of a C-like enum."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "serde_spanned",
|
||||||
|
"version": "0.6.9",
|
||||||
|
"authors": null,
|
||||||
|
"repository": "https://github.com/toml-rs/toml",
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"license_file": null,
|
||||||
|
"description": "Serde-compatible spanned Value"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "serde_tuple",
|
"name": "serde_tuple",
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
|
@ -4454,6 +4463,15 @@
|
||||||
"license_file": null,
|
"license_file": null,
|
||||||
"description": "Yet another format-preserving TOML parser."
|
"description": "Yet another format-preserving TOML parser."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "toml_write",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"authors": null,
|
||||||
|
"repository": "https://github.com/toml-rs/toml",
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"license_file": null,
|
||||||
|
"description": "A low-level interface for writing out TOML"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "tower",
|
"name": "tower",
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
|
|
2
ninja
2
ninja
|
@ -8,7 +8,7 @@ else
|
||||||
out="$BUILD_ROOT"
|
out="$BUILD_ROOT"
|
||||||
fi
|
fi
|
||||||
export CARGO_TARGET_DIR=$out/rust
|
export CARGO_TARGET_DIR=$out/rust
|
||||||
export RECONFIGURE_KEY="${MAC_X86};${SOURCEMAP};${HMR}"
|
export RECONFIGURE_KEY="${MAC_X86};${LIN_ARM64};${SOURCEMAP};${HMR}"
|
||||||
|
|
||||||
if [ "$SKIP_RUNNER_BUILD" = "1" ]; then
|
if [ "$SKIP_RUNNER_BUILD" = "1" ]; then
|
||||||
echo "Runner not rebuilt."
|
echo "Runner not rebuilt."
|
||||||
|
|
|
@ -177,7 +177,8 @@ class MPVBase:
|
||||||
startup.
|
startup.
|
||||||
"""
|
"""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while self.is_running() and time.time() < start + 10:
|
timeout = 60 if is_mac else 10
|
||||||
|
while self.is_running() and time.time() < start + timeout:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
if is_win:
|
if is_win:
|
||||||
# named pipe
|
# named pipe
|
||||||
|
|
1
qt/launcher/.gitignore
vendored
1
qt/launcher/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
release/pyproject.toml
|
|
|
@ -1,7 +1,26 @@
|
||||||
[package]
|
[package]
|
||||||
name = "launcher"
|
name = "launcher"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
edition = "2024"
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dirs = "5.0"
|
anki_io.workspace = true
|
||||||
|
anki_process.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
dirs.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi.workspace = true
|
||||||
|
libc.workspace = true
|
||||||
|
libc-stdhandle.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "build_win"
|
||||||
|
path = "src/bin/build_win.rs"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
|
embed-resource.workspace = true
|
||||||
|
|
8
qt/launcher/build.rs
Normal file
8
qt/launcher/build.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
fn main() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
embed_resource::compile("win/anki-manifest.rc", embed_resource::NONE);
|
||||||
|
}
|
||||||
|
}
|
30
qt/launcher/lin/anki
Normal file
30
qt/launcher/lin/anki
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Universal Anki launcher script
|
||||||
|
|
||||||
|
# Get the directory where this script is located
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Determine architecture
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64|amd64)
|
||||||
|
LAUNCHER="$SCRIPT_DIR/launcher.amd64"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
LAUNCHER="$SCRIPT_DIR/launcher.arm64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unsupported architecture: $ARCH"
|
||||||
|
echo "Supported architectures: x86_64, aarch64"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check if launcher exists
|
||||||
|
if [ ! -f "$LAUNCHER" ]; then
|
||||||
|
echo "Error: Launcher not found: $LAUNCHER"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute the appropriate launcher with all arguments
|
||||||
|
exec "$LAUNCHER" "$@"
|
66
qt/launcher/lin/build.sh
Executable file
66
qt/launcher/lin/build.sh
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Add Linux cross-compilation target
|
||||||
|
rustup target add aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
# Define output paths
|
||||||
|
OUTPUT_DIR="../../../out/launcher"
|
||||||
|
LAUNCHER_DIR="$OUTPUT_DIR/anki-launcher"
|
||||||
|
|
||||||
|
# Clean existing output directory
|
||||||
|
rm -rf "$LAUNCHER_DIR"
|
||||||
|
|
||||||
|
# Build binaries for both Linux architectures
|
||||||
|
cargo build -p launcher --release --target x86_64-unknown-linux-gnu
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||||
|
cargo build -p launcher --release --target aarch64-unknown-linux-gnu
|
||||||
|
(cd ../../.. && ./ninja extract:uv_lin_arm)
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p "$LAUNCHER_DIR"
|
||||||
|
|
||||||
|
# Copy binaries and support files
|
||||||
|
TARGET_DIR=${CARGO_TARGET_DIR:-../../../target}
|
||||||
|
|
||||||
|
# Copy launcher binaries with architecture suffixes
|
||||||
|
cp "$TARGET_DIR/x86_64-unknown-linux-gnu/release/launcher" "$LAUNCHER_DIR/launcher.amd64"
|
||||||
|
cp "$TARGET_DIR/aarch64-unknown-linux-gnu/release/launcher" "$LAUNCHER_DIR/launcher.arm64"
|
||||||
|
|
||||||
|
# Copy uv binaries with architecture suffixes
|
||||||
|
cp "../../../out/extracted/uv/uv" "$LAUNCHER_DIR/uv.amd64"
|
||||||
|
cp "../../../out/extracted/uv_lin_arm/uv" "$LAUNCHER_DIR/uv.arm64"
|
||||||
|
|
||||||
|
# Copy support files from lin directory
|
||||||
|
for file in README.md anki.1 anki.desktop anki.png anki.xml anki.xpm install.sh uninstall.sh anki; do
|
||||||
|
cp "$file" "$LAUNCHER_DIR/"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Copy additional files from parent directory
|
||||||
|
cp ../pyproject.toml "$LAUNCHER_DIR/"
|
||||||
|
cp ../../../.python-version "$LAUNCHER_DIR/"
|
||||||
|
|
||||||
|
# Set executable permissions
|
||||||
|
chmod +x \
|
||||||
|
"$LAUNCHER_DIR/anki" \
|
||||||
|
"$LAUNCHER_DIR/launcher.amd64" \
|
||||||
|
"$LAUNCHER_DIR/launcher.arm64" \
|
||||||
|
"$LAUNCHER_DIR/uv.amd64" \
|
||||||
|
"$LAUNCHER_DIR/uv.arm64" \
|
||||||
|
"$LAUNCHER_DIR/install.sh" \
|
||||||
|
"$LAUNCHER_DIR/uninstall.sh"
|
||||||
|
|
||||||
|
# Set proper permissions and create tarball
|
||||||
|
chmod -R a+r "$LAUNCHER_DIR"
|
||||||
|
|
||||||
|
# Create tarball using the same options as the Rust template
|
||||||
|
ZSTD="zstd -c --long -T0 -18"
|
||||||
|
TRANSFORM="s%^.%anki-launcher%S"
|
||||||
|
TARBALL="$OUTPUT_DIR/anki-launcher.tar.zst"
|
||||||
|
|
||||||
|
tar -I "$ZSTD" --transform "$TRANSFORM" -cf "$TARBALL" -C "$LAUNCHER_DIR" .
|
||||||
|
|
||||||
|
echo "Build complete:"
|
||||||
|
echo "Universal launcher: $LAUNCHER_DIR"
|
||||||
|
echo "Tarball: $TARBALL"
|
|
@ -13,7 +13,7 @@ fi
|
||||||
|
|
||||||
rm -rf "$PREFIX"/share/anki "$PREFIX"/bin/anki
|
rm -rf "$PREFIX"/share/anki "$PREFIX"/bin/anki
|
||||||
mkdir -p "$PREFIX"/share/anki
|
mkdir -p "$PREFIX"/share/anki
|
||||||
cp -av --no-preserve=owner,context -- * "$PREFIX"/share/anki/
|
cp -av --no-preserve=owner,context -- * .python-version "$PREFIX"/share/anki/
|
||||||
mkdir -p "$PREFIX"/bin
|
mkdir -p "$PREFIX"/bin
|
||||||
ln -sf "$PREFIX"/share/anki/anki "$PREFIX"/bin/anki
|
ln -sf "$PREFIX"/share/anki/anki "$PREFIX"/bin/anki
|
||||||
# fix a previous packaging issue where we created this as a file
|
# fix a previous packaging issue where we created this as a file
|
||||||
|
|
81
qt/launcher/release/pyproject.toml
Normal file
81
qt/launcher/release/pyproject.toml
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
[project]
|
||||||
|
name = "anki-release"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "A package to lock Anki's dependencies"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"anki==0.1.2",
|
||||||
|
"aqt==0.1.2",
|
||||||
|
"anki-audio==0.1.0 ; sys_platform == 'darwin' or sys_platform == 'win32'",
|
||||||
|
"attrs==25.3.0",
|
||||||
|
"beautifulsoup4==4.12.3",
|
||||||
|
"blinker==1.9.0",
|
||||||
|
"certifi==2025.4.26",
|
||||||
|
"charset-normalizer==3.4.2",
|
||||||
|
"click==8.1.8 ; python_full_version < '3.10'",
|
||||||
|
"click==8.2.1 ; python_full_version >= '3.10'",
|
||||||
|
"colorama==0.4.6 ; sys_platform == 'win32'",
|
||||||
|
"decorator==5.2.1",
|
||||||
|
"distro==1.9.0 ; sys_platform != 'darwin' and sys_platform != 'win32'",
|
||||||
|
"flask==3.1.1",
|
||||||
|
"flask-cors==6.0.0",
|
||||||
|
"idna==3.10",
|
||||||
|
"importlib-metadata==8.7.0 ; python_full_version < '3.10'",
|
||||||
|
"itsdangerous==2.2.0",
|
||||||
|
"jinja2==3.1.6",
|
||||||
|
"jsonschema==4.24.0",
|
||||||
|
"jsonschema-specifications==2025.4.1",
|
||||||
|
"markdown==3.8",
|
||||||
|
"markupsafe==3.0.2",
|
||||||
|
"mock==5.2.0",
|
||||||
|
"orjson==3.10.18",
|
||||||
|
"pip-system-certs==4.0",
|
||||||
|
"protobuf==6.31.1",
|
||||||
|
"psutil==7.0.0 ; sys_platform == 'win32'",
|
||||||
|
"pyqt6==6.7.1",
|
||||||
|
"pyqt6-qt6==6.7.3",
|
||||||
|
"pyqt6-sip==13.10.2",
|
||||||
|
"pyqt6-webengine==6.7.0",
|
||||||
|
"pyqt6-webengine-qt6==6.7.3",
|
||||||
|
"pyqt6-webenginesubwheel-qt6==6.7.3",
|
||||||
|
"pysocks==1.7.1",
|
||||||
|
"pywin32==310 ; sys_platform == 'win32'",
|
||||||
|
"referencing==0.36.2",
|
||||||
|
"requests==2.32.3",
|
||||||
|
"rpds-py==0.25.1",
|
||||||
|
"send2trash==1.8.3",
|
||||||
|
"soupsieve==2.7",
|
||||||
|
"types-click==7.1.8",
|
||||||
|
"types-decorator==5.2.0.20250324",
|
||||||
|
"types-flask==1.1.6",
|
||||||
|
"types-flask-cors==6.0.0.20250520",
|
||||||
|
"types-jinja2==2.11.9",
|
||||||
|
"types-markdown==3.8.0.20250415",
|
||||||
|
"types-markupsafe==1.1.10",
|
||||||
|
"types-orjson==3.6.2",
|
||||||
|
"types-protobuf==6.30.2.20250516",
|
||||||
|
"types-pywin32==310.0.0.20250516",
|
||||||
|
"types-requests==2.32.0.20250602",
|
||||||
|
"types-waitress==3.0.1.20241117",
|
||||||
|
"types-werkzeug==1.0.9",
|
||||||
|
"typing-extensions==4.14.0",
|
||||||
|
"urllib3==2.4.0",
|
||||||
|
"waitress==3.0.2",
|
||||||
|
"werkzeug==3.1.3",
|
||||||
|
"wrapt==1.17.2",
|
||||||
|
"zipp==3.23.0 ; python_full_version < '3.10'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
name = "testpypi"
|
||||||
|
url = "https://test.pypi.org/simple/"
|
||||||
|
publish-url = "https://test.pypi.org/legacy/"
|
||||||
|
explicit = true
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
# hatch throws an error if nothing is included
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
include = ["no-such-file"]
|
295
qt/launcher/src/bin/build_win.rs
Normal file
295
qt/launcher/src/bin/build_win.rs
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
// 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::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anki_io::copy_file;
|
||||||
|
use anki_io::create_dir_all;
|
||||||
|
use anki_io::remove_dir_all;
|
||||||
|
use anki_io::write_file;
|
||||||
|
use anki_process::CommandExt;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
const OUTPUT_DIR: &str = "../../../out/launcher";
|
||||||
|
const LAUNCHER_EXE_DIR: &str = "../../../out/launcher_exe";
|
||||||
|
const NSIS_DIR: &str = "../../../out/nsis";
|
||||||
|
const CARGO_TARGET_DIR: &str = "../../../out/rust";
|
||||||
|
const NSIS_PATH: &str = "C:\\Program Files (x86)\\NSIS\\makensis.exe";
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
println!("Building Windows launcher...");
|
||||||
|
|
||||||
|
let output_dir = PathBuf::from(OUTPUT_DIR);
|
||||||
|
let launcher_exe_dir = PathBuf::from(LAUNCHER_EXE_DIR);
|
||||||
|
let nsis_dir = PathBuf::from(NSIS_DIR);
|
||||||
|
|
||||||
|
setup_directories(&output_dir, &launcher_exe_dir, &nsis_dir)?;
|
||||||
|
build_launcher_binary()?;
|
||||||
|
extract_nsis_plugins()?;
|
||||||
|
copy_files(&output_dir)?;
|
||||||
|
sign_binaries(&output_dir)?;
|
||||||
|
copy_nsis_files(&nsis_dir)?;
|
||||||
|
build_uninstaller(&output_dir, &nsis_dir)?;
|
||||||
|
sign_file(&output_dir.join("uninstall.exe"))?;
|
||||||
|
generate_install_manifest(&output_dir)?;
|
||||||
|
build_installer(&output_dir, &nsis_dir)?;
|
||||||
|
sign_file(&PathBuf::from("../../../out/launcher_exe/anki-install.exe"))?;
|
||||||
|
|
||||||
|
println!("Build completed successfully!");
|
||||||
|
println!("Output directory: {}", output_dir.display());
|
||||||
|
println!("Installer: ../../../out/launcher_exe/anki-install.exe");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_directories(output_dir: &Path, launcher_exe_dir: &Path, nsis_dir: &Path) -> Result<()> {
|
||||||
|
println!("Setting up directories...");
|
||||||
|
|
||||||
|
// Remove existing output directories
|
||||||
|
if output_dir.exists() {
|
||||||
|
remove_dir_all(output_dir)?;
|
||||||
|
}
|
||||||
|
if launcher_exe_dir.exists() {
|
||||||
|
remove_dir_all(launcher_exe_dir)?;
|
||||||
|
}
|
||||||
|
if nsis_dir.exists() {
|
||||||
|
remove_dir_all(nsis_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output directories
|
||||||
|
create_dir_all(output_dir)?;
|
||||||
|
create_dir_all(launcher_exe_dir)?;
|
||||||
|
create_dir_all(nsis_dir)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_launcher_binary() -> Result<()> {
|
||||||
|
println!("Building launcher binary...");
|
||||||
|
|
||||||
|
env::set_var("CARGO_TARGET_DIR", CARGO_TARGET_DIR);
|
||||||
|
|
||||||
|
Command::new("cargo")
|
||||||
|
.args([
|
||||||
|
"build",
|
||||||
|
"-p",
|
||||||
|
"launcher",
|
||||||
|
"--release",
|
||||||
|
"--target",
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
])
|
||||||
|
.ensure_success()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_nsis_plugins() -> Result<()> {
|
||||||
|
println!("Extracting NSIS plugins...");
|
||||||
|
|
||||||
|
// Change to the anki root directory and run tools/ninja.bat
|
||||||
|
Command::new("cmd")
|
||||||
|
.args([
|
||||||
|
"/c",
|
||||||
|
"cd",
|
||||||
|
"/d",
|
||||||
|
"..\\..\\..\\",
|
||||||
|
"&&",
|
||||||
|
"tools\\ninja.bat",
|
||||||
|
"extract:nsis_plugins",
|
||||||
|
])
|
||||||
|
.ensure_success()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_files(output_dir: &Path) -> Result<()> {
|
||||||
|
println!("Copying binaries...");
|
||||||
|
|
||||||
|
// Copy launcher binary as anki.exe
|
||||||
|
let launcher_src =
|
||||||
|
PathBuf::from(CARGO_TARGET_DIR).join("x86_64-pc-windows-msvc/release/launcher.exe");
|
||||||
|
let launcher_dst = output_dir.join("anki.exe");
|
||||||
|
copy_file(&launcher_src, &launcher_dst)?;
|
||||||
|
|
||||||
|
// Copy uv.exe
|
||||||
|
let uv_src = PathBuf::from("../../../out/extracted/uv/uv.exe");
|
||||||
|
let uv_dst = output_dir.join("uv.exe");
|
||||||
|
copy_file(&uv_src, &uv_dst)?;
|
||||||
|
|
||||||
|
println!("Copying support files...");
|
||||||
|
|
||||||
|
// Copy pyproject.toml
|
||||||
|
copy_file("../pyproject.toml", output_dir.join("pyproject.toml"))?;
|
||||||
|
|
||||||
|
// Copy .python-version
|
||||||
|
copy_file(
|
||||||
|
"../../../.python-version",
|
||||||
|
output_dir.join(".python-version"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy anki-console.bat
|
||||||
|
copy_file("anki-console.bat", output_dir.join("anki-console.bat"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_binaries(output_dir: &Path) -> Result<()> {
|
||||||
|
sign_file(&output_dir.join("anki.exe"))?;
|
||||||
|
sign_file(&output_dir.join("uv.exe"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_file(file_path: &Path) -> Result<()> {
|
||||||
|
let codesign = env::var("CODESIGN").unwrap_or_default();
|
||||||
|
if codesign != "1" {
|
||||||
|
println!(
|
||||||
|
"Skipping code signing for {} (CODESIGN not set to 1)",
|
||||||
|
file_path.display()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let signtool_path = find_signtool()?;
|
||||||
|
println!("Signing {}...", file_path.display());
|
||||||
|
|
||||||
|
Command::new(&signtool_path)
|
||||||
|
.args([
|
||||||
|
"sign",
|
||||||
|
"/sha1",
|
||||||
|
"dccfc6d312fc0432197bb7be951478e5866eebf8",
|
||||||
|
"/fd",
|
||||||
|
"sha256",
|
||||||
|
"/tr",
|
||||||
|
"http://time.certum.pl",
|
||||||
|
"/td",
|
||||||
|
"sha256",
|
||||||
|
"/v",
|
||||||
|
])
|
||||||
|
.arg(file_path)
|
||||||
|
.ensure_success()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_signtool() -> Result<PathBuf> {
|
||||||
|
println!("Locating signtool.exe...");
|
||||||
|
|
||||||
|
let output = Command::new("where")
|
||||||
|
.args([
|
||||||
|
"/r",
|
||||||
|
"C:\\Program Files (x86)\\Windows Kits",
|
||||||
|
"signtool.exe",
|
||||||
|
])
|
||||||
|
.utf8_output()?;
|
||||||
|
|
||||||
|
// Find signtool.exe with "arm64" in the path (as per original batch logic)
|
||||||
|
for line in output.stdout.lines() {
|
||||||
|
if line.contains("\\arm64\\") {
|
||||||
|
let signtool_path = PathBuf::from(line.trim());
|
||||||
|
println!("Using signtool: {}", signtool_path.display());
|
||||||
|
return Ok(signtool_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!("Could not find signtool.exe with arm64 architecture");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_install_manifest(output_dir: &Path) -> Result<()> {
|
||||||
|
println!("Generating install manifest...");
|
||||||
|
|
||||||
|
let mut manifest_content = String::new();
|
||||||
|
let entries = anki_io::read_dir_files(output_dir)?;
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if let Some(file_name) = path.file_name() {
|
||||||
|
let file_name_str = file_name.to_string_lossy();
|
||||||
|
// Skip manifest file and uninstaller (can't delete itself)
|
||||||
|
if file_name_str != "anki.install-manifest" && file_name_str != "uninstall.exe" {
|
||||||
|
if let Ok(relative_path) = path.strip_prefix(output_dir) {
|
||||||
|
// Convert to Windows-style backslashes for NSIS
|
||||||
|
let windows_path = relative_path.display().to_string().replace('/', "\\");
|
||||||
|
// Use Windows line endings (\r\n) as expected by NSIS
|
||||||
|
manifest_content.push_str(&format!("{}\r\n", windows_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file(output_dir.join("anki.install-manifest"), manifest_content)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_nsis_files(nsis_dir: &Path) -> Result<()> {
|
||||||
|
println!("Copying NSIS support files...");
|
||||||
|
|
||||||
|
// Copy anki.template.nsi as anki.nsi
|
||||||
|
copy_file("anki.template.nsi", nsis_dir.join("anki.nsi"))?;
|
||||||
|
|
||||||
|
// Copy fileassoc.nsh
|
||||||
|
copy_file("fileassoc.nsh", nsis_dir.join("fileassoc.nsh"))?;
|
||||||
|
|
||||||
|
// Copy nsProcess.dll
|
||||||
|
copy_file(
|
||||||
|
"../../../out/extracted/nsis_plugins/nsProcess.dll",
|
||||||
|
nsis_dir.join("nsProcess.dll"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_uninstaller(output_dir: &Path, nsis_dir: &Path) -> Result<()> {
|
||||||
|
println!("Building uninstaller...");
|
||||||
|
|
||||||
|
let mut flags = vec!["-V3", "-DWRITE_UNINSTALLER"];
|
||||||
|
if env::var("NO_COMPRESS").unwrap_or_default() == "1" {
|
||||||
|
println!("NO_COMPRESS=1 detected, disabling compression");
|
||||||
|
flags.push("-DNO_COMPRESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_nsis(
|
||||||
|
&PathBuf::from("anki.nsi"),
|
||||||
|
&flags,
|
||||||
|
nsis_dir, // Run from nsis directory
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy uninstaller from nsis directory to output directory
|
||||||
|
copy_file(
|
||||||
|
nsis_dir.join("uninstall.exe"),
|
||||||
|
output_dir.join("uninstall.exe"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_installer(_output_dir: &Path, nsis_dir: &Path) -> Result<()> {
|
||||||
|
println!("Building installer...");
|
||||||
|
|
||||||
|
let mut flags = vec!["-V3"];
|
||||||
|
if env::var("NO_COMPRESS").unwrap_or_default() == "1" {
|
||||||
|
println!("NO_COMPRESS=1 detected, disabling compression");
|
||||||
|
flags.push("-DNO_COMPRESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_nsis(
|
||||||
|
&PathBuf::from("anki.nsi"),
|
||||||
|
&flags,
|
||||||
|
nsis_dir, // Run from nsis directory
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_nsis(script_path: &Path, flags: &[&str], working_dir: &Path) -> Result<()> {
|
||||||
|
let mut cmd = Command::new(NSIS_PATH);
|
||||||
|
cmd.args(flags).arg(script_path).current_dir(working_dir);
|
||||||
|
|
||||||
|
cmd.ensure_success()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,114 +1,117 @@
|
||||||
use std::os::unix::process::CommandExt;
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
|
use std::io::stdin;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::process::Stdio;
|
|
||||||
|
use anki_io::copy_file;
|
||||||
|
use anki_io::create_dir_all;
|
||||||
|
use anki_io::metadata;
|
||||||
|
use anki_io::remove_file;
|
||||||
|
use anki_io::write_file;
|
||||||
|
use anki_process::CommandExt;
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::platform::exec_anki;
|
||||||
|
use crate::platform::get_anki_binary_path;
|
||||||
|
use crate::platform::get_exe_and_resources_dirs;
|
||||||
|
use crate::platform::get_uv_binary_name;
|
||||||
|
use crate::platform::handle_first_launch;
|
||||||
|
use crate::platform::handle_terminal_launch;
|
||||||
|
use crate::platform::initial_terminal_setup;
|
||||||
|
use crate::platform::launch_anki_detached;
|
||||||
|
|
||||||
|
mod platform;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub show_console: bool,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let Some(uv_install_root) =
|
if let Err(e) = run() {
|
||||||
dirs::data_local_dir().map(|data_dir| data_dir.join("AnkiProgramFiles"))
|
eprintln!("Error: {:#}", e);
|
||||||
else {
|
eprintln!("Press enter to close...");
|
||||||
println!("Unable to determine data_dir");
|
let mut input = String::new();
|
||||||
|
let _ = stdin().read_line(&mut input);
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<()> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
initial_terminal_setup(&mut config);
|
||||||
|
|
||||||
|
let uv_install_root = dirs::data_local_dir()
|
||||||
|
.context("Unable to determine data_dir")?
|
||||||
|
.join("AnkiProgramFiles");
|
||||||
|
|
||||||
let sync_complete_marker = uv_install_root.join(".sync_complete");
|
let sync_complete_marker = uv_install_root.join(".sync_complete");
|
||||||
let exe_dir = std::env::current_exe()
|
let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?;
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned();
|
|
||||||
let resources_dir = exe_dir.parent().unwrap().join("Resources");
|
|
||||||
let dist_pyproject_path = resources_dir.join("pyproject.toml");
|
let dist_pyproject_path = resources_dir.join("pyproject.toml");
|
||||||
let user_pyproject_path = uv_install_root.join("pyproject.toml");
|
let user_pyproject_path = uv_install_root.join("pyproject.toml");
|
||||||
let dist_python_version_path = resources_dir.join(".python-version");
|
let dist_python_version_path = resources_dir.join(".python-version");
|
||||||
let user_python_version_path = uv_install_root.join(".python-version");
|
let user_python_version_path = uv_install_root.join(".python-version");
|
||||||
|
let uv_lock_path = uv_install_root.join("uv.lock");
|
||||||
|
let uv_path: std::path::PathBuf = exe_dir.join(get_uv_binary_name());
|
||||||
|
|
||||||
let pyproject_has_changed =
|
let pyproject_has_changed =
|
||||||
!user_pyproject_path.exists() || !sync_complete_marker.exists() || {
|
!user_pyproject_path.exists() || !sync_complete_marker.exists() || {
|
||||||
let pyproject_toml_time = std::fs::metadata(&user_pyproject_path)
|
let pyproject_toml_time = metadata(&user_pyproject_path)?
|
||||||
.unwrap()
|
|
||||||
.modified()
|
.modified()
|
||||||
.unwrap();
|
.context("Failed to get pyproject.toml modified time")?;
|
||||||
let sync_complete_time = std::fs::metadata(&sync_complete_marker)
|
let sync_complete_time = metadata(&sync_complete_marker)?
|
||||||
.unwrap()
|
|
||||||
.modified()
|
.modified()
|
||||||
.unwrap();
|
.context("Failed to get sync marker modified time")?;
|
||||||
pyproject_toml_time > sync_complete_time
|
Ok::<bool, anyhow::Error>(pyproject_toml_time > sync_complete_time)
|
||||||
};
|
}
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if !pyproject_has_changed {
|
||||||
|
// If venv is already up to date, exec as normal
|
||||||
|
let anki_bin = get_anki_binary_path(&uv_install_root);
|
||||||
|
exec_anki(&anki_bin, &config)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// we'll need to launch uv; reinvoke ourselves in a terminal so the user can see
|
// we'll need to launch uv; reinvoke ourselves in a terminal so the user can see
|
||||||
if pyproject_has_changed {
|
handle_terminal_launch()?;
|
||||||
let stdout_is_terminal = std::io::IsTerminal::is_terminal(&std::io::stdout());
|
|
||||||
if stdout_is_terminal {
|
|
||||||
print!("\x1B[2J\x1B[H"); // Clear screen and move cursor to top
|
|
||||||
println!("\x1B[1mPreparing to start Anki...\x1B[0m\n");
|
|
||||||
} else {
|
|
||||||
// If launched from GUI, relaunch in Terminal.app
|
|
||||||
let current_exe = std::env::current_exe().unwrap();
|
|
||||||
Command::new("open")
|
|
||||||
.args(["-a", "Terminal"])
|
|
||||||
.arg(current_exe)
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pyproject_has_changed {
|
// Create install directory and copy project files in
|
||||||
let uv_path: std::path::PathBuf = exe_dir.join("uv");
|
create_dir_all(&uv_install_root)?;
|
||||||
|
|
||||||
// Create install directory and copy pyproject.toml in if missing
|
|
||||||
std::fs::create_dir_all(&uv_install_root).unwrap();
|
|
||||||
if !user_pyproject_path.exists() {
|
if !user_pyproject_path.exists() {
|
||||||
std::fs::copy(&dist_pyproject_path, &user_pyproject_path).unwrap();
|
copy_file(&dist_pyproject_path, &user_pyproject_path)?;
|
||||||
std::fs::copy(&dist_python_version_path, &user_python_version_path).unwrap();
|
copy_file(&dist_python_version_path, &user_python_version_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove sync marker before attempting sync
|
// Remove sync marker before attempting sync
|
||||||
let _ = std::fs::remove_file(&sync_complete_marker);
|
let _ = remove_file(&sync_complete_marker);
|
||||||
|
|
||||||
// Sync the venv
|
// Sync the venv
|
||||||
let sync_result = Command::new(&uv_path)
|
if let Err(e) = Command::new(&uv_path)
|
||||||
.current_dir(&uv_install_root)
|
.current_dir(&uv_install_root)
|
||||||
.args(["sync"])
|
.args(["sync", "--refresh"])
|
||||||
.status()
|
.ensure_success()
|
||||||
.unwrap();
|
{
|
||||||
|
// If sync fails due to things like a missing wheel on pypi,
|
||||||
if !sync_result.success() {
|
// we need to remove the lockfile or uv will cache the bad result.
|
||||||
println!("uv sync failed");
|
let _ = remove_file(&uv_lock_path);
|
||||||
println!("Press enter to close");
|
return Err(e.into());
|
||||||
let mut input = String::new();
|
|
||||||
let _ = std::io::stdin().read_line(&mut input);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write marker file to indicate successful sync
|
// Write marker file to indicate successful sync
|
||||||
std::fs::write(&sync_complete_marker, "").unwrap();
|
write_file(&sync_complete_marker, "")?;
|
||||||
}
|
|
||||||
|
|
||||||
// invoke anki from the synced venv
|
// First launch
|
||||||
if pyproject_has_changed {
|
let anki_bin = get_anki_binary_path(&uv_install_root);
|
||||||
// Pre-validate by running --version to trigger any Gatekeeper checks
|
handle_first_launch(&anki_bin)?;
|
||||||
let anki_bin = uv_install_root.join(".venv/bin/anki");
|
|
||||||
println!("\n\x1B[1mThis may take a few minutes. Please wait...\x1B[0m");
|
|
||||||
let _ = Command::new(&anki_bin)
|
|
||||||
.env("ANKI_FIRST_RUN", "1")
|
|
||||||
.arg("--version")
|
|
||||||
.status();
|
|
||||||
|
|
||||||
// Then launch the binary as detached subprocess so the terminal can close
|
// Then launch the binary as detached subprocess so the terminal can close
|
||||||
let child = Command::new(&anki_bin)
|
launch_anki_detached(&anki_bin, &config)?;
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(Stdio::null())
|
Ok(())
|
||||||
.stderr(Stdio::null())
|
|
||||||
.process_group(0)
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
std::mem::forget(child);
|
|
||||||
} else {
|
|
||||||
// If venv already existed, exec as normal
|
|
||||||
println!(
|
|
||||||
"Anki return code: {:?}",
|
|
||||||
Command::new(uv_install_root.join(".venv/bin/anki")).exec()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
80
qt/launcher/src/platform/mac.rs
Normal file
80
qt/launcher/src/platform/mac.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anki_process::CommandExt as AnkiCommandExt;
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
// Re-export Unix functions that macOS uses
|
||||||
|
pub use super::unix::{
|
||||||
|
exec_anki,
|
||||||
|
get_anki_binary_path,
|
||||||
|
initial_terminal_setup,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn launch_anki_detached(anki_bin: &std::path::Path, _config: &crate::Config) -> Result<()> {
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
let child = Command::new(anki_bin)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.process_group(0)
|
||||||
|
.ensure_spawn()?;
|
||||||
|
std::mem::forget(child);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_terminal_launch() -> Result<()> {
|
||||||
|
let stdout_is_terminal = std::io::IsTerminal::is_terminal(&std::io::stdout());
|
||||||
|
if stdout_is_terminal {
|
||||||
|
print!("\x1B[2J\x1B[H"); // Clear screen and move cursor to top
|
||||||
|
println!("\x1B[1mPreparing to start Anki...\x1B[0m\n");
|
||||||
|
} else {
|
||||||
|
// If launched from GUI, relaunch in Terminal.app
|
||||||
|
relaunch_in_terminal()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relaunch_in_terminal() -> Result<()> {
|
||||||
|
let current_exe = std::env::current_exe().context("Failed to get current executable path")?;
|
||||||
|
Command::new("open")
|
||||||
|
.args(["-a", "Terminal"])
|
||||||
|
.arg(current_exe)
|
||||||
|
.ensure_spawn()?;
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_first_launch(anki_bin: &std::path::Path) -> Result<()> {
|
||||||
|
// Pre-validate by running --version to trigger any Gatekeeper checks
|
||||||
|
println!("\n\x1B[1mThis may take a few minutes. Please wait...\x1B[0m");
|
||||||
|
let _ = Command::new(anki_bin)
|
||||||
|
.env("ANKI_FIRST_RUN", "1")
|
||||||
|
.arg("--version")
|
||||||
|
.ensure_success();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exe_and_resources_dirs() -> Result<(std::path::PathBuf, std::path::PathBuf)> {
|
||||||
|
let exe_dir = std::env::current_exe()
|
||||||
|
.context("Failed to get current executable path")?
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get executable directory")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let resources_dir = exe_dir
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get parent directory")?
|
||||||
|
.join("Resources");
|
||||||
|
|
||||||
|
Ok((exe_dir, resources_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uv_binary_name() -> &'static str {
|
||||||
|
// macOS uses standard uv binary name
|
||||||
|
"uv"
|
||||||
|
}
|
18
qt/launcher/src/platform/mod.rs
Normal file
18
qt/launcher/src/platform/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
mod unix;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod mac;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub use mac::*;
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
pub use unix::*;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub use windows::*;
|
74
qt/launcher/src/platform/unix.rs
Normal file
74
qt/launcher/src/platform/unix.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anki_process::CommandExt as AnkiCommandExt;
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
pub fn initial_terminal_setup(_config: &mut Config) {
|
||||||
|
// No special terminal setup needed on Unix
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_terminal_launch() -> Result<()> {
|
||||||
|
print!("\x1B[2J\x1B[H"); // Clear screen and move cursor to top
|
||||||
|
println!("\x1B[1mPreparing to start Anki...\x1B[0m\n");
|
||||||
|
// Skip terminal relaunch on non-macOS Unix systems as we don't know which
|
||||||
|
// terminal is installed
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_anki_binary_path(uv_install_root: &std::path::Path) -> PathBuf {
|
||||||
|
uv_install_root.join(".venv/bin/anki")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_anki_detached(anki_bin: &std::path::Path, config: &Config) -> Result<()> {
|
||||||
|
// On non-macOS Unix systems, we don't need to detach since we never spawned a
|
||||||
|
// terminal
|
||||||
|
exec_anki(anki_bin, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_first_launch(_anki_bin: &std::path::Path) -> Result<()> {
|
||||||
|
// No special first launch handling needed for generic Unix systems
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec_anki(anki_bin: &std::path::Path, _config: &Config) -> Result<()> {
|
||||||
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
|
Command::new(anki_bin)
|
||||||
|
.args(args)
|
||||||
|
.ensure_exec()
|
||||||
|
.map_err(anyhow::Error::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exe_and_resources_dirs() -> Result<(PathBuf, PathBuf)> {
|
||||||
|
let exe_dir = std::env::current_exe()
|
||||||
|
.context("Failed to get current executable path")?
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get executable directory")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
// On generic Unix systems, assume resources are in the same directory as
|
||||||
|
// executable
|
||||||
|
let resources_dir = exe_dir.clone();
|
||||||
|
|
||||||
|
Ok((exe_dir, resources_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uv_binary_name() -> &'static str {
|
||||||
|
// Use architecture-specific uv binary for non-Mac Unix systems
|
||||||
|
if cfg!(target_arch = "x86_64") {
|
||||||
|
"uv.amd64"
|
||||||
|
} else if cfg!(target_arch = "aarch64") {
|
||||||
|
"uv.arm64"
|
||||||
|
} else {
|
||||||
|
// Fallback to generic uv for other architectures
|
||||||
|
"uv"
|
||||||
|
}
|
||||||
|
}
|
118
qt/launcher/src/platform/windows.rs
Normal file
118
qt/launcher/src/platform/windows.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anki_process::CommandExt;
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
pub fn handle_terminal_launch() -> Result<()> {
|
||||||
|
// uv will do this itself
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If parent process has a console (eg cmd.exe), redirect our output there.
|
||||||
|
/// Sets config.show_console to true if successfully attached to console.
|
||||||
|
pub fn initial_terminal_setup(config: &mut Config) {
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use libc_stdhandle::*;
|
||||||
|
use winapi::um::wincon;
|
||||||
|
|
||||||
|
let console_attached = unsafe { wincon::AttachConsole(wincon::ATTACH_PARENT_PROCESS) };
|
||||||
|
if console_attached == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let conin = CString::new("CONIN$").unwrap();
|
||||||
|
let conout = CString::new("CONOUT$").unwrap();
|
||||||
|
let r = CString::new("r").unwrap();
|
||||||
|
let w = CString::new("w").unwrap();
|
||||||
|
|
||||||
|
// Python uses the CRT for I/O, and it requires the descriptors are reopened.
|
||||||
|
unsafe {
|
||||||
|
libc::freopen(conin.as_ptr(), r.as_ptr(), stdin());
|
||||||
|
libc::freopen(conout.as_ptr(), w.as_ptr(), stdout());
|
||||||
|
libc::freopen(conout.as_ptr(), w.as_ptr(), stderr());
|
||||||
|
}
|
||||||
|
|
||||||
|
config.show_console = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_anki_binary_path(uv_install_root: &std::path::Path) -> std::path::PathBuf {
|
||||||
|
uv_install_root.join(".venv/Scripts/anki.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_python_command(
|
||||||
|
anki_bin: &std::path::Path,
|
||||||
|
args: &[String],
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Command> {
|
||||||
|
let venv_dir = anki_bin
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get venv Scripts directory")?
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get venv directory")?;
|
||||||
|
|
||||||
|
// Use python.exe if show_console is true, otherwise pythonw.exe
|
||||||
|
let python_exe = if config.show_console {
|
||||||
|
venv_dir.join("Scripts/python.exe")
|
||||||
|
} else {
|
||||||
|
venv_dir.join("Scripts/pythonw.exe")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cmd = Command::new(python_exe);
|
||||||
|
cmd.args(["-c", "import aqt; aqt.run()"]);
|
||||||
|
cmd.args(args);
|
||||||
|
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_anki_detached(anki_bin: &std::path::Path, config: &Config) -> Result<()> {
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
|
||||||
|
const DETACHED_PROCESS: u32 = 0x00000008;
|
||||||
|
|
||||||
|
let mut cmd = build_python_command(anki_bin, &[], config)?;
|
||||||
|
cmd.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS)
|
||||||
|
.ensure_spawn()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_first_launch(_anki_bin: &std::path::Path) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec_anki(anki_bin: &std::path::Path, config: &Config) -> Result<()> {
|
||||||
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
|
let mut cmd = build_python_command(anki_bin, &args, config)?;
|
||||||
|
cmd.ensure_success()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exe_and_resources_dirs() -> Result<(PathBuf, PathBuf)> {
|
||||||
|
let exe_dir = std::env::current_exe()
|
||||||
|
.context("Failed to get current executable path")?
|
||||||
|
.parent()
|
||||||
|
.context("Failed to get executable directory")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
// On Windows, resources dir is the same as exe_dir
|
||||||
|
let resources_dir = exe_dir.clone();
|
||||||
|
|
||||||
|
Ok((exe_dir, resources_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_uv_binary_name() -> &'static str {
|
||||||
|
// Windows uses standard uv binary name
|
||||||
|
"uv.exe"
|
||||||
|
}
|
|
@ -1,9 +1,25 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity type="win32" name="Anki" version="1.0.0.0" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<windowsSettings>
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||||
</windowsSettings>
|
</windowsSettings>
|
||||||
</application>
|
</application>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|
|
@ -23,8 +23,8 @@ Name "Anki"
|
||||||
|
|
||||||
Unicode true
|
Unicode true
|
||||||
|
|
||||||
; The file to write (make relative to repo root instead of out/bundle)
|
; The file to write (relative to nsis directory)
|
||||||
OutFile "..\..\@@INSTALLER@@"
|
OutFile "..\launcher_exe\anki-install.exe"
|
||||||
|
|
||||||
; Non elevated
|
; Non elevated
|
||||||
RequestExecutionLevel user
|
RequestExecutionLevel user
|
||||||
|
@ -62,7 +62,7 @@ Function .onInit
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
!ifdef WRITE_UNINSTALLER
|
!ifdef WRITE_UNINSTALLER
|
||||||
!uninstfinalize 'copy "%1" "std\uninstall.exe"'
|
!uninstfinalize 'copy "%1" "uninstall.exe"'
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
;--------------------------------
|
;--------------------------------
|
||||||
|
@ -191,7 +191,7 @@ Section ""
|
||||||
|
|
||||||
; Add files to installer
|
; Add files to installer
|
||||||
!ifndef WRITE_UNINSTALLER
|
!ifndef WRITE_UNINSTALLER
|
||||||
File /r ..\..\@@SRC@@\*.*
|
File /r ..\launcher\*.*
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!insertmacro APP_ASSOCIATE_HKCU "apkg" "anki.apkg" \
|
!insertmacro APP_ASSOCIATE_HKCU "apkg" "anki.apkg" \
|
||||||
|
@ -213,8 +213,8 @@ Section ""
|
||||||
WriteRegStr HKCU Software\Anki "Install_Dir64" "$INSTDIR"
|
WriteRegStr HKCU Software\Anki "Install_Dir64" "$INSTDIR"
|
||||||
|
|
||||||
; Write the uninstall keys for Windows
|
; Write the uninstall keys for Windows
|
||||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayName" "Anki"
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayName" "Anki Launcher"
|
||||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "@@VERSION@@"
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "1.0.0"
|
||||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
|
||||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoModify" 1
|
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoModify" 1
|
||||||
|
@ -252,9 +252,15 @@ Section "Uninstall"
|
||||||
!insertmacro APP_UNASSOCIATE_HKCU "ankiaddon" "anki.ankiaddon"
|
!insertmacro APP_UNASSOCIATE_HKCU "ankiaddon" "anki.ankiaddon"
|
||||||
!insertmacro UPDATEFILEASSOC
|
!insertmacro UPDATEFILEASSOC
|
||||||
|
|
||||||
|
; Schedule uninstaller for deletion on reboot
|
||||||
|
Delete /REBOOTOK "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
; try to remove top level folder if empty
|
; try to remove top level folder if empty
|
||||||
RMDir "$INSTDIR"
|
RMDir "$INSTDIR"
|
||||||
|
|
||||||
|
; Remove AnkiProgramData folder created during runtime
|
||||||
|
RMDir /r "$LOCALAPPDATA\AnkiProgramFiles"
|
||||||
|
|
||||||
; Remove registry keys
|
; Remove registry keys
|
||||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki"
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki"
|
||||||
DeleteRegKey HKCU Software\Anki
|
DeleteRegKey HKCU Software\Anki
|
||||||
|
|
5
qt/launcher/win/build.bat
Normal file
5
qt/launcher/win/build.bat
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
set CODESIGN=1
|
||||||
|
REM set NO_COMPRESS=1
|
||||||
|
cargo run --bin build_win
|
|
@ -37,14 +37,14 @@ qt66 = [
|
||||||
"pyqt6-webengine-qt6==6.6.2",
|
"pyqt6-webengine-qt6==6.6.2",
|
||||||
"pyqt6_sip==13.6.0",
|
"pyqt6_sip==13.6.0",
|
||||||
]
|
]
|
||||||
qt = [
|
qt67 = [
|
||||||
"pyqt6==6.7.1",
|
"pyqt6==6.7.1",
|
||||||
"pyqt6-qt6==6.7.3",
|
"pyqt6-qt6==6.7.3",
|
||||||
"pyqt6-webengine==6.7.0",
|
"pyqt6-webengine==6.7.0",
|
||||||
"pyqt6-webengine-qt6==6.7.3",
|
"pyqt6-webengine-qt6==6.7.3",
|
||||||
"pyqt6_sip==13.10.2",
|
"pyqt6_sip==13.10.2",
|
||||||
]
|
]
|
||||||
qt68 = [
|
qt = [
|
||||||
"pyqt6==6.8.0",
|
"pyqt6==6.8.0",
|
||||||
"pyqt6-qt6==6.8.1",
|
"pyqt6-qt6==6.8.1",
|
||||||
"pyqt6-webengine==6.8.0",
|
"pyqt6-webengine==6.8.0",
|
||||||
|
@ -57,7 +57,7 @@ conflicts = [
|
||||||
[
|
[
|
||||||
{ extra = "qt" },
|
{ extra = "qt" },
|
||||||
{ extra = "qt66" },
|
{ extra = "qt66" },
|
||||||
{ extra = "qt68" },
|
{ extra = "qt67" },
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
|
|
||||||
export UV_PUBLISH_TOKEN=$(pass show w/pypi-api-test)
|
export UV_PUBLISH_TOKEN=$(pass show w/pypi-api-test)
|
||||||
|
|
||||||
# Get the project root (two levels up from qt/bundle)
|
# Get the project root (two levels up from qt/release)
|
||||||
PROJ_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
PROJ_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
|
||||||
# Use extracted uv binary
|
# Use extracted uv binary
|
||||||
UV="$PROJ_ROOT/out/extracted/uv/uv"
|
UV="$PROJ_ROOT/out/extracted/uv/uv"
|
||||||
|
|
||||||
cd release
|
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
"$UV" build --wheel
|
"$UV" build --wheel
|
||||||
"$UV" publish --index testpypi
|
"$UV" publish --index testpypi
|
80
qt/release/pyproject.toml
Normal file
80
qt/release/pyproject.toml
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
[project]
|
||||||
|
name = "anki-release"
|
||||||
|
version = "0.1.3"
|
||||||
|
description = "A package to lock Anki's dependencies"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"anki==0.1.2",
|
||||||
|
"aqt==0.1.2",
|
||||||
|
"anki-audio==0.1.0 ; sys_platform == 'darwin' or sys_platform == 'win32'",
|
||||||
|
"attrs==25.3.0",
|
||||||
|
"beautifulsoup4==4.12.3",
|
||||||
|
"blinker==1.9.0",
|
||||||
|
"certifi==2025.4.26",
|
||||||
|
"charset-normalizer==3.4.2",
|
||||||
|
"click==8.1.8 ; python_full_version < '3.10'",
|
||||||
|
"click==8.2.1 ; python_full_version >= '3.10'",
|
||||||
|
"colorama==0.4.6 ; sys_platform == 'win32'",
|
||||||
|
"decorator==5.2.1",
|
||||||
|
"distro==1.9.0 ; sys_platform != 'darwin' and sys_platform != 'win32'",
|
||||||
|
"flask==3.1.1",
|
||||||
|
"flask-cors==6.0.0",
|
||||||
|
"idna==3.10",
|
||||||
|
"importlib-metadata==8.7.0 ; python_full_version < '3.10'",
|
||||||
|
"itsdangerous==2.2.0",
|
||||||
|
"jinja2==3.1.6",
|
||||||
|
"jsonschema==4.24.0",
|
||||||
|
"jsonschema-specifications==2025.4.1",
|
||||||
|
"markdown==3.8",
|
||||||
|
"markupsafe==3.0.2",
|
||||||
|
"mock==5.2.0",
|
||||||
|
"orjson==3.10.18",
|
||||||
|
"pip-system-certs==4.0",
|
||||||
|
"protobuf==6.31.1",
|
||||||
|
"psutil==7.0.0 ; sys_platform == 'win32'",
|
||||||
|
"pyqt6==6.8.0",
|
||||||
|
"pyqt6-qt6==6.8.1",
|
||||||
|
"pyqt6-sip==13.10.2",
|
||||||
|
"pyqt6-webengine==6.8.0",
|
||||||
|
"pyqt6-webengine-qt6==6.8.1",
|
||||||
|
"pysocks==1.7.1",
|
||||||
|
"pywin32==310 ; sys_platform == 'win32'",
|
||||||
|
"referencing==0.36.2",
|
||||||
|
"requests==2.32.3",
|
||||||
|
"rpds-py==0.25.1",
|
||||||
|
"send2trash==1.8.3",
|
||||||
|
"soupsieve==2.7",
|
||||||
|
"types-click==7.1.8",
|
||||||
|
"types-decorator==5.2.0.20250324",
|
||||||
|
"types-flask==1.1.6",
|
||||||
|
"types-flask-cors==6.0.0.20250520",
|
||||||
|
"types-jinja2==2.11.9",
|
||||||
|
"types-markdown==3.8.0.20250415",
|
||||||
|
"types-markupsafe==1.1.10",
|
||||||
|
"types-orjson==3.6.2",
|
||||||
|
"types-protobuf==6.30.2.20250516",
|
||||||
|
"types-pywin32==310.0.0.20250516",
|
||||||
|
"types-requests==2.32.0.20250602",
|
||||||
|
"types-waitress==3.0.1.20241117",
|
||||||
|
"types-werkzeug==1.0.9",
|
||||||
|
"typing-extensions==4.14.0",
|
||||||
|
"urllib3==2.4.0",
|
||||||
|
"waitress==3.0.2",
|
||||||
|
"werkzeug==3.1.3",
|
||||||
|
"wrapt==1.17.2",
|
||||||
|
"zipp==3.23.0 ; python_full_version < '3.10'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
name = "testpypi"
|
||||||
|
url = "https://test.pypi.org/simple/"
|
||||||
|
publish-url = "https://test.pypi.org/legacy/"
|
||||||
|
explicit = true
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
# hatch throws an error if nothing is included
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
include = ["no-such-file"]
|
|
@ -1,7 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Get the project root (two levels up from qt/bundle)
|
test -f update-release.sh || {
|
||||||
|
echo "run from release folder"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the project root (two levels up from qt/release)
|
||||||
PROJ_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
PROJ_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
|
||||||
# Use extracted uv binary
|
# Use extracted uv binary
|
||||||
|
@ -10,15 +16,13 @@ UV="$PROJ_ROOT/out/extracted/uv/uv"
|
||||||
# Prompt for wheel version
|
# Prompt for wheel version
|
||||||
read -p "Wheel version: " VERSION
|
read -p "Wheel version: " VERSION
|
||||||
|
|
||||||
# Create release directory if it doesn't exist
|
|
||||||
mkdir -p release
|
|
||||||
|
|
||||||
# Export dependencies using uv
|
# Export dependencies using uv
|
||||||
echo "Exporting dependencies..."
|
echo "Exporting dependencies..."
|
||||||
|
rm -f pyproject.toml
|
||||||
DEPS=$("$UV" export --no-hashes --no-annotate --no-header --extra audio --extra qt --all-packages --no-dev --no-emit-workspace)
|
DEPS=$("$UV" export --no-hashes --no-annotate --no-header --extra audio --extra qt --all-packages --no-dev --no-emit-workspace)
|
||||||
|
|
||||||
# Generate the pyproject.toml file
|
# Generate the pyproject.toml file
|
||||||
cat > release/pyproject.toml << EOF
|
cat > pyproject.toml << EOF
|
||||||
[project]
|
[project]
|
||||||
name = "anki-release"
|
name = "anki-release"
|
||||||
version = "$VERSION"
|
version = "$VERSION"
|
||||||
|
@ -32,12 +36,12 @@ EOF
|
||||||
# Add the exported dependencies to the file
|
# Add the exported dependencies to the file
|
||||||
echo "$DEPS" | while IFS= read -r line; do
|
echo "$DEPS" | while IFS= read -r line; do
|
||||||
if [[ -n "$line" ]]; then
|
if [[ -n "$line" ]]; then
|
||||||
echo " \"$line\"," >> release/pyproject.toml
|
echo " \"$line\"," >> pyproject.toml
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Complete the pyproject.toml file
|
# Complete the pyproject.toml file
|
||||||
cat >> release/pyproject.toml << 'EOF'
|
cat >> pyproject.toml << 'EOF'
|
||||||
]
|
]
|
||||||
|
|
||||||
[[tool.uv.index]]
|
[[tool.uv.index]]
|
||||||
|
@ -55,4 +59,4 @@ build-backend = "hatchling.build"
|
||||||
include = ["no-such-file"]
|
include = ["no-such-file"]
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Generated release/pyproject.toml with version $VERSION"
|
echo "Generated pyproject.toml with version $VERSION"
|
|
@ -57,6 +57,9 @@ pub trait CommandExt {
|
||||||
|
|
||||||
fn ensure_success(&mut self) -> Result<&mut Self>;
|
fn ensure_success(&mut self) -> Result<&mut Self>;
|
||||||
fn utf8_output(&mut self) -> Result<Utf8Output>;
|
fn utf8_output(&mut self) -> Result<Utf8Output>;
|
||||||
|
fn ensure_spawn(&mut self) -> Result<std::process::Child>;
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn ensure_exec(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandExt for Command {
|
impl CommandExt for Command {
|
||||||
|
@ -94,6 +97,23 @@ impl CommandExt for Command {
|
||||||
})?,
|
})?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_spawn(&mut self) -> Result<std::process::Child> {
|
||||||
|
self.spawn().with_context(|_| DidNotExecuteSnafu {
|
||||||
|
cmdline: get_cmdline(self),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn ensure_exec(&mut self) -> Result<()> {
|
||||||
|
use std::os::unix::process::CommandExt as UnixCommandExt;
|
||||||
|
let cmdline = get_cmdline(self);
|
||||||
|
let error = self.exec();
|
||||||
|
Err(Error::DidNotExecute {
|
||||||
|
cmdline,
|
||||||
|
source: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cmdline(arg: &mut Command) -> String {
|
fn get_cmdline(arg: &mut Command) -> String {
|
||||||
|
|
12
tools/build-arm-lin
Executable file
12
tools/build-arm-lin
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# sudo apt install libc6-dev-arm64-cross gcc-aarch64-linux-gnu
|
||||||
|
rustup target add aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
||||||
|
export LIN_ARM64=1
|
||||||
|
|
||||||
|
RELEASE=2 ./ninja wheels:anki
|
||||||
|
echo "wheels are in out/wheels"
|
10
tools/build-x64-mac
Executable file
10
tools/build-x64-mac
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
rustup target add x86_64-apple-darwin
|
||||||
|
|
||||||
|
export MAC_X86=1
|
||||||
|
|
||||||
|
RELEASE=2 ./ninja wheels:anki
|
||||||
|
echo "wheels are in out/wheels"
|
|
@ -1,5 +1,7 @@
|
||||||
@echo off
|
@echo off
|
||||||
pushd "%~dp0"\..
|
pushd "%~dp0"\..
|
||||||
set RELEASE=1
|
if exist out\wheels rmdir /s /q out\wheels
|
||||||
|
set RELEASE=2
|
||||||
tools\ninja wheels || exit /b 1
|
tools\ninja wheels || exit /b 1
|
||||||
|
echo wheels are in out/wheels
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Run a command with an alternative buildroot and Intel architecture target, for building Intel on an ARM Mac.
|
|
||||||
# Eg ./tools/mac-x86 ./tools/run-qt5.14
|
|
||||||
#
|
|
||||||
# Uses hard-coded paths to Python and build folders.
|
|
||||||
#
|
|
||||||
|
|
||||||
export BUILD_ROOT=~/Local/build/anki-x86
|
|
||||||
export NORMAL_BUILD_ROOT=~/Local/build/anki
|
|
||||||
export MAC_X86=1
|
|
||||||
|
|
||||||
|
|
||||||
# run provided command
|
|
||||||
$*
|
|
||||||
|
|
||||||
BUILD_ROOT=$NORMAL_BUILD_ROOT ./ninja just-to-restore-build-root-and-failure-is-expected
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
./tools/build
|
|
||||||
|
|
||||||
export UV_PUBLISH_TOKEN=$(pass show w/pypi-api-test)
|
export UV_PUBLISH_TOKEN=$(pass show w/pypi-api-test)
|
||||||
out/extracted/uv/uv publish --index testpypi out/wheels/*
|
out/extracted/uv/uv publish --index testpypi out/wheels/*
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue