Anki/build/configure/src/python.rs
Damien Elmes 15dcb09036
Detect incorrect usage of triple slash in TypeScript (#2524)
* Migrate check_copyright to Rust

* Add a new lint to check accidental usages of /// in ts/svelte comments

* Fix a bunch of incorrect jdoc comments

* Move contributor check into minilints

Will allow users to detect the issue locally with './ninja check'
before pushing to CI.

* Make Cargo.toml consistent with other crates
2023-05-26 12:49:44 +10:00

238 lines
7.1 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use ninja_gen::action::BuildAction;
use ninja_gen::archives::Platform;
use ninja_gen::build::FilesHandle;
use ninja_gen::command::RunCommand;
use ninja_gen::glob;
use ninja_gen::hashmap;
use ninja_gen::input::BuildInput;
use ninja_gen::inputs;
use ninja_gen::python::python_format;
use ninja_gen::python::PythonEnvironment;
use ninja_gen::python::PythonLint;
use ninja_gen::python::PythonTypecheck;
use ninja_gen::rsync::RsyncFiles;
use ninja_gen::Build;
use ninja_gen::Result;
pub fn setup_venv(build: &mut Build) -> Result<()> {
let requirements_txt = if cfg!(windows) {
inputs![
"python/requirements.dev.txt",
"python/requirements.qt6_4.txt",
"python/requirements.win.txt",
]
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
inputs!["python/requirements.dev.txt"]
} else {
inputs![
"python/requirements.dev.txt",
"python/requirements.qt6_5.txt",
]
};
build.add(
"pyenv",
PythonEnvironment {
folder: "pyenv",
base_requirements_txt: inputs!["python/requirements.base.txt"],
requirements_txt,
extra_binary_exports: &[
"pip-compile",
"pip-sync",
"mypy",
"black",
"isort",
"pylint",
"pytest",
"protoc-gen-mypy",
],
},
)?;
// optional venvs for testing with Qt5
let mut reqs_qt5 = inputs!["python/requirements.bundle.txt"];
if cfg!(windows) {
reqs_qt5 = inputs![reqs_qt5, "python/requirements.win.txt"];
}
build.add(
"pyenv-qt5.15",
PythonEnvironment {
folder: "pyenv-qt5.15",
base_requirements_txt: inputs!["python/requirements.base.txt"],
requirements_txt: inputs![&reqs_qt5, "python/requirements.qt5_15.txt"],
extra_binary_exports: &[],
},
)?;
build.add(
"pyenv-qt5.14",
PythonEnvironment {
folder: "pyenv-qt5.14",
base_requirements_txt: inputs!["python/requirements.base.txt"],
requirements_txt: inputs![reqs_qt5, "python/requirements.qt5_14.txt"],
extra_binary_exports: &[],
},
)?;
Ok(())
}
pub struct GenPythonProto {
pub proto_files: BuildInput,
}
impl BuildAction for GenPythonProto {
fn command(&self) -> &str {
"$protoc $
--plugin=protoc-gen-mypy=$protoc-gen-mypy $
--python_out=$builddir/pylib $
--mypy_out=$builddir/pylib $
-Iproto $in"
}
fn files(&mut self, build: &mut impl FilesHandle) {
let proto_inputs = build.expand_inputs(&self.proto_files);
let python_outputs: Vec<_> = proto_inputs
.iter()
.flat_map(|path| {
let path = path
.replace('\\', "/")
.replace("proto/", "pylib/")
.replace(".proto", "_pb2");
[format!("{path}.py"), format!("{path}.pyi")]
})
.collect();
build.add_inputs("in", &self.proto_files);
build.add_inputs("protoc", inputs!["$protoc_binary"]);
build.add_inputs("protoc-gen-mypy", inputs![":pyenv:protoc-gen-mypy"]);
build.add_outputs("", python_outputs);
}
}
pub struct BuildWheel {
pub name: &'static str,
pub version: String,
pub src_folder: &'static str,
pub gen_folder: &'static str,
pub platform: Option<Platform>,
pub deps: BuildInput,
}
impl BuildAction for BuildWheel {
fn command(&self) -> &str {
"$pyenv_bin $script $src $gen $out"
}
fn files(&mut self, build: &mut impl FilesHandle) {
build.add_inputs("pyenv_bin", inputs![":pyenv:bin"]);
build.add_inputs("script", inputs!["python/write_wheel.py"]);
build.add_inputs("", &self.deps);
build.add_variable("src", self.src_folder);
build.add_variable("gen", self.gen_folder);
let tag = if let Some(platform) = self.platform {
let platform = match platform {
Platform::LinuxX64 => "manylinux_2_28_x86_64",
Platform::LinuxArm => "manylinux_2_31_aarch64",
Platform::MacX64 => "macosx_10_13_x86_64",
Platform::MacArm => "macosx_11_0_arm64",
Platform::WindowsX64 => "win_amd64",
};
format!("cp39-abi3-{platform}")
} else {
"py3-none-any".into()
};
let name = self.name;
let version = &self.version;
let wheel_path = format!("wheels/{name}-{version}-{tag}.whl");
build.add_outputs("out", vec![wheel_path]);
}
}
pub fn check_python(build: &mut Build) -> Result<()> {
python_format(build, "ftl", inputs![glob!("ftl/**/*.py")])?;
python_format(build, "tools", inputs![glob!("tools/**/*.py")])?;
build.add(
"check:mypy",
PythonTypecheck {
folders: &[
"pylib",
"ts/lib",
"qt/aqt",
"qt/tools",
"out/pylib/anki",
"out/qt/_aqt",
"ftl",
"python",
"tools",
],
deps: inputs![
glob!["{pylib,ftl,qt}/**/*.{py,pyi}"],
":pylib/anki",
":qt/aqt"
],
},
)?;
add_pylint(build)?;
Ok(())
}
fn add_pylint(build: &mut Build) -> Result<()> {
// pylint does not support PEP420 implicit namespaces split across import paths,
// so we need to merge our pylib sources and generated files before invoking it,
// and add a top-level __init__.py
build.add(
"pylint/anki",
RsyncFiles {
inputs: inputs![":pylib/anki"],
target_folder: "pylint/anki",
strip_prefix: "$builddir/pylib/anki",
// avoid copying our large rsbridge binary
extra_args: "--links",
},
)?;
build.add(
"pylint/anki",
RsyncFiles {
inputs: inputs![glob!["pylib/anki/**"]],
target_folder: "pylint/anki",
strip_prefix: "pylib/anki",
extra_args: "",
},
)?;
build.add(
"pylint/anki",
RunCommand {
command: ":pyenv:bin",
args: "$script $out",
inputs: hashmap! { "script" => inputs!["python/mkempty.py"] },
outputs: hashmap! { "out" => vec!["pylint/anki/__init__.py"] },
},
)?;
build.add(
"check:pylint",
PythonLint {
folders: &[
"$builddir/pylint/anki",
"qt/aqt",
"ftl",
"pylib/tools",
"tools",
"python",
],
pylint_ini: inputs![".pylintrc"],
deps: inputs![
":pylint/anki",
":qt/aqt",
glob!("{pylib/tools,ftl,qt,python,tools}/**/*.py")
],
},
)?;
Ok(())
}