mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00

A couple of motivations for this: - genbackend.py was somewhat messy, and difficult to change with the lack of types. The mobile clients used it as a base for their generation, so improving it will make life easier for them too, once they're ported. - It will make it easier to write a .ts generator in the future - We currently implement a bunch of helper methods on protobuf types which don't allow us to compile the protobuf types until we compile the Anki crate. If we change this in the future, we will be able to do more of the compilation up-front. We no longer need to record the services in the proto file, as we can extract the service order from the compiled protos. Support for map types has also been added.
242 lines
7.3 KiB
Rust
242 lines
7.3 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);
|
|
// not a direct dependency, but we include the output interface in our declared
|
|
// outputs
|
|
build.add_inputs("", inputs!["rslib/proto"]);
|
|
build.add_outputs("", vec!["pylib/anki/_backend_generated.py"]);
|
|
}
|
|
}
|
|
|
|
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(())
|
|
}
|