mirror of
https://github.com/ankitects/anki.git
synced 2025-11-17 01:57:12 -05:00
* Update to latest Node LTS * Add sveltekit * Split tslib into separate @generated and @tslib components SvelteKit's path aliases don't support multiple locations, so our old approach of using @tslib to refer to both ts/lib and out/ts/lib will no longer work. Instead, all generated sources and their includes are placed in a separate out/ts/generated folder, and imported via @generated instead. This also allows us to generate .ts files, instead of needing to output separate .d.ts and .js files. * Switch package.json to module type * Avoid usage of baseUrl Incompatible with SvelteKit * Move sass into ts; use relative links SvelteKit's default sass support doesn't allow overriding loadPaths * jest->vitest, graphs example working with yarn dev * most pages working in dev mode * Some fixes after rebasing * Fix/silence some svelte-check errors * Get image-occlusion working with Fabric types * Post-rebase lock changes * Editor is now checked * SvelteKit build integrated into ninja * Use the new SvelteKit entrypoint for pages like congrats/deck options/etc * Run eslint once for ts/**; fix some tests * Fix a bunch of issues introduced when rebasing over latest main * Run eslint fix * Fix remaining eslint+pylint issues; tests now all pass * Fix some issues with a clean build * Latest bufbuild no longer requires @__PURE__ hack * Add a few missed dependencies * Add yarn.bat to fix Windows build * Fix pages failing to show when ANKI_API_PORT not defined * Fix svelte-check and vitest on Windows * Set node path in ./yarn * Move svelte-kit output to ts/.svelte-kit Sadly, I couldn't figure out a way to store it in out/ if out/ is a symlink, as it breaks module resolution when SvelteKit is run. * Allow HMR inside Anki * Skip SvelteKit build when HMR is defined * Fix some post-rebase issues I should have done a normal merge instead.
190 lines
5.8 KiB
Rust
190 lines
5.8 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 std::fs;
|
|
use std::io::Write;
|
|
use std::process::Command;
|
|
use std::time::Instant;
|
|
|
|
use camino::Utf8Path;
|
|
use camino::Utf8PathBuf;
|
|
use clap::Args;
|
|
use termcolor::Color;
|
|
use termcolor::ColorChoice;
|
|
use termcolor::ColorSpec;
|
|
use termcolor::StandardStream;
|
|
use termcolor::WriteColor;
|
|
|
|
#[derive(Args)]
|
|
pub struct BuildArgs {
|
|
#[arg(trailing_var_arg = true)]
|
|
args: Vec<String>,
|
|
}
|
|
|
|
pub fn run_build(args: BuildArgs) {
|
|
let build_root = &setup_build_root();
|
|
|
|
let path = if cfg!(windows) {
|
|
format!(
|
|
"out\\bin;out\\extracted\\node;node_modules\\.bin;{};\\msys64\\usr\\bin",
|
|
env::var("PATH").unwrap()
|
|
)
|
|
} else {
|
|
format!(
|
|
"{br}/bin:{br}/extracted/node/bin:{path}",
|
|
br = build_root
|
|
.canonicalize_utf8()
|
|
.expect("resolving build root")
|
|
.as_str(),
|
|
path = env::var("PATH").unwrap()
|
|
)
|
|
};
|
|
|
|
maybe_update_env_file(build_root);
|
|
maybe_update_buildhash(build_root);
|
|
|
|
// Ensure build file is up to date
|
|
let build_file = build_root.join("build.ninja");
|
|
if !build_file.exists() {
|
|
bootstrap_build();
|
|
}
|
|
|
|
// automatically convert foo:bar references to foo_bar, as Ninja can not
|
|
// represent the former
|
|
let ninja_args = args.args.into_iter().map(|a| a.replace(':', "_"));
|
|
|
|
let start_time = Instant::now();
|
|
let mut command = Command::new(get_ninja_command());
|
|
command
|
|
.arg("-f")
|
|
.arg(&build_file)
|
|
.args(ninja_args)
|
|
.env("PATH", &path)
|
|
.env(
|
|
"MYPY_CACHE_DIR",
|
|
build_root.join("tests").join("mypy").into_string(),
|
|
)
|
|
.env("PYTHONPYCACHEPREFIX", build_root.join("pycache"))
|
|
// commands will not show colors by default, as we do not provide a tty
|
|
.env("FORCE_COLOR", "1")
|
|
.env("MYPY_FORCE_COLOR", "1")
|
|
.env("TERM", std::env::var("TERM").unwrap_or_default());
|
|
if env::var("NINJA_STATUS").is_err() {
|
|
command.env("NINJA_STATUS", "[%f/%t; %r active; %es] ");
|
|
}
|
|
|
|
// run build
|
|
let mut status = command.status().expect("ninja not installed");
|
|
if !status.success() && Instant::now().duration_since(start_time).as_secs() < 3 {
|
|
// if the build fails quickly, there's a reasonable chance that build.ninja
|
|
// references a file that has been renamed/deleted. We currently don't
|
|
// capture stderr, so we can't confirm, but in case that's the case, we
|
|
// regenerate the build.ninja file then try again.
|
|
bootstrap_build();
|
|
status = command.status().expect("ninja missing");
|
|
}
|
|
let mut stdout = StandardStream::stdout(ColorChoice::Always);
|
|
if status.success() {
|
|
stdout
|
|
.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
|
|
.unwrap();
|
|
writeln!(
|
|
&mut stdout,
|
|
"\nBuild succeeded in {:.2}s.",
|
|
start_time.elapsed().as_secs_f32()
|
|
)
|
|
.unwrap();
|
|
stdout.reset().unwrap();
|
|
} else {
|
|
stdout
|
|
.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))
|
|
.unwrap();
|
|
writeln!(&mut stdout, "\nBuild failed.").unwrap();
|
|
stdout.reset().unwrap();
|
|
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
fn get_ninja_command() -> &'static str {
|
|
if which::which("n2").is_ok() {
|
|
"n2"
|
|
} else {
|
|
"ninja"
|
|
}
|
|
}
|
|
|
|
fn setup_build_root() -> Utf8PathBuf {
|
|
let build_root = Utf8Path::new("out");
|
|
|
|
#[cfg(unix)]
|
|
if let Ok(new_target) = env::var("BUILD_ROOT").map(camino::Utf8PathBuf::from) {
|
|
let create = if let Ok(existing_target) = build_root.read_link_utf8() {
|
|
if existing_target != new_target {
|
|
fs::remove_file(build_root).unwrap();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
true
|
|
};
|
|
if create {
|
|
println!("Switching build root to {}", new_target);
|
|
std::os::unix::fs::symlink(new_target, build_root).unwrap();
|
|
}
|
|
}
|
|
|
|
fs::create_dir_all(build_root).unwrap();
|
|
if cfg!(windows) {
|
|
build_root.to_owned()
|
|
} else {
|
|
build_root.canonicalize_utf8().unwrap()
|
|
}
|
|
}
|
|
|
|
fn bootstrap_build() {
|
|
let status = Command::new("cargo")
|
|
.args(["run", "-p", "configure"])
|
|
.status();
|
|
assert!(status.expect("ninja").success());
|
|
}
|
|
|
|
fn maybe_update_buildhash(build_root: &Utf8Path) {
|
|
// only updated on release builds
|
|
let path = build_root.join("buildhash");
|
|
if (env::var("RELEASE").is_ok() && env::var("OFFLINE_BUILD").is_err()) || !path.exists() {
|
|
write_if_changed(&path, &get_buildhash())
|
|
}
|
|
}
|
|
|
|
fn get_buildhash() -> String {
|
|
let output = Command::new("git")
|
|
.args(["rev-parse", "--short=8", "HEAD"])
|
|
.output()
|
|
.expect("git");
|
|
assert!(output.status.success(),
|
|
"Invoking 'git' failed. Make sure you're building from a clone of the git repo, and that 'git' is installed.");
|
|
String::from_utf8(output.stdout).unwrap().trim().into()
|
|
}
|
|
|
|
fn write_if_changed(path: &Utf8Path, contents: &str) {
|
|
if let Ok(old_contents) = fs::read_to_string(path) {
|
|
if old_contents == contents {
|
|
return;
|
|
}
|
|
}
|
|
fs::write(path, contents).unwrap();
|
|
}
|
|
|
|
/// Trigger reconfigure when our env vars change
|
|
fn maybe_update_env_file(build_root: &Utf8Path) {
|
|
let env_file = build_root.join("env");
|
|
let build_root_env = env::var("BUILD_ROOT").unwrap_or_default();
|
|
let release = env::var("RELEASE").unwrap_or_default();
|
|
let other_watched_env = env::var("RECONFIGURE_KEY").unwrap_or_default();
|
|
let current_env = format!("{build_root_env};{release};{other_watched_env}");
|
|
|
|
write_if_changed(&env_file, ¤t_env);
|
|
}
|