mirror of
https://github.com/ankitects/anki.git
synced 2025-11-12 15:47:12 -05:00
All platforms: - rename scripts/ to tools/: Bazelisk expects to find its wrapper script (used by the Mac changes below) in tools/. Rather than have a separate scripts/ and tools/, it's simpler to just move everything into tools/. - wheel outputs and binary bundles now go into .bazel/out/dist. While not technically Bazel build products, doing it this way ensures they get cleaned up when 'bazel clean' is run, and it keeps them out of the source folder. - update to the latest Bazel Windows changes: - bazel.bat has been removed, and tools\setup-env.bat has been added. Other scripts like .\run.bat will automatically call it to set up the environment. - because Bazel is now on the path, you can 'bazel test ...' from any folder, instead of having to do \anki\bazel. - the bat files can handle being called from any working directory, so things like running "\anki\tools\python" from c:\ will work. - build installer as part of bundling process Mac changes: - `arch -arch x86_64 bazel ...` will now automatically use a different build root, so that it is cheap to switch back and forth between archs on a new Mac. - tools/run-qt* will now automatically use Rosetta - disable jemalloc in Mac x86 build for now, as it won't build under Rosetta (perhaps due to its build scripts using $host_cpu instead of $target_cpu) - create app bundle as part of bundling process Linux changes: - remove arm64 orjson workaround in Linux bundle, as without a readily-available, relatively distro-agonstic PyQt/Qt build we can use, the arm64 Linux bundle is of very limited usefulness. - update Docker files for release build - include fcitx5 in both the qt5 and qt6 bundles - create tarballs as part of the bundling process
222 lines
6.7 KiB
Rust
222 lines
6.7 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
use std::{
|
|
fs,
|
|
io::prelude::*,
|
|
path::{Component, Path, PathBuf, Prefix},
|
|
process::Command,
|
|
};
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
use slog::*;
|
|
use tugger_windows_codesign::{CodeSigningCertificate, SigntoolSign, SystemStore, TimestampServer};
|
|
use walkdir::WalkDir;
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
|
|
let logger = Logger::root(slog_term::FullFormat::new(plain).build().fuse(), o!());
|
|
|
|
let args: Vec<_> = std::env::args().collect();
|
|
let build_folder = PathBuf::from(args.get(1).context("build folder")?);
|
|
let bazel_external = PathBuf::from(args.get(2).context("bazel external")?);
|
|
// bundle/build.py folder
|
|
let build_py_folder = PathBuf::from(args.get(3).context("build_py_folder")?);
|
|
let version = args.get(4).context("version")?;
|
|
|
|
let std_folder = build_folder.join("std");
|
|
let alt_folder = build_folder.join("alt");
|
|
let folders = &[&std_folder, &alt_folder];
|
|
|
|
for folder in folders {
|
|
fs::copy(
|
|
build_py_folder.join("win").join("anki-console.bat"),
|
|
folder.join("anki-console.bat"),
|
|
)
|
|
.context("anki-console")?;
|
|
}
|
|
|
|
println!("--- Copy in audio");
|
|
copy_in_audio(&std_folder, &bazel_external)?;
|
|
copy_in_audio(&alt_folder, &bazel_external)?;
|
|
|
|
println!("--- Build uninstaller");
|
|
build_installer(&std_folder, &build_folder, version, true).context("uninstaller")?;
|
|
|
|
// sign the anki.exe and uninstaller.exe in std, then copy into alt
|
|
println!("--- Sign binaries");
|
|
codesign(
|
|
&logger,
|
|
&[
|
|
&std_folder.join("anki.exe"),
|
|
&std_folder.join("uninstall.exe"),
|
|
],
|
|
)?;
|
|
for fname in &["anki.exe", "uninstall.exe"] {
|
|
fs::copy(std_folder.join(fname), alt_folder.join(fname))
|
|
.with_context(|| format!("copy {fname}"))?;
|
|
}
|
|
|
|
println!("--- Build manifest");
|
|
for folder in folders {
|
|
build_manifest(folder).context("manifest")?;
|
|
}
|
|
|
|
let mut installer_paths = vec![];
|
|
for (folder, variant) in folders.iter().zip(&["qt6", "qt5"]) {
|
|
println!(
|
|
"--- Build installer for {}",
|
|
folder.file_name().unwrap().to_str().unwrap()
|
|
);
|
|
build_installer(folder, &build_folder, version, false)?;
|
|
let installer_filename = format!("anki-{version}-windows-{variant}.exe");
|
|
let installer_path = build_folder
|
|
.join("..")
|
|
.join("dist")
|
|
.join(installer_filename);
|
|
|
|
fs::rename(build_folder.join("anki-setup.exe"), &installer_path)
|
|
.context("rename installer")?;
|
|
installer_paths.push(installer_path);
|
|
}
|
|
|
|
println!("--- Sign installers");
|
|
codesign(&logger, &installer_paths)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn build_installer(
|
|
variant_folder: &Path,
|
|
build_folder: &Path,
|
|
version: &str,
|
|
uninstaller: bool,
|
|
) -> Result<()> {
|
|
let rendered_nsi = include_str!("../anki.template.nsi")
|
|
.replace("@@SRC@@", variant_folder.to_str().unwrap())
|
|
.replace("@@VERSION@@", version);
|
|
let rendered_nsi_path = build_folder.join("anki.nsi");
|
|
fs::write(&rendered_nsi_path, rendered_nsi).context("anki.nsi")?;
|
|
fs::write(
|
|
build_folder.join("fileassoc.nsh"),
|
|
include_str!("../fileassoc.nsh"),
|
|
)?;
|
|
let mut cmd = Command::new("c:/program files (x86)/nsis/makensis.exe");
|
|
cmd.arg("-V3");
|
|
if uninstaller {
|
|
cmd.arg("-DWRITE_UNINSTALLER");
|
|
};
|
|
if option_env!("NO_COMPRESS").is_some() {
|
|
cmd.arg("-DNO_COMPRESS");
|
|
}
|
|
cmd.arg(rendered_nsi_path);
|
|
let status = cmd.status()?;
|
|
if !status.success() {
|
|
bail!("makensis failed");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Copy everything at the provided path into the bundle dir.
|
|
/// Excludes standard Bazel repo files.
|
|
fn extend_app_contents(source: &Path, bundle_dir: &Path) -> Result<()> {
|
|
let status = Command::new("rsync")
|
|
.arg("-a")
|
|
.args(["--exclude", "BUILD.bazel", "--exclude", "WORKSPACE"])
|
|
.arg(format!("{}/", path_for_rsync(source, true)?))
|
|
.arg(format!("{}/", path_for_rsync(bundle_dir, true)?))
|
|
.status()?;
|
|
if !status.success() {
|
|
bail!("error syncing {source:?}");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Munge path into a format rsync expects on Windows.
|
|
fn path_for_rsync(path: &Path, trailing_slash: bool) -> Result<String> {
|
|
let mut components = path.components();
|
|
let mut drive = None;
|
|
if let Some(Component::Prefix(prefix)) = components.next() {
|
|
if let Prefix::Disk(letter) = prefix.kind() {
|
|
drive = Some(char::from(letter));
|
|
}
|
|
};
|
|
let drive = drive.context("missing drive letter")?;
|
|
let remaining_path: PathBuf = components.collect();
|
|
Ok(format!(
|
|
"/{}{}{}",
|
|
drive,
|
|
remaining_path
|
|
.to_str()
|
|
.context("remaining_path")?
|
|
.replace("\\", "/"),
|
|
if trailing_slash { "/" } else { "" }
|
|
))
|
|
}
|
|
|
|
fn copy_in_audio(bundle_dir: &Path, bazel_external: &Path) -> Result<()> {
|
|
extend_app_contents(&bazel_external.join("audio_win_amd64"), bundle_dir)
|
|
}
|
|
|
|
fn codesign(logger: &Logger, paths: &[impl AsRef<Path>]) -> Result<()> {
|
|
if option_env!("ANKI_CODESIGN").is_none() {
|
|
return Ok(());
|
|
}
|
|
let cert = CodeSigningCertificate::Sha1Thumbprint(
|
|
SystemStore::My,
|
|
"60abdb9cb52b7dc13550e8838486a00e693770d9".into(),
|
|
);
|
|
let mut sign = SigntoolSign::new(cert);
|
|
sign.file_digest_algorithm("sha256")
|
|
.timestamp_server(TimestampServer::Rfc3161(
|
|
"http://time.certum.pl".into(),
|
|
"sha256".into(),
|
|
))
|
|
.verbose();
|
|
paths.iter().for_each(|path| {
|
|
sign.sign_file(path);
|
|
});
|
|
sign.run(logger)
|
|
}
|
|
|
|
// FIXME: check uninstall.exe required or not
|
|
fn build_manifest(base_path: &Path) -> Result<()> {
|
|
let mut buf = vec![];
|
|
for entry in WalkDir::new(base_path)
|
|
.min_depth(1)
|
|
.sort_by_file_name()
|
|
.into_iter()
|
|
{
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
let relative_path = path.strip_prefix(base_path)?;
|
|
write!(
|
|
&mut buf,
|
|
"{}\r\n",
|
|
relative_path.to_str().context("relative_path utf8")?
|
|
)?;
|
|
}
|
|
fs::write(base_path.join("anki.install-manifest"), buf)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
#[allow(unused_imports)]
|
|
use super::*;
|
|
|
|
#[test]
|
|
#[cfg(windows)]
|
|
fn test_path_for_rsync() -> Result<()> {
|
|
assert_eq!(
|
|
path_for_rsync(Path::new("c:\\foo\\bar"), false)?,
|
|
"/C/foo/bar"
|
|
);
|
|
assert_eq!(
|
|
path_for_rsync(Path::new("c:\\foo\\bar"), true)?,
|
|
"/C/foo/bar/"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|