refactor: streamline BuildFlags initialization across multiple modules
This commit is contained in:
@@ -870,12 +870,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_default_configure_install_dirs_respects_explicit_user_overrides() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.configure = vec![
|
||||
"--sbindir=/sbin".to_string(),
|
||||
"--libdir=/custom/lib".to_string(),
|
||||
"--datadir=/custom/share".to_string(),
|
||||
];
|
||||
let flags = BuildFlags {
|
||||
configure: vec![
|
||||
"--sbindir=/sbin".to_string(),
|
||||
"--libdir=/custom/lib".to_string(),
|
||||
"--datadir=/custom/share".to_string(),
|
||||
],
|
||||
..BuildFlags::default()
|
||||
};
|
||||
let help = "--bindir=DIR --sbindir=DIR --libdir=DIR --datadir=DIR";
|
||||
let args = default_configure_install_dirs(&flags, Some(help));
|
||||
assert!(!args.iter().any(|a| a.starts_with("--sbindir=")));
|
||||
@@ -1123,8 +1125,10 @@ foo: bar
|
||||
|
||||
#[test]
|
||||
fn test_resolve_configure_path_uses_configure_file_and_expands_vars() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.configure_file = "build-aux/$name-configure".into();
|
||||
let flags = BuildFlags {
|
||||
configure_file: "build-aux/$name-configure".into(),
|
||||
..BuildFlags::default()
|
||||
};
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".into(),
|
||||
|
||||
@@ -436,10 +436,10 @@ mod tests {
|
||||
let spec_dir = tmp.path().join("specdir");
|
||||
let external = tmp.path().join("external");
|
||||
let expanded = src_root.join("x-1.0").join("sub");
|
||||
std::fs::create_dir_all(&src_root.join("sub")).unwrap();
|
||||
std::fs::create_dir_all(src_root.join("sub")).unwrap();
|
||||
std::fs::create_dir_all(&expanded).unwrap();
|
||||
// create directories for candidates
|
||||
std::fs::create_dir_all(&spec_dir.join("../llvm")).unwrap();
|
||||
std::fs::create_dir_all(spec_dir.join("../llvm")).unwrap();
|
||||
std::fs::create_dir_all(&external).unwrap();
|
||||
|
||||
let spec = PackageSpec {
|
||||
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
spec.spec_dir = spec_dir.path().to_path_buf();
|
||||
|
||||
// src_dir is empty; build() should copy build.sh from spec_dir and run it (no-op)
|
||||
let _ = build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
|
||||
build(&spec, tmp_src.path(), tmp_dest.path(), None, true)?;
|
||||
// If we reached here, build() succeeded and build.sh was copied into src
|
||||
assert!(tmp_src.path().join("build.sh").exists());
|
||||
Ok(())
|
||||
|
||||
@@ -277,9 +277,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_meson_setup_args_honor_explicit_prefix() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.prefix = "/usr".to_string();
|
||||
flags.configure = vec!["--prefix=/opt".to_string()];
|
||||
let flags = BuildFlags {
|
||||
prefix: "/usr".to_string(),
|
||||
configure: vec!["--prefix=/opt".to_string()],
|
||||
..BuildFlags::default()
|
||||
};
|
||||
|
||||
let args = meson_setup_args(&flags, None, &[]);
|
||||
assert_eq!(args.iter().filter(|a| a.starts_with("--prefix")).count(), 1);
|
||||
|
||||
+8
-6
@@ -271,9 +271,11 @@ mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn mk_spec(cflags: Vec<&str>, ldflags: Vec<&str>) -> PackageSpec {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.cflags = cflags.into_iter().map(String::from).collect();
|
||||
flags.ldflags = ldflags.into_iter().map(String::from).collect();
|
||||
let flags = BuildFlags {
|
||||
cflags: cflags.into_iter().map(String::from).collect(),
|
||||
ldflags: ldflags.into_iter().map(String::from).collect(),
|
||||
..BuildFlags::default()
|
||||
};
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "env-test".into(),
|
||||
@@ -323,9 +325,9 @@ mod tests {
|
||||
prepare_command(&mut cmd, &vec![("MYVAR".to_string(), "myval".to_string())]);
|
||||
|
||||
let envs: HashMap<_, _> = cmd.get_envs().collect();
|
||||
assert!(envs.get(OsStr::new("PATH")).is_some());
|
||||
assert!(envs.get(OsStr::new("HOME")).is_some());
|
||||
assert!(envs.get(OsStr::new("FORBIDDEN")).is_none());
|
||||
assert!(envs.contains_key(OsStr::new("PATH")));
|
||||
assert!(envs.contains_key(OsStr::new("HOME")));
|
||||
assert!(!envs.contains_key(OsStr::new("FORBIDDEN")));
|
||||
assert_eq!(
|
||||
envs.get(OsStr::new("SHELL")),
|
||||
Some(&Some(std::ffi::OsString::from("/bin/sh").as_os_str()))
|
||||
|
||||
+1
-1
@@ -611,7 +611,7 @@ cflags += ["-g"]
|
||||
let orig_home = std::env::var_os("HOME");
|
||||
let fake_home = tempfile::tempdir().unwrap();
|
||||
unsafe {
|
||||
std::env::set_var("HOME", &fake_home.path());
|
||||
std::env::set_var("HOME", fake_home.path());
|
||||
}
|
||||
|
||||
let config = Config::for_rootfs(Path::new("/"));
|
||||
|
||||
+85
-85
@@ -459,6 +459,91 @@ pub fn calculate_upgrade_paths(
|
||||
Ok(remove_paths)
|
||||
}
|
||||
|
||||
/// Get set of all installed package names
|
||||
pub fn get_installed_packages(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
if !db_path.exists() {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let mut stmt = conn.prepare("SELECT name FROM packages")?;
|
||||
let names: HashSet<String> = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Get set of all provided package names (alternatives)
|
||||
pub fn get_all_provides(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
if !db_path.exists() {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let mut stmt = conn.prepare("SELECT provides_name FROM provides")?;
|
||||
let names: HashSet<String> = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Get version of a specific installed package
|
||||
pub fn get_package_version(db_path: &Path, name: &str) -> Result<Option<String>> {
|
||||
if !db_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let version: Option<String> = conn
|
||||
.query_row(
|
||||
"SELECT version FROM packages WHERE name = ?1",
|
||||
params![name],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok();
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// Find the installed package that owns a filesystem path from the local DB.
|
||||
pub fn owns_path(db_path: &Path, path: &Path) -> Result<Option<String>> {
|
||||
if !db_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let normalized = path
|
||||
.to_string_lossy()
|
||||
.trim_start_matches('/')
|
||||
.trim_start_matches("./")
|
||||
.to_string();
|
||||
if normalized.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let owner = conn
|
||||
.query_row(
|
||||
"SELECT p.name
|
||||
FROM files f
|
||||
JOIN packages p ON p.id = f.package_id
|
||||
WHERE f.path = ?1
|
||||
LIMIT 1",
|
||||
params![normalized],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok();
|
||||
Ok(owner)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -733,88 +818,3 @@ mod tests {
|
||||
assert!(!files.contains(&"usr/bin/shared_dir/old_file".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get set of all installed package names
|
||||
pub fn get_installed_packages(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
if !db_path.exists() {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let mut stmt = conn.prepare("SELECT name FROM packages")?;
|
||||
let names: HashSet<String> = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Get set of all provided package names (alternatives)
|
||||
pub fn get_all_provides(db_path: &Path) -> Result<std::collections::HashSet<String>> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
if !db_path.exists() {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let mut stmt = conn.prepare("SELECT provides_name FROM provides")?;
|
||||
let names: HashSet<String> = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
/// Get version of a specific installed package
|
||||
pub fn get_package_version(db_path: &Path, name: &str) -> Result<Option<String>> {
|
||||
if !db_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let version: Option<String> = conn
|
||||
.query_row(
|
||||
"SELECT version FROM packages WHERE name = ?1",
|
||||
params![name],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok();
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// Find the installed package that owns a filesystem path from the local DB.
|
||||
pub fn owns_path(db_path: &Path, path: &Path) -> Result<Option<String>> {
|
||||
if !db_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let normalized = path
|
||||
.to_string_lossy()
|
||||
.trim_start_matches('/')
|
||||
.trim_start_matches("./")
|
||||
.to_string();
|
||||
if normalized.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let conn = Connection::open(db_path)?;
|
||||
init_db(&conn)?;
|
||||
let owner = conn
|
||||
.query_row(
|
||||
"SELECT p.name
|
||||
FROM files f
|
||||
JOIN packages p ON p.id = f.package_id
|
||||
WHERE f.path = ?1
|
||||
LIMIT 1",
|
||||
params![normalized],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.ok();
|
||||
Ok(owner)
|
||||
}
|
||||
|
||||
+4
-2
@@ -2151,7 +2151,7 @@ optional = []
|
||||
let indexed = manager.read_indexed_package(&pkg_path).unwrap();
|
||||
manager.insert_indexed_package(&mut conn, indexed).unwrap();
|
||||
|
||||
let (name, version, revision, desc, home, lic, sha256, sha512): (
|
||||
type PackageRow = (
|
||||
String,
|
||||
String,
|
||||
i64,
|
||||
@@ -2160,7 +2160,9 @@ optional = []
|
||||
Option<String>,
|
||||
String,
|
||||
String,
|
||||
) = conn
|
||||
);
|
||||
|
||||
let (name, version, revision, desc, home, lic, sha256, sha512): PackageRow = conn
|
||||
.query_row(
|
||||
"SELECT name, version, revision, description, homepage, license, sha256, sha512 FROM packages",
|
||||
[],
|
||||
|
||||
+31
-29
@@ -1477,35 +1477,37 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn spec_to_minimal_toml_includes_extended_build_flags() {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.source_subdir = "project/subdir".into();
|
||||
flags.configure = vec!["--disable-static".into(), "--enable-foo".into()];
|
||||
flags.configure_file = "build-aux/configure".into();
|
||||
flags.post_configure = vec!["./configure-helper.sh".into()];
|
||||
flags.post_compile = vec!["make check".into()];
|
||||
flags.post_install = vec!["strip $DESTDIR/usr/bin/foo".into()];
|
||||
flags.makefile_commands = vec!["make".into()];
|
||||
flags.makefile_install_commands = vec!["make DESTDIR=$DESTDIR install".into()];
|
||||
flags.cargs = vec!["--locked".into()];
|
||||
flags.config_settings = vec!["editable_mode=compat".into()];
|
||||
flags.rustflags = vec!["-Ctarget-cpu=native".into()];
|
||||
flags.cxxflags = vec!["-O2".into(), "-fno-rtti".into()];
|
||||
flags.ltoflags = vec!["-flto=auto".into()];
|
||||
flags.target = "x86_64-unknown-linux-gnu".into();
|
||||
flags.keep = vec!["etc/locale.gen".into()];
|
||||
flags.use_lto = false;
|
||||
flags.no_flags = true;
|
||||
flags.no_strip = true;
|
||||
flags.no_delete_static = true;
|
||||
flags.no_compress_man = true;
|
||||
flags.skip_tests = true;
|
||||
flags.makeflags = "-j12 --output-sync=target".into();
|
||||
flags.make_vars = vec!["V=1".into()];
|
||||
flags.make_dirs = vec!["lib".into(), "libelf".into()];
|
||||
flags.make_test_vars = vec!["TESTS=unit".into()];
|
||||
flags.make_test_dirs = vec!["tests".into()];
|
||||
flags.make_install_vars = vec!["STRIPPROG=true".into()];
|
||||
flags.make_install_dirs = vec!["lib".into(), "apps".into()];
|
||||
let flags = BuildFlags {
|
||||
source_subdir: "project/subdir".into(),
|
||||
configure: vec!["--disable-static".into(), "--enable-foo".into()],
|
||||
configure_file: "build-aux/configure".into(),
|
||||
post_configure: vec!["./configure-helper.sh".into()],
|
||||
post_compile: vec!["make check".into()],
|
||||
post_install: vec!["strip $DESTDIR/usr/bin/foo".into()],
|
||||
makefile_commands: vec!["make".into()],
|
||||
makefile_install_commands: vec!["make DESTDIR=$DESTDIR install".into()],
|
||||
cargs: vec!["--locked".into()],
|
||||
config_settings: vec!["editable_mode=compat".into()],
|
||||
rustflags: vec!["-Ctarget-cpu=native".into()],
|
||||
cxxflags: vec!["-O2".into(), "-fno-rtti".into()],
|
||||
ltoflags: vec!["-flto=auto".into()],
|
||||
target: "x86_64-unknown-linux-gnu".into(),
|
||||
keep: vec!["etc/locale.gen".into()],
|
||||
use_lto: false,
|
||||
no_flags: true,
|
||||
no_strip: true,
|
||||
no_delete_static: true,
|
||||
no_compress_man: true,
|
||||
skip_tests: true,
|
||||
makeflags: "-j12 --output-sync=target".into(),
|
||||
make_vars: vec!["V=1".into()],
|
||||
make_dirs: vec!["lib".into(), "libelf".into()],
|
||||
make_test_vars: vec!["TESTS=unit".into()],
|
||||
make_test_dirs: vec!["tests".into()],
|
||||
make_install_vars: vec!["STRIPPROG=true".into()],
|
||||
make_install_dirs: vec!["lib".into(), "apps".into()],
|
||||
..BuildFlags::default()
|
||||
};
|
||||
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
|
||||
+35
-1
@@ -1279,6 +1279,35 @@ type = "custom"
|
||||
assert_eq!(spec.sources()[1].extract_dir, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_source_without_sha256_defaults_to_skip() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let path = tmp.path().join("pkg.toml");
|
||||
|
||||
std::fs::write(
|
||||
&path,
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "1.0"
|
||||
description = "d"
|
||||
homepage = "h"
|
||||
license = "MIT"
|
||||
|
||||
[source]
|
||||
url = "https://example.com/foo.tar.gz"
|
||||
extract_dir = "foo"
|
||||
|
||||
[build]
|
||||
type = "custom"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let spec = PackageSpec::from_file(&path).unwrap();
|
||||
assert_eq!(spec.sources()[0].sha256, "skip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_git_source_with_cherry_pick() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
@@ -2801,7 +2830,8 @@ pub struct Alternatives {
|
||||
pub struct Source {
|
||||
pub url: String,
|
||||
/// Checksum for the source (e.g. `sha256:...`, `sha512:...`, `md5:...`, or raw SHA256 hex).
|
||||
/// Use `skip` to bypass verification.
|
||||
/// Defaults to `skip` when omitted.
|
||||
#[serde(default = "default_source_sha256")]
|
||||
pub sha256: String,
|
||||
/// Directory name after extraction (supports $name, $version)
|
||||
pub extract_dir: String,
|
||||
@@ -2857,6 +2887,10 @@ pub struct ManualSource {
|
||||
pub dest: Option<String>,
|
||||
}
|
||||
|
||||
fn default_source_sha256() -> String {
|
||||
"skip".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneOrManySources {
|
||||
|
||||
+111
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use git2::{Cred, CredentialType, FetchOptions, Oid, RemoteCallbacks, Repository};
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use inquire::Password;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
@@ -47,8 +48,16 @@ pub fn checkout(
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid mirror path"))?;
|
||||
|
||||
crate::log_info!("Cloning git source into {}...", checkout_dir.display());
|
||||
Repository::clone(mirror_url, checkout_dir)
|
||||
let checkout_progress = CheckoutProgress::new(format!("git {}", pkgname));
|
||||
let mut checkout = git2::build::CheckoutBuilder::new();
|
||||
checkout_progress.attach(&mut checkout);
|
||||
|
||||
let mut builder = git2::build::RepoBuilder::new();
|
||||
builder.with_checkout(checkout);
|
||||
builder
|
||||
.clone(mirror_url, checkout_dir)
|
||||
.with_context(|| format!("Failed to clone from mirror for {}", url))?;
|
||||
checkout_progress.finish("checkout complete");
|
||||
|
||||
let repo = Repository::open(checkout_dir)?;
|
||||
checkout_rev(&repo, rev).with_context(|| format!("Failed to checkout revision '{}'", rev))?;
|
||||
@@ -136,13 +145,15 @@ fn ensure_mirror(url: &str, mirror_dir: &Path, pkgname: &str) -> Result<()> {
|
||||
if !mirror_dir.exists() {
|
||||
crate::log_info!("Cloning git mirror for {} ({})...", pkgname, url);
|
||||
let mut fo = FetchOptions::new();
|
||||
fo.remote_callbacks(remote_callbacks());
|
||||
let transfer_progress = TransferProgress::new(format!("git {}", pkgname));
|
||||
fo.remote_callbacks(remote_callbacks(Some(transfer_progress.bar()), url));
|
||||
let mut builder = git2::build::RepoBuilder::new();
|
||||
builder.fetch_options(fo);
|
||||
builder.bare(true);
|
||||
builder
|
||||
.clone(url, mirror_dir)
|
||||
.with_context(|| format!("Failed to clone git mirror: {}", url))?;
|
||||
transfer_progress.finish("mirror ready");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -156,12 +167,14 @@ fn ensure_mirror(url: &str, mirror_dir: &Path, pkgname: &str) -> Result<()> {
|
||||
.with_context(|| format!("Failed to create remote for {}", url))?;
|
||||
|
||||
let mut fo = FetchOptions::new();
|
||||
fo.remote_callbacks(remote_callbacks());
|
||||
let transfer_progress = TransferProgress::new(format!("git {}", pkgname));
|
||||
fo.remote_callbacks(remote_callbacks(Some(transfer_progress.bar()), url));
|
||||
|
||||
// Fetch all remote refs (tags + heads). Empty refspec uses default.
|
||||
remote
|
||||
.fetch(&[] as &[&str], Some(&mut fo), None)
|
||||
.with_context(|| format!("Failed to fetch updates for {}", url))?;
|
||||
transfer_progress.finish("mirror updated");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -261,17 +274,110 @@ fn resolve_remote_head_like<'a>(repo: &'a Repository) -> Result<Option<git2::Obj
|
||||
Ok(Some(repo.find_object(candidates[0].1, None)?))
|
||||
}
|
||||
|
||||
fn remote_callbacks() -> RemoteCallbacks<'static> {
|
||||
fn remote_callbacks(progress_bar: Option<ProgressBar>, _label: &str) -> RemoteCallbacks<'static> {
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
let mut credential_state = CredentialState::default();
|
||||
|
||||
callbacks.credentials(move |url, username_from_url, allowed| {
|
||||
credential_state.provide(url, username_from_url, allowed)
|
||||
});
|
||||
if let Some(progress_bar) = progress_bar {
|
||||
callbacks.transfer_progress(move |stats| {
|
||||
let total_objects = stats.total_objects() as u64;
|
||||
if total_objects > 0 {
|
||||
progress_bar.set_length(total_objects);
|
||||
progress_bar.set_position(stats.received_objects() as u64);
|
||||
}
|
||||
|
||||
progress_bar.set_message(format!(
|
||||
"{} obj, {} delta, {} bytes",
|
||||
stats.received_objects(),
|
||||
stats.indexed_deltas(),
|
||||
stats.received_bytes()
|
||||
));
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
callbacks
|
||||
}
|
||||
|
||||
struct TransferProgress {
|
||||
bar: ProgressBar,
|
||||
}
|
||||
|
||||
impl TransferProgress {
|
||||
fn new(prefix: String) -> Self {
|
||||
let bar = ProgressBar::new(1);
|
||||
bar.set_draw_target(progress_draw_target());
|
||||
bar.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("{prefix:.bold} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
||||
.unwrap_or_else(|_| ProgressStyle::default_bar())
|
||||
.progress_chars("#>-"),
|
||||
);
|
||||
bar.set_prefix(prefix);
|
||||
bar.set_message("starting transfer");
|
||||
Self { bar }
|
||||
}
|
||||
|
||||
fn bar(&self) -> ProgressBar {
|
||||
self.bar.clone()
|
||||
}
|
||||
|
||||
fn finish(&self, message: &str) {
|
||||
self.bar.finish_and_clear();
|
||||
crate::log_info!("{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckoutProgress {
|
||||
bar: ProgressBar,
|
||||
}
|
||||
|
||||
impl CheckoutProgress {
|
||||
fn new(prefix: String) -> Self {
|
||||
let bar = ProgressBar::new(1);
|
||||
bar.set_draw_target(progress_draw_target());
|
||||
bar.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("{prefix:.bold} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
||||
.unwrap_or_else(|_| ProgressStyle::default_bar())
|
||||
.progress_chars("#>-"),
|
||||
);
|
||||
bar.set_prefix(prefix);
|
||||
bar.set_message("preparing checkout");
|
||||
Self { bar }
|
||||
}
|
||||
|
||||
fn attach(&self, checkout: &mut git2::build::CheckoutBuilder<'static>) {
|
||||
let bar = self.bar.clone();
|
||||
checkout.progress(move |path, current, total| {
|
||||
let total = total as u64;
|
||||
if total > 0 {
|
||||
bar.set_length(total);
|
||||
bar.set_position(current as u64);
|
||||
}
|
||||
if let Some(path) = path {
|
||||
bar.set_message(path.display().to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn finish(&self, message: &str) {
|
||||
self.bar.finish_and_clear();
|
||||
crate::log_info!("{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_draw_target() -> ProgressDrawTarget {
|
||||
if io::stderr().is_terminal() {
|
||||
ProgressDrawTarget::stderr()
|
||||
} else {
|
||||
ProgressDrawTarget::hidden()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CredentialState {
|
||||
username: Option<String>,
|
||||
@@ -458,7 +564,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
checkout_rev(&repo, "HEAD").unwrap();
|
||||
assert_eq!(repo.head_detached().unwrap(), true);
|
||||
assert!(repo.head_detached().unwrap());
|
||||
assert_eq!(repo.head().unwrap().target().unwrap(), commit_oid);
|
||||
}
|
||||
|
||||
|
||||
+142
-23
@@ -313,6 +313,29 @@ fn prepare_one(
|
||||
.map(|rev| spec.expand_vars(rev))
|
||||
.collect();
|
||||
|
||||
// Treat `<url>#<rev>` and bare `*.git` sources as git before local file://
|
||||
// handling so `file://...repo.git#tag` resolves through the git checkout path.
|
||||
if let Some((base, rev)) = split_git_url(&url) {
|
||||
let checkout_dir = build_dir.join(&extract_dir_name);
|
||||
if checkout_dir.exists() && checkout_dir.join(".depot_state").exists() {
|
||||
crate::log_info!(
|
||||
"Resuming build in existing git directory: {}",
|
||||
checkout_dir.display()
|
||||
);
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
git::checkout(
|
||||
&base,
|
||||
&rev,
|
||||
&checkout_dir,
|
||||
&cache_dir.join("git"),
|
||||
&spec.package.name,
|
||||
&cherry_pick_revs,
|
||||
)?;
|
||||
hooks::post_extract(spec, source, &checkout_dir, cache_dir)?;
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
|
||||
// Local file:// handling (directories or archives)
|
||||
if let Some(path_str) = url.strip_prefix("file://") {
|
||||
let local_path = PathBuf::from(path_str);
|
||||
@@ -354,29 +377,6 @@ fn prepare_one(
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristic: if the URL contains '#', treat it as a git URL with a revision.
|
||||
// (except when it clearly looks like an archive URL)
|
||||
if let Some((base, rev)) = split_git_url(&url) {
|
||||
let checkout_dir = build_dir.join(&extract_dir_name);
|
||||
if checkout_dir.exists() && checkout_dir.join(".depot_state").exists() {
|
||||
crate::log_info!(
|
||||
"Resuming build in existing git directory: {}",
|
||||
checkout_dir.display()
|
||||
);
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
git::checkout(
|
||||
&base,
|
||||
&rev,
|
||||
&checkout_dir,
|
||||
&cache_dir.join("git"),
|
||||
&spec.package.name,
|
||||
&cherry_pick_revs,
|
||||
)?;
|
||||
hooks::post_extract(spec, source, &checkout_dir, cache_dir)?;
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
|
||||
if !source.cherry_pick.is_empty() {
|
||||
bail!(
|
||||
"source.cherry_pick is only supported for git sources (got URL: {})",
|
||||
@@ -459,6 +459,89 @@ mod tests {
|
||||
Alternatives, Build, BuildFlags, BuildType, Dependencies, ManualSource, PackageInfo,
|
||||
PackageSpec, Source,
|
||||
};
|
||||
use git2::{Oid, Repository};
|
||||
use std::path::Path;
|
||||
|
||||
fn commit_file(repo: &Repository, workdir: &Path, rel: &str, data: &str) -> Oid {
|
||||
let full_path = workdir.join(rel);
|
||||
if let Some(parent) = full_path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
std::fs::write(&full_path, data).unwrap();
|
||||
|
||||
let mut index = repo.index().unwrap();
|
||||
index.add_path(Path::new(rel)).unwrap();
|
||||
let tree_id = index.write_tree().unwrap();
|
||||
let tree = repo.find_tree(tree_id).unwrap();
|
||||
let sig = git2::Signature::now("depot-test", "depot@example.test").unwrap();
|
||||
let mut parents = Vec::new();
|
||||
if let Ok(head) = repo.head()
|
||||
&& let Some(oid) = head.target()
|
||||
{
|
||||
parents.push(repo.find_commit(oid).unwrap());
|
||||
}
|
||||
let parent_refs: Vec<&git2::Commit<'_>> = parents.iter().collect();
|
||||
|
||||
repo.commit(Some("HEAD"), &sig, &sig, "test", &tree, &parent_refs)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn make_git_source_spec(source_url: String, extract_dir: &str) -> PackageSpec {
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".into(),
|
||||
version: "1.0".into(),
|
||||
revision: 1,
|
||||
description: "d".into(),
|
||||
homepage: "h".into(),
|
||||
license: vec!["MIT".into()],
|
||||
},
|
||||
packages: Vec::new(),
|
||||
alternatives: Alternatives::default(),
|
||||
manual_sources: Vec::new(),
|
||||
source: vec![Source {
|
||||
url: source_url,
|
||||
sha256: "skip".into(),
|
||||
extract_dir: extract_dir.into(),
|
||||
patches: Vec::new(),
|
||||
post_extract: Vec::new(),
|
||||
cherry_pick: Vec::new(),
|
||||
}],
|
||||
build: Build {
|
||||
build_type: BuildType::Custom,
|
||||
flags: BuildFlags::default(),
|
||||
},
|
||||
dependencies: Dependencies::default(),
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
spec_dir: PathBuf::from("."),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_remote_git_repo() -> (tempfile::TempDir, String, Oid, Oid) {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let remote_dir = tmp.path().join("origin.git");
|
||||
let workdir = tmp.path().join("work");
|
||||
|
||||
Repository::init_bare(&remote_dir).unwrap();
|
||||
let repo = Repository::init(&workdir).unwrap();
|
||||
let tagged = commit_file(&repo, &workdir, "README", "tagged\n");
|
||||
let tag_target = repo.find_object(tagged, None).unwrap();
|
||||
repo.tag_lightweight("v1.0.0", &tag_target, false).unwrap();
|
||||
let hashed = commit_file(&repo, &workdir, "README", "hashed\n");
|
||||
|
||||
let branch_ref = repo.head().unwrap().name().unwrap().to_string();
|
||||
let mut remote = repo.remote("origin", remote_dir.to_str().unwrap()).unwrap();
|
||||
let push_specs = [
|
||||
format!("{branch_ref}:{branch_ref}"),
|
||||
"refs/tags/v1.0.0:refs/tags/v1.0.0".to_string(),
|
||||
];
|
||||
let push_spec_refs: Vec<&String> = push_specs.iter().collect();
|
||||
remote.push(&push_spec_refs, None).unwrap();
|
||||
|
||||
let remote_url = url::Url::from_file_path(&remote_dir).unwrap().to_string();
|
||||
(tmp, remote_url, tagged, hashed)
|
||||
}
|
||||
|
||||
fn mk_spec_with_manuals(spec_dir: PathBuf, manuals: Vec<ManualSource>) -> PackageSpec {
|
||||
PackageSpec {
|
||||
@@ -579,6 +662,42 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_one_checks_out_git_tag_revision() {
|
||||
let (_tmp, remote_url, tagged, _hashed) = make_remote_git_repo();
|
||||
let cache_dir = tempfile::tempdir().unwrap();
|
||||
let build_dir = tempfile::tempdir().unwrap();
|
||||
let spec = make_git_source_spec(format!("{remote_url}#v1.0.0"), "src-tag");
|
||||
|
||||
let checkout_dir =
|
||||
prepare_one(&spec, &spec.source[0], cache_dir.path(), build_dir.path()).unwrap();
|
||||
let repo = Repository::open(&checkout_dir).unwrap();
|
||||
|
||||
assert_eq!(repo.head().unwrap().target().unwrap(), tagged);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(checkout_dir.join("README")).unwrap(),
|
||||
"tagged\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_one_checks_out_git_commit_hash_revision() {
|
||||
let (_tmp, remote_url, _tagged, hashed) = make_remote_git_repo();
|
||||
let cache_dir = tempfile::tempdir().unwrap();
|
||||
let build_dir = tempfile::tempdir().unwrap();
|
||||
let spec = make_git_source_spec(format!("{remote_url}#{hashed}"), "src-hash");
|
||||
|
||||
let checkout_dir =
|
||||
prepare_one(&spec, &spec.source[0], cache_dir.path(), build_dir.path()).unwrap();
|
||||
let repo = Repository::open(&checkout_dir).unwrap();
|
||||
|
||||
assert_eq!(repo.head().unwrap().target().unwrap(), hashed);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(checkout_dir.join("README")).unwrap(),
|
||||
"hashed\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_file_hash_accepts_multiple_algorithms() {
|
||||
use sha2::{Digest, Sha256, Sha512};
|
||||
|
||||
+5
-3
@@ -772,9 +772,11 @@ mod tests {
|
||||
use crate::package::{Build, BuildFlags, BuildType, Dependencies, PackageInfo, PackageSpec};
|
||||
|
||||
fn mk_spec_for_stage_processing() -> PackageSpec {
|
||||
let mut flags = BuildFlags::default();
|
||||
flags.no_strip = true;
|
||||
flags.no_compress_man = true;
|
||||
let flags = BuildFlags {
|
||||
no_strip: true,
|
||||
no_compress_man: true,
|
||||
..BuildFlags::default()
|
||||
};
|
||||
PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".into(),
|
||||
|
||||
Reference in New Issue
Block a user