Anki/build/ninja_gen/src/git.rs
Kai Knoblich 42cc2c913c
Add support for offline builds (#2963)
* CONTRIBUTORS: Add myself to the contributors list

* Add support for offline builds

Downloading files during build time is a non-starter for FreeBSD ports
(and presumably for other *BSD ports and some Linux distros as well).

In order to still be able to build Anki successfully, two new
environment variables have been added that can be set accordingly:

* NO_VENV: If set, the Python system environment is used instead of
  a venv. This is necessary if there are no usable Python wheels for a
  platform, e.g. PyQt6.

* OFFLINE_BUILD: If set, the git repository synchronization (translation
  files, build hash, etc.) is skipped.

To successfully build Anki offline, following conditions must be met:

1. All required dependencies (node, Python, rust, yarn, etc.) must be
   present in the build environment.

2. The offline repositories for the translation files must be
   copied/linked to ftl/qt-repo and ftl/core-repo.

3. The Python pseudo venv needs to be setup:

   $ mkdir out/pyenv/bin
   $ ln -s /path/to/python out/pyenv/bin/python
   $ ln -s /path/to/protoc-gen-mypy out/pyenv/bin/protoc-gen-mypy

4. Create the offline cache for yarn and use its own environment
   variable YARN_CACHE_FOLDER to it:

   YARN_CACHE_FOLDER=/path/to/the/yarn/cache
   $ /path/to/yarn install --ignore-scripts

5. Build Anki:

   $ /path/to/cargo build --package runner --release --verbose --verbose
   $ OFFLINE_BUILD=1 \
     NO_VENV=1 \
     ${WRKSRC}/out/rust/release/runner build wheels
2024-01-31 09:13:46 +10:00

84 lines
2.5 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 itertools::Itertools;
use super::*;
use crate::action::BuildAction;
use crate::input::BuildInput;
pub struct SyncSubmodule {
pub path: &'static str,
pub offline_build: bool,
}
impl BuildAction for SyncSubmodule {
fn command(&self) -> &str {
if self.offline_build {
"echo OFFLINE_BUILD is set, skipping git repository update for $path"
} else {
"git -c protocol.file.allow=always submodule update --init $path"
}
}
fn files(&mut self, build: &mut impl build::FilesHandle) {
if !self.offline_build {
if let Some(head) = locate_git_head() {
build.add_inputs("", head);
} else {
println!("Warning, .git/HEAD not found; submodules may be stale");
}
}
build.add_variable("path", self.path);
build.add_output_stamp(format!("git/{}", self.path));
}
fn on_first_instance(&self, build: &mut Build) -> Result<()> {
build.pool("git", 1);
Ok(())
}
fn concurrency_pool(&self) -> Option<&'static str> {
Some("git")
}
}
/// We check the mtime of .git/HEAD to detect when we should sync submodules.
/// If this repo is a submodule of another project, .git/HEAD will not exist,
/// and we fall back on .git/modules/*/HEAD in a parent folder instead.
fn locate_git_head() -> Option<BuildInput> {
let standard_path = Utf8Path::new(".git/HEAD");
if standard_path.exists() {
return Some(inputs![standard_path.to_string()]);
}
let mut folder = Utf8PathBuf::from_path_buf(
dunce::canonicalize(Utf8Path::new(".").canonicalize().unwrap()).unwrap(),
)
.unwrap();
loop {
let path = folder.join(".git").join("modules");
if path.exists() {
let heads = path
.read_dir_utf8()
.unwrap()
.filter_map(|p| {
let head = p.unwrap().path().join("HEAD");
if head.exists() {
Some(head.as_str().replace(':', "$:"))
} else {
None
}
})
.collect_vec();
return Some(inputs![heads]);
}
if let Some(parent) = folder.parent() {
folder = parent.to_owned();
} else {
return None;
}
}
}