From fd58dc6afcef0c7a26d4fcbc6990bc3b85add414 Mon Sep 17 00:00:00 2001 From: SFG545 Date: Sun, 8 Mar 2026 15:35:45 -0500 Subject: [PATCH] refactor: streamline BuildFlags initialization across multiple modules --- src/builder/autotools.rs | 20 +++-- src/builder/cmake.rs | 4 +- src/builder/custom.rs | 2 +- src/builder/meson.rs | 8 +- src/builder/mod.rs | 14 +-- src/config.rs | 2 +- src/db/mod.rs | 170 ++++++++++++++++++------------------- src/db/repo.rs | 6 +- src/package/interactive.rs | 60 ++++++------- src/package/spec.rs | 36 +++++++- src/source/git.rs | 116 +++++++++++++++++++++++-- src/source/mod.rs | 165 ++++++++++++++++++++++++++++++----- src/staging/mod.rs | 8 +- 13 files changed, 442 insertions(+), 169 deletions(-) diff --git a/src/builder/autotools.rs b/src/builder/autotools.rs index 5e0878d..7bc1bed 100755 --- a/src/builder/autotools.rs +++ b/src/builder/autotools.rs @@ -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(), diff --git a/src/builder/cmake.rs b/src/builder/cmake.rs index 6c6f469..fed7e51 100755 --- a/src/builder/cmake.rs +++ b/src/builder/cmake.rs @@ -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 { diff --git a/src/builder/custom.rs b/src/builder/custom.rs index 49e2015..ecf2424 100755 --- a/src/builder/custom.rs +++ b/src/builder/custom.rs @@ -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(()) diff --git a/src/builder/meson.rs b/src/builder/meson.rs index c750057..d76e518 100755 --- a/src/builder/meson.rs +++ b/src/builder/meson.rs @@ -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); diff --git a/src/builder/mod.rs b/src/builder/mod.rs index e743098..1f32588 100755 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -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())) diff --git a/src/config.rs b/src/config.rs index d5c280b..e2a259b 100755 --- a/src/config.rs +++ b/src/config.rs @@ -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("/")); diff --git a/src/db/mod.rs b/src/db/mod.rs index 9bfd954..8726f6d 100755 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -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> { + 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 = 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> { + 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 = 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> { + if !db_path.exists() { + return Ok(None); + } + + let conn = Connection::open(db_path)?; + init_db(&conn)?; + let version: Option = 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> { + 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> { - 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 = 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> { - 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 = 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> { - if !db_path.exists() { - return Ok(None); - } - - let conn = Connection::open(db_path)?; - init_db(&conn)?; - let version: Option = 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> { - 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) -} diff --git a/src/db/repo.rs b/src/db/repo.rs index 72754fb..7d0299c 100644 --- a/src/db/repo.rs +++ b/src/db/repo.rs @@ -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, - ) = 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", [], diff --git a/src/package/interactive.rs b/src/package/interactive.rs index 6740a2b..3f3ad14 100644 --- a/src/package/interactive.rs +++ b/src/package/interactive.rs @@ -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 { diff --git a/src/package/spec.rs b/src/package/spec.rs index 166b996..72d939c 100755 --- a/src/package/spec.rs +++ b/src/package/spec.rs @@ -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, } +fn default_source_sha256() -> String { + "skip".to_string() +} + #[derive(Debug, Deserialize)] #[serde(untagged)] enum OneOrManySources { diff --git a/src/source/git.rs b/src/source/git.rs index 19007c2..377bb26 100755 --- a/src/source/git.rs +++ b/src/source/git.rs @@ -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 RemoteCallbacks<'static> { +fn remote_callbacks(progress_bar: Option, _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, @@ -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); } diff --git a/src/source/mod.rs b/src/source/mod.rs index e0c9ea1..fa3f595 100755 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -313,6 +313,29 @@ fn prepare_one( .map(|rev| spec.expand_vars(rev)) .collect(); + // Treat `#` 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) -> 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}; diff --git a/src/staging/mod.rs b/src/staging/mod.rs index ed14db7..d36cc15 100755 --- a/src/staging/mod.rs +++ b/src/staging/mod.rs @@ -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(),