mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
390 lines
12 KiB
Rust
390 lines
12 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_macos_helper(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", "_qt5.py"));
|
|
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",
|
|
},
|
|
)
|
|
}
|
|
|
|
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_macos_helper(build: &mut Build) -> Result<()> {
|
|
if cfg!(target_os = "macos") {
|
|
build.add_action(
|
|
"qt:aqt:data:lib:libankihelper",
|
|
RunCommand {
|
|
command: ":pyenv:bin",
|
|
args: "$script $out $in",
|
|
inputs: hashmap! {
|
|
"script" => inputs!["qt/mac/helper_build.py"],
|
|
"in" => inputs![glob!["qt/mac/*.swift"]],
|
|
"" => inputs!["out/env"],
|
|
},
|
|
outputs: hashmap! {
|
|
"out" => vec!["qt/_aqt/data/lib/libankihelper.dylib"],
|
|
},
|
|
},
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn build_wheel(build: &mut Build) -> Result<()> {
|
|
build.add_action(
|
|
"wheels:aqt",
|
|
BuildWheel {
|
|
name: "aqt",
|
|
version: anki_version(),
|
|
src_folder: "qt/aqt",
|
|
gen_folder: "$builddir/qt/_aqt",
|
|
platform: None,
|
|
deps: inputs![":qt:aqt", glob!("qt/aqt/**"), "python/requirements.aqt.in"],
|
|
},
|
|
)
|
|
}
|
|
|
|
fn check_python(build: &mut Build) -> Result<()> {
|
|
python_format(
|
|
build,
|
|
"qt",
|
|
inputs![glob!("qt/**/*.py", "qt/bundle/PyOxidizer/**")],
|
|
)?;
|
|
|
|
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/**"]],
|
|
},
|
|
)
|
|
}
|