mirror of
https://github.com/ankitects/anki.git
synced 2025-11-07 05:07:10 -05:00
- Use --locked to assert that the lockfile won't change, so we need to explicitly 'uv lock' when making changes. Still trying to get to the bottom of why the lockfile sometimes has editable entries, which break things when switching between platforms. - Exclude __pycache__ from wheels - Move the typing stubs to our dev deps (https://github.com/ankitects/anki/pull/4074#pullrequestreview-2948088436)
366 lines
11 KiB
Rust
366 lines
11 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
use anyhow::Result;
|
|
use ninja_gen::action::BuildAction;
|
|
use ninja_gen::command::RunCommand;
|
|
use ninja_gen::copy::CopyFile;
|
|
use ninja_gen::copy::CopyFiles;
|
|
use ninja_gen::glob;
|
|
use ninja_gen::hashmap;
|
|
use ninja_gen::inputs;
|
|
use ninja_gen::node::CompileSass;
|
|
use ninja_gen::node::EsbuildScript;
|
|
use ninja_gen::node::TypescriptCheck;
|
|
use ninja_gen::python::python_format;
|
|
use ninja_gen::python::PythonTest;
|
|
use ninja_gen::rsync::RsyncFiles;
|
|
use ninja_gen::Build;
|
|
use ninja_gen::Utf8Path;
|
|
use ninja_gen::Utf8PathBuf;
|
|
|
|
use crate::anki_version;
|
|
use crate::python::BuildWheel;
|
|
use crate::web::copy_mathjax;
|
|
|
|
pub fn build_and_check_aqt(build: &mut Build) -> Result<()> {
|
|
build_forms(build)?;
|
|
build_generated_sources(build)?;
|
|
build_data_folder(build)?;
|
|
build_wheel(build)?;
|
|
check_python(build)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_forms(build: &mut Build) -> Result<()> {
|
|
let ui_files = glob!["qt/aqt/forms/*.ui"];
|
|
let outdir = Utf8PathBuf::from("qt/_aqt/forms");
|
|
let mut py_files = vec![];
|
|
for path in ui_files.resolve() {
|
|
let outpath = outdir.join(path.file_name().unwrap()).into_string();
|
|
py_files.push(outpath.replace(".ui", "_qt6.py"));
|
|
}
|
|
build.add_action(
|
|
"qt:aqt:forms",
|
|
RunCommand {
|
|
command: ":pyenv:bin",
|
|
args: "$script $first_form",
|
|
inputs: hashmap! {
|
|
"script" => inputs!["qt/tools/build_ui.py"],
|
|
"" => inputs![ui_files],
|
|
},
|
|
outputs: hashmap! {
|
|
"first_form" => vec![py_files[0].as_str()],
|
|
"" => py_files.iter().skip(1).map(|s| s.as_str()).collect(),
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
/// For legacy reasons, we can not easily separate sources and generated files
|
|
/// up with a PEP420 namespace, as aqt/__init__.py exports a bunch of things.
|
|
/// To allow code to run/typecheck without having to merge source and generated
|
|
/// files into a separate folder, the generated files are exported as a separate
|
|
/// _aqt module.
|
|
fn build_generated_sources(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"qt:aqt:hooks.py",
|
|
RunCommand {
|
|
command: ":pyenv:bin",
|
|
args: "$script $out",
|
|
inputs: hashmap! {
|
|
"script" => inputs!["qt/tools/genhooks_gui.py"],
|
|
"" => inputs!["pylib/anki/_vendor/stringcase.py", "pylib/tools/hookslib.py"]
|
|
},
|
|
outputs: hashmap! {
|
|
"out" => vec!["qt/_aqt/hooks.py"]
|
|
},
|
|
},
|
|
)?;
|
|
build.add_action(
|
|
"qt:aqt:sass_vars",
|
|
RunCommand {
|
|
command: ":pyenv:bin",
|
|
args: "$script $root_scss $out",
|
|
inputs: hashmap! {
|
|
"script" => inputs!["qt/tools/extract_sass_vars.py"],
|
|
"root_scss" => inputs![":css:_root-vars"],
|
|
},
|
|
outputs: hashmap! {
|
|
"out" => vec![
|
|
"qt/_aqt/colors.py",
|
|
"qt/_aqt/props.py"
|
|
]
|
|
},
|
|
},
|
|
)?;
|
|
// we need to add a py.typed file to the generated sources, or mypy
|
|
// will ignore them when used with the generated wheel
|
|
build.add_action(
|
|
"qt:aqt:py.typed",
|
|
CopyFile {
|
|
input: "qt/aqt/py.typed".into(),
|
|
output: "qt/_aqt/py.typed",
|
|
},
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_data_folder(build: &mut Build) -> Result<()> {
|
|
build_css(build)?;
|
|
build_imgs(build)?;
|
|
build_js(build)?;
|
|
build_pages(build)?;
|
|
build_icons(build)?;
|
|
copy_sveltekit(build)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn copy_sveltekit(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"qt:aqt:data:web:sveltekit",
|
|
RsyncFiles {
|
|
inputs: inputs![":sveltekit:folder"],
|
|
target_folder: "qt/_aqt/data/web/",
|
|
strip_prefix: "$builddir/",
|
|
extra_args: "-a --delete",
|
|
},
|
|
)
|
|
}
|
|
|
|
fn build_css(build: &mut Build) -> Result<()> {
|
|
let scss_files = build.expand_inputs(inputs![glob!["qt/aqt/data/web/css/*.scss"]]);
|
|
let out_dir = Utf8Path::new("qt/_aqt/data/web/css");
|
|
for scss in scss_files {
|
|
let stem = Utf8Path::new(&scss).file_stem().unwrap();
|
|
let mut out_path = out_dir.join(stem);
|
|
out_path.set_extension("css");
|
|
|
|
build.add_action(
|
|
"qt:aqt:data:web:css",
|
|
CompileSass {
|
|
input: scss.into(),
|
|
output: out_path.as_str(),
|
|
deps: inputs![":sass"],
|
|
load_paths: vec![".", "node_modules"],
|
|
},
|
|
)?;
|
|
}
|
|
let other_ts_css = build.inputs_with_suffix(
|
|
inputs![":ts:editor", ":ts:editable", ":ts:reviewer:reviewer.css"],
|
|
".css",
|
|
);
|
|
build.add_action(
|
|
"qt:aqt:data:web:css",
|
|
CopyFiles {
|
|
inputs: other_ts_css.into(),
|
|
output_folder: "qt/_aqt/data/web/css",
|
|
},
|
|
)
|
|
}
|
|
|
|
fn build_imgs(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"qt:aqt:data:web:imgs",
|
|
CopyFiles {
|
|
inputs: inputs![glob!["qt/aqt/data/web/imgs/*"]],
|
|
output_folder: "qt/_aqt/data/web/imgs",
|
|
},
|
|
)
|
|
}
|
|
|
|
fn build_js(build: &mut Build) -> Result<()> {
|
|
for ts_file in &["deckbrowser", "webview", "toolbar", "reviewer-bottom"] {
|
|
build.add_action(
|
|
"qt:aqt:data:web:js",
|
|
EsbuildScript {
|
|
script: "ts/transform_ts.mjs".into(),
|
|
entrypoint: format!("qt/aqt/data/web/js/{ts_file}.ts").into(),
|
|
deps: inputs![],
|
|
output_stem: &format!("qt/_aqt/data/web/js/{ts_file}"),
|
|
extra_exts: &[],
|
|
},
|
|
)?;
|
|
}
|
|
let files = inputs![glob!["qt/aqt/data/web/js/*"]];
|
|
build.add_action(
|
|
"check:typescript:aqt",
|
|
TypescriptCheck {
|
|
tsconfig: "qt/aqt/data/web/js/tsconfig.json".into(),
|
|
inputs: files,
|
|
},
|
|
)?;
|
|
let files_from_ts = build.inputs_with_suffix(
|
|
inputs![":ts:editor", ":ts:reviewer:reviewer.js", ":ts:mathjax"],
|
|
".js",
|
|
);
|
|
build.add_action(
|
|
"qt:aqt:data:web:js",
|
|
CopyFiles {
|
|
inputs: files_from_ts.into(),
|
|
output_folder: "qt/_aqt/data/web/js",
|
|
},
|
|
)?;
|
|
build_vendor_js(build)
|
|
}
|
|
|
|
fn build_vendor_js(build: &mut Build) -> Result<()> {
|
|
build.add_action("qt:aqt:data:web:js:vendor:mathjax", copy_mathjax())?;
|
|
build.add_action(
|
|
"qt:aqt:data:web:js:vendor",
|
|
CopyFiles {
|
|
inputs: inputs![
|
|
":node_modules:jquery",
|
|
":node_modules:jquery-ui",
|
|
":node_modules:bootstrap-dist",
|
|
"qt/aqt/data/web/js/vendor/plot.js"
|
|
],
|
|
output_folder: "qt/_aqt/data/web/js/vendor",
|
|
},
|
|
)
|
|
}
|
|
|
|
fn build_pages(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"qt:aqt:data:web:pages",
|
|
CopyFiles {
|
|
inputs: inputs![":ts:pages"],
|
|
output_folder: "qt/_aqt/data/web/pages",
|
|
},
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_icons(build: &mut Build) -> Result<()> {
|
|
build_themed_icons(build)?;
|
|
build.add_action(
|
|
"qt:aqt:data:qt:icons:mdi_unthemed",
|
|
CopyFiles {
|
|
inputs: inputs![":node_modules:mdi_unthemed"],
|
|
output_folder: "qt/_aqt/data/qt/icons",
|
|
},
|
|
)?;
|
|
build.add_action(
|
|
"qt:aqt:data:qt:icons:from_src",
|
|
CopyFiles {
|
|
inputs: inputs![glob!["qt/aqt/data/qt/icons/*.{png,svg}"]],
|
|
output_folder: "qt/_aqt/data/qt/icons",
|
|
},
|
|
)?;
|
|
build.add_action(
|
|
"qt:aqt:data:qt:icons",
|
|
RunCommand {
|
|
command: ":pyenv:bin",
|
|
args: "$script $out $in",
|
|
inputs: hashmap! {
|
|
"script" => inputs!["qt/tools/build_qrc.py"],
|
|
"in" => inputs![
|
|
":qt:aqt:data:qt:icons:mdi_unthemed",
|
|
":qt:aqt:data:qt:icons:mdi_themed",
|
|
":qt:aqt:data:qt:icons:from_src",
|
|
]
|
|
},
|
|
outputs: hashmap! {
|
|
"out" => vec!["qt/_aqt/data/qt/icons.qrc"]
|
|
},
|
|
},
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_themed_icons(build: &mut Build) -> Result<()> {
|
|
let themed_icons_with_extra = hashmap! {
|
|
"chevron-up" => &["FG_DISABLED"],
|
|
"chevron-down" => &["FG_DISABLED"],
|
|
"drag-vertical" => &["FG_SUBTLE"],
|
|
"drag-horizontal" => &["FG_SUBTLE"],
|
|
"check" => &["FG_DISABLED"],
|
|
"circle-medium" => &["FG_DISABLED"],
|
|
"minus-thick" => &["FG_DISABLED"],
|
|
};
|
|
for icon_path in build.expand_inputs(inputs![":node_modules:mdi_themed"]) {
|
|
let path = Utf8Path::new(&icon_path);
|
|
let stem = path.file_stem().unwrap();
|
|
let mut colors = vec!["FG"];
|
|
if let Some(&extra) = themed_icons_with_extra.get(stem) {
|
|
colors.extend(extra);
|
|
}
|
|
build.add_action(
|
|
"qt:aqt:data:qt:icons:mdi_themed",
|
|
BuildThemedIcon {
|
|
src_icon: path,
|
|
colors,
|
|
},
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
struct BuildThemedIcon<'a> {
|
|
src_icon: &'a Utf8Path,
|
|
colors: Vec<&'a str>,
|
|
}
|
|
|
|
impl BuildAction for BuildThemedIcon<'_> {
|
|
fn command(&self) -> &str {
|
|
"$pyenv_bin $script $in $colors $out"
|
|
}
|
|
|
|
fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
|
|
let stem = self.src_icon.file_stem().unwrap();
|
|
// eg foo-light.svg, foo-dark.svg, foo-FG_SUBTLE-light.svg,
|
|
// foo-FG_SUBTLE-dark.svg
|
|
let outputs: Vec<_> = self
|
|
.colors
|
|
.iter()
|
|
.flat_map(|&color| {
|
|
let variant = if color == "FG" {
|
|
"".into()
|
|
} else {
|
|
format!("-{color}")
|
|
};
|
|
[
|
|
format!("qt/_aqt/data/qt/icons/{stem}{variant}-light.svg"),
|
|
format!("qt/_aqt/data/qt/icons/{stem}{variant}-dark.svg"),
|
|
]
|
|
})
|
|
.collect();
|
|
|
|
build.add_inputs("pyenv_bin", inputs![":pyenv:bin"]);
|
|
build.add_inputs("script", inputs!["qt/tools/color_svg.py"]);
|
|
build.add_inputs("in", inputs![self.src_icon.as_str()]);
|
|
build.add_inputs("", inputs![":qt:aqt:sass_vars"]);
|
|
build.add_variable("colors", self.colors.join(":"));
|
|
build.add_outputs("out", outputs);
|
|
}
|
|
}
|
|
|
|
fn build_wheel(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"wheels:aqt",
|
|
BuildWheel {
|
|
name: "aqt",
|
|
version: anki_version(),
|
|
platform: None,
|
|
deps: inputs![
|
|
":qt:aqt",
|
|
glob!("qt/aqt/**"),
|
|
"qt/pyproject.toml",
|
|
"qt/hatch_build.py"
|
|
],
|
|
},
|
|
)
|
|
}
|
|
|
|
fn check_python(build: &mut Build) -> Result<()> {
|
|
python_format(build, "qt", inputs![glob!("qt/**/*.py")])?;
|
|
|
|
build.add_action(
|
|
"check:pytest:aqt",
|
|
PythonTest {
|
|
folder: "qt/tests",
|
|
python_path: &["pylib", "$builddir/pylib", "$builddir/qt"],
|
|
deps: inputs![":pylib:anki", ":qt:aqt", glob!["qt/tests/**"]],
|
|
},
|
|
)
|
|
}
|