Anki/build/ninja_gen/src/python.rs
Damien Elmes 0d902067b7 Use Windows ARM64 cargo/node binaries during build
We can't provide ARM64 wheels to users yet due to #4079, but we can
at least speed up the build.

The rustls -> native-tls change on Windows is because ring requires
clang to compile for ARM64, and I figured it's best to keep our Windows
deps consistent. We already built the wheels with native-tls.
2025-06-19 13:39:46 +07:00

305 lines
9.6 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::env;
use anyhow::Result;
use camino::Utf8Path;
use maplit::hashmap;
use crate::action::BuildAction;
use crate::archives::download_and_extract;
use crate::archives::with_exe;
use crate::archives::OnlineArchive;
use crate::archives::Platform;
use crate::command::RunCommand;
use crate::hash::simple_hash;
use crate::input::BuildInput;
use crate::inputs;
use crate::Build;
// To update, run 'cargo run --bin update_uv'.
// You'll need to do this when bumping Python versions, as uv bakes in
// the latest known version.
// When updating Python version, make sure to update version tag in BuildWheel
// too.
pub fn uv_archive(platform: Platform) -> OnlineArchive {
match platform {
Platform::LinuxX64 => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-x86_64-unknown-linux-gnu.tar.gz",
sha256: "909278eb197c5ed0e9b5f16317d1255270d1f9ea4196e7179ce934d48c4c2545",
}
},
Platform::LinuxArm => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-aarch64-unknown-linux-gnu.tar.gz",
sha256: "0b2ad9fe4295881615295add8cc5daa02549d29cc9a61f0578e397efcf12f08f",
}
},
Platform::MacX64 => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-x86_64-apple-darwin.tar.gz",
sha256: "d785753ac092e25316180626aa691c5dfe1fb075290457ba4fdb72c7c5661321",
}
},
Platform::MacArm => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-aarch64-apple-darwin.tar.gz",
sha256: "721f532b73171586574298d4311a91d5ea2c802ef4db3ebafc434239330090c6",
}
},
Platform::WindowsX64 => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-x86_64-pc-windows-msvc.zip",
sha256: "e199b10bef1a7cc540014483e7f60f825a174988f41020e9d2a6b01bd60f0669",
}
},
Platform::WindowsArm => {
OnlineArchive {
url: "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-aarch64-pc-windows-msvc.zip",
sha256: "bb40708ad549ad6a12209cb139dd751bf0ede41deb679ce7513ce197bd9ef234",
}
}
}
}
pub fn setup_uv(build: &mut Build, platform: Platform) -> Result<()> {
let uv_binary = match env::var("UV_BINARY") {
Ok(path) => {
assert!(
Utf8Path::new(&path).is_absolute(),
"UV_BINARY must be absolute"
);
path.into()
}
Err(_) => {
download_and_extract(
build,
"uv",
uv_archive(platform),
hashmap! { "bin" => [
with_exe("uv")
] },
)?;
inputs![":extract:uv:bin"]
}
};
build.add_dependency("uv_binary", uv_binary);
// Our macOS packaging needs access to the x86 binary on ARM.
download_and_extract(
build,
"uv_mac_x86",
uv_archive(Platform::MacX64),
hashmap! { "bin" => [
with_exe("uv")
] },
)?;
Ok(())
}
pub struct PythonEnvironment {
pub deps: BuildInput,
// todo: rename
pub venv_folder: &'static str,
pub extra_args: &'static str,
pub extra_binary_exports: &'static [&'static str],
}
impl BuildAction for PythonEnvironment {
fn command(&self) -> &str {
if env::var("OFFLINE_BUILD").is_err() {
"$runner pyenv $uv_binary $builddir/$pyenv_folder -- $extra_args"
} else {
"echo 'OFFLINE_BUILD is set. Using the existing PythonEnvironment.'"
}
}
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
let bin_path = |binary: &str| -> Vec<String> {
let folder = self.venv_folder;
let path = if cfg!(windows) {
format!("{folder}/scripts/{binary}.exe")
} else {
format!("{folder}/bin/{binary}")
};
vec![path]
};
build.add_inputs("", &self.deps);
build.add_variable("pyenv_folder", self.venv_folder);
if env::var("OFFLINE_BUILD").is_err() {
build.add_inputs("uv_binary", inputs![":uv_binary"]);
// Add --python flag to extra_args if PYTHON_BINARY is set
let mut args = self.extra_args.to_string();
if let Ok(python_binary) = env::var("PYTHON_BINARY") {
args = format!("--python {} {}", python_binary, args);
}
build.add_variable("extra_args", args);
}
build.add_outputs_ext("bin", bin_path("python"), true);
for binary in self.extra_binary_exports {
build.add_outputs_ext(*binary, bin_path(binary), true);
}
build.add_output_stamp(format!("{}/.stamp", self.venv_folder));
}
}
pub struct PythonTypecheck {
pub folders: &'static [&'static str],
pub deps: BuildInput,
}
impl BuildAction for PythonTypecheck {
fn command(&self) -> &str {
"$mypy $folders"
}
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
build.add_inputs("", &self.deps);
build.add_inputs("mypy", inputs![":pyenv:mypy"]);
build.add_inputs("", inputs![".mypy.ini"]);
build.add_variable("folders", self.folders.join(" "));
let hash = simple_hash(self.folders);
build.add_output_stamp(format!("tests/python_typecheck.{hash}"));
}
fn hide_progress(&self) -> bool {
true
}
}
struct PythonFormat<'a> {
pub inputs: &'a BuildInput,
pub check_only: bool,
pub isort_ini: &'a BuildInput,
}
impl BuildAction for PythonFormat<'_> {
fn command(&self) -> &str {
"$black -t py39 -q $check --color $in && $
$isort --color --settings-path $isort_ini $check $in"
}
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
build.add_inputs("in", self.inputs);
build.add_inputs("black", inputs![":pyenv:black"]);
build.add_inputs("isort", inputs![":pyenv:isort"]);
let hash = simple_hash(self.inputs);
build.add_env_var("BLACK_CACHE_DIR", "out/python/black.cache.{hash}");
build.add_inputs("isort_ini", self.isort_ini);
build.add_variable(
"check",
if self.check_only {
"--diff --check"
} else {
""
},
);
build.add_output_stamp(format!(
"tests/python_format.{}.{hash}",
if self.check_only { "check" } else { "fix" }
));
}
}
pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Result<()> {
let isort_ini = &inputs![".isort.cfg"];
build.add_action(
format!("check:format:python:{group}"),
PythonFormat {
inputs: &inputs,
check_only: true,
isort_ini,
},
)?;
build.add_action(
format!("format:python:{group}"),
PythonFormat {
inputs: &inputs,
check_only: false,
isort_ini,
},
)?;
Ok(())
}
pub struct PythonLint {
pub folders: &'static [&'static str],
pub pylint_ini: BuildInput,
pub deps: BuildInput,
}
impl BuildAction for PythonLint {
fn command(&self) -> &str {
"$pylint --rcfile $pylint_ini -sn -j $cpus $folders"
}
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
build.add_inputs("", &self.deps);
build.add_inputs("pylint", inputs![":pyenv:pylint"]);
build.add_inputs("pylint_ini", &self.pylint_ini);
build.add_variable("folders", self.folders.join(" "));
// On a 16 core system, values above 10 do not improve wall clock time,
// but waste extra cores that could be working on other tests.
build.add_variable("cpus", num_cpus::get().min(10).to_string());
let hash = simple_hash(&self.deps);
build.add_output_stamp(format!("tests/python_lint.{hash}"));
}
}
pub struct PythonTest {
pub folder: &'static str,
pub python_path: &'static [&'static str],
pub deps: BuildInput,
}
impl BuildAction for PythonTest {
fn command(&self) -> &str {
"$pytest -p no:cacheprovider $folder"
}
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
build.add_inputs("", &self.deps);
build.add_inputs("pytest", inputs![":pyenv:pytest"]);
build.add_variable("folder", self.folder);
build.add_variable(
"pythonpath",
self.python_path.join(if cfg!(windows) { ";" } else { ":" }),
);
build.add_env_var("PYTHONPATH", "$pythonpath");
build.add_env_var("ANKI_TEST_MODE", "1");
let hash = simple_hash(self.folder);
build.add_output_stamp(format!("tests/python_pytest.{hash}"));
}
fn hide_progress(&self) -> bool {
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"],
},
},
)
}