feat: update depot version to 0.32.1, add git clone support and enhance manual source handling
This commit is contained in:
Generated
+1
-1
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.32.0"
|
version = "0.32.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ar",
|
"ar",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.32.0"
|
version = "0.32.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'depot',
|
'depot',
|
||||||
version: '0.32.0',
|
version: '0.32.1',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
default_options: ['buildtype=release'],
|
default_options: ['buildtype=release'],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -314,6 +314,8 @@ pub enum InternalCommands {
|
|||||||
#[arg(long, default_value = "/usr")]
|
#[arg(long, default_value = "/usr")]
|
||||||
prefix: String,
|
prefix: String,
|
||||||
},
|
},
|
||||||
|
#[command(hide = true)]
|
||||||
|
Clone { repo: String, dest: Option<PathBuf> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
#[derive(Debug, Clone, Args)]
|
||||||
|
|||||||
+100
@@ -640,6 +640,36 @@ fn run_internal_command(command: InternalCommands) -> Result<()> {
|
|||||||
&env_vars,
|
&env_vars,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
InternalCommands::Clone { repo, dest } => {
|
||||||
|
let (base, rev) = crate::source::split_git_url(&repo).with_context(|| {
|
||||||
|
format!("Unsupported repository URL for internal clone: {}", repo)
|
||||||
|
})?;
|
||||||
|
let dest = if let Some(dest) = dest {
|
||||||
|
dest
|
||||||
|
} else {
|
||||||
|
let cwd =
|
||||||
|
std::env::current_dir().context("Failed to determine current directory")?;
|
||||||
|
cwd.join(crate::source::git_default_checkout_dir_name(&base))
|
||||||
|
};
|
||||||
|
if dest.exists() {
|
||||||
|
anyhow::bail!("Clone destination already exists: {}", dest.display());
|
||||||
|
}
|
||||||
|
let cache_root =
|
||||||
|
tempfile::tempdir().context("Failed to create temporary git cache for clone")?;
|
||||||
|
let label = dest
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.unwrap_or("clone");
|
||||||
|
crate::source::git_checkout(
|
||||||
|
&base,
|
||||||
|
&rev,
|
||||||
|
&dest,
|
||||||
|
&cache_root.path().join("git"),
|
||||||
|
label,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5974,6 +6004,8 @@ mod tests {
|
|||||||
UpdateArgs,
|
UpdateArgs,
|
||||||
};
|
};
|
||||||
use crate::test_support::TestEnv;
|
use crate::test_support::TestEnv;
|
||||||
|
use git2::{Oid, Repository};
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
static ASSUME_YES_TEST_LOCK: Mutex<()> = Mutex::new(());
|
static ASSUME_YES_TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||||
@@ -6071,6 +6103,74 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn commit_git_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_remote_git_repo() -> (tempfile::TempDir, String, 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_git_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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_internal_clone_checks_out_git_revision() {
|
||||||
|
let (_tmp, remote_url, tagged) = make_remote_git_repo();
|
||||||
|
let clone_root = tempfile::tempdir().unwrap();
|
||||||
|
let dest = clone_root.path().join("cloned-src");
|
||||||
|
|
||||||
|
run_internal_command(InternalCommands::Clone {
|
||||||
|
repo: format!("{remote_url}#v1.0.0"),
|
||||||
|
dest: Some(dest.clone()),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let repo = Repository::open(&dest).unwrap();
|
||||||
|
assert_eq!(repo.head().unwrap().target().unwrap(), tagged);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(dest.join("README")).unwrap(),
|
||||||
|
"tagged\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn register_installed_test_package(
|
fn register_installed_test_package(
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ fn prompt_manual_sources() -> Result<Vec<ManualSource>> {
|
|||||||
} else {
|
} else {
|
||||||
prompt_repeating_list(
|
prompt_repeating_list(
|
||||||
"Manual source URL",
|
"Manual source URL",
|
||||||
"Supports http(s), ftp, and file:// URLs",
|
"Supports http(s), ftp, file://, and git URLs like ...repo.git#rev",
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -3973,17 +3973,18 @@ pub struct ManualSource {
|
|||||||
/// Multiple filenames in the spec directory (local manual source mode).
|
/// Multiple filenames in the spec directory (local manual source mode).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
/// Remote URL to fetch (remote manual source mode).
|
/// Remote URL to fetch or clone (remote manual source mode).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
/// Multiple remote URLs to fetch (remote manual source mode).
|
/// Multiple remote URLs to fetch or clone (remote manual source mode).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub urls: Vec<String>,
|
pub urls: Vec<String>,
|
||||||
/// Checksum (optional, use "skip" to bypass verification).
|
/// Checksum (optional, use "skip" to bypass verification).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sha256: Option<String>,
|
pub sha256: Option<String>,
|
||||||
/// Destination path relative to build work directory.
|
/// Destination path relative to build work directory.
|
||||||
/// Defaults to `file` for local mode or a derived filename for URL mode.
|
/// Defaults to `file` for local mode, a derived filename for archive URLs,
|
||||||
|
/// or the repository directory name for git URLs.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dest: Option<String>,
|
pub dest: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-2
@@ -14,6 +14,7 @@ const DEPOT_HAUL_HELPER_ENV: &str = "DEPOT_HAUL_HELPER";
|
|||||||
const DEPOT_SUBDESTDIR_HELPER_ENV: &str = "DEPOT_SUBDESTDIR_HELPER";
|
const DEPOT_SUBDESTDIR_HELPER_ENV: &str = "DEPOT_SUBDESTDIR_HELPER";
|
||||||
const DEPOT_PYTHON_BUILD_HELPER_ENV: &str = "DEPOT_PYTHON_BUILD_HELPER";
|
const DEPOT_PYTHON_BUILD_HELPER_ENV: &str = "DEPOT_PYTHON_BUILD_HELPER";
|
||||||
const DEPOT_PYTHON_INSTALL_HELPER_ENV: &str = "DEPOT_PYTHON_INSTALL_HELPER";
|
const DEPOT_PYTHON_INSTALL_HELPER_ENV: &str = "DEPOT_PYTHON_INSTALL_HELPER";
|
||||||
|
const DEPOT_CLONE_HELPER_ENV: &str = "DEPOT_CLONE_HELPER";
|
||||||
const DEPOT_EXECUTABLE_ENV: &str = "DEPOT_EXECUTABLE";
|
const DEPOT_EXECUTABLE_ENV: &str = "DEPOT_EXECUTABLE";
|
||||||
|
|
||||||
/// Ephemeral helper command directory to prepend to PATH while running scripts.
|
/// Ephemeral helper command directory to prepend to PATH while running scripts.
|
||||||
@@ -26,6 +27,7 @@ pub struct ShellHelpers {
|
|||||||
subdestdir_path: PathBuf,
|
subdestdir_path: PathBuf,
|
||||||
python_build_path: PathBuf,
|
python_build_path: PathBuf,
|
||||||
python_install_path: PathBuf,
|
python_install_path: PathBuf,
|
||||||
|
clone_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShellHelpers {
|
impl ShellHelpers {
|
||||||
@@ -123,6 +125,24 @@ impl ShellHelpers {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clone_path = bin_dir.join("depot_clone");
|
||||||
|
fs::write(&clone_path, CLONE_SCRIPT).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to write shell helper command: {}",
|
||||||
|
clone_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&clone_path)
|
||||||
|
.with_context(|| format!("Failed to stat helper: {}", clone_path.display()))?
|
||||||
|
.permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&clone_path, perms)
|
||||||
|
.with_context(|| format!("Failed to chmod helper: {}", clone_path.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
let path_value = crate::runtime_env::prepend_helper_to_safe_path(&bin_dir);
|
let path_value = crate::runtime_env::prepend_helper_to_safe_path(&bin_dir);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -134,6 +154,7 @@ impl ShellHelpers {
|
|||||||
subdestdir_path,
|
subdestdir_path,
|
||||||
python_build_path,
|
python_build_path,
|
||||||
python_install_path,
|
python_install_path,
|
||||||
|
clone_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +187,11 @@ impl ShellHelpers {
|
|||||||
DEPOT_PYTHON_INSTALL_HELPER_ENV,
|
DEPOT_PYTHON_INSTALL_HELPER_ENV,
|
||||||
self.python_install_path.to_string_lossy().into_owned(),
|
self.python_install_path.to_string_lossy().into_owned(),
|
||||||
);
|
);
|
||||||
|
set_env_var(
|
||||||
|
env_vars,
|
||||||
|
DEPOT_CLONE_HELPER_ENV,
|
||||||
|
self.clone_path.to_string_lossy().into_owned(),
|
||||||
|
);
|
||||||
set_env_var(
|
set_env_var(
|
||||||
env_vars,
|
env_vars,
|
||||||
DEPOT_EXECUTABLE_ENV,
|
DEPOT_EXECUTABLE_ENV,
|
||||||
@@ -178,7 +204,7 @@ impl ShellHelpers {
|
|||||||
/// through `/bin/sh`, avoiding direct execution from mounts that may be `noexec`.
|
/// through `/bin/sh`, avoiding direct execution from mounts that may be `noexec`.
|
||||||
pub fn wrap_shell_command(command: &str) -> String {
|
pub fn wrap_shell_command(command: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"haul() {{ /bin/sh \"${{{DEPOT_HAUL_HELPER_ENV}:?}}\" \"$@\"; }}\nsubdestdir() {{ /bin/sh \"${{{DEPOT_SUBDESTDIR_HELPER_ENV}:?}}\" \"$@\"; }}\npython_build() {{ /bin/sh \"${{{DEPOT_PYTHON_BUILD_HELPER_ENV}:?}}\" \"$@\"; }}\npython_install() {{ /bin/sh \"${{{DEPOT_PYTHON_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\n{command}"
|
"haul() {{ /bin/sh \"${{{DEPOT_HAUL_HELPER_ENV}:?}}\" \"$@\"; }}\nsubdestdir() {{ /bin/sh \"${{{DEPOT_SUBDESTDIR_HELPER_ENV}:?}}\" \"$@\"; }}\npython_build() {{ /bin/sh \"${{{DEPOT_PYTHON_BUILD_HELPER_ENV}:?}}\" \"$@\"; }}\npython_install() {{ /bin/sh \"${{{DEPOT_PYTHON_INSTALL_HELPER_ENV}:?}}\" \"$@\"; }}\ndepot_clone() {{ /bin/sh \"${{{DEPOT_CLONE_HELPER_ENV}:?}}\" \"$@\"; }}\n{command}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +461,19 @@ fail() {
|
|||||||
exec "$DEPOT_EXECUTABLE" internal python-install "$@"
|
exec "$DEPOT_EXECUTABLE" internal python-install "$@"
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
const CLONE_SCRIPT: &str = r#"#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "depot_clone: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "${DEPOT_EXECUTABLE:-}" != "" ] || fail "DEPOT_EXECUTABLE is not set"
|
||||||
|
|
||||||
|
exec "$DEPOT_EXECUTABLE" internal clone "$@"
|
||||||
|
"#;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{INTERNAL_DEPOT_DIR, ShellHelpers, shell_ident_suffix, wrap_shell_command};
|
use super::{INTERNAL_DEPOT_DIR, ShellHelpers, shell_ident_suffix, wrap_shell_command};
|
||||||
@@ -517,14 +556,16 @@ mod tests {
|
|||||||
envs.iter()
|
envs.iter()
|
||||||
.any(|(key, _)| key == "DEPOT_PYTHON_INSTALL_HELPER")
|
.any(|(key, _)| key == "DEPOT_PYTHON_INSTALL_HELPER")
|
||||||
);
|
);
|
||||||
|
assert!(envs.iter().any(|(key, _)| key == "DEPOT_CLONE_HELPER"));
|
||||||
assert!(envs.iter().any(|(key, _)| key == "DEPOT_EXECUTABLE"));
|
assert!(envs.iter().any(|(key, _)| key == "DEPOT_EXECUTABLE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wrap_shell_command_exposes_python_helpers() {
|
fn wrap_shell_command_exposes_python_helpers() {
|
||||||
let wrapped = wrap_shell_command("python_build\npython_install");
|
let wrapped = wrap_shell_command("python_build\npython_install\ndepot_clone foo");
|
||||||
assert!(wrapped.contains("python_build()"));
|
assert!(wrapped.contains("python_build()"));
|
||||||
assert!(wrapped.contains("python_install()"));
|
assert!(wrapped.contains("python_install()"));
|
||||||
|
assert!(wrapped.contains("depot_clone()"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use sha2::{Digest, Sha256};
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, IsTerminal, Write};
|
use std::io::{self, IsTerminal, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
/// Checkout a repository URL at a specific revision into `checkout_dir`.
|
/// Checkout a repository URL at a specific revision into `checkout_dir`.
|
||||||
///
|
///
|
||||||
@@ -33,6 +34,14 @@ pub fn checkout(
|
|||||||
git_cache_dir.display()
|
git_cache_dir.display()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
if let Some(parent) = checkout_dir.parent() {
|
||||||
|
fs::create_dir_all(parent).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create parent directory for checkout: {}",
|
||||||
|
parent.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
let mirror_dir = git_cache_dir.join(mirror_key(url));
|
let mirror_dir = git_cache_dir.join(mirror_key(url));
|
||||||
ensure_mirror(url, &mirror_dir, pkgname, rev, cherry_pick_revs)?;
|
ensure_mirror(url, &mirror_dir, pkgname, rev, cherry_pick_revs)?;
|
||||||
@@ -76,6 +85,51 @@ pub fn checkout(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prime the git mirror cache for a repository/revision without creating a checkout.
|
||||||
|
pub fn prime_cache(
|
||||||
|
url: &str,
|
||||||
|
rev: &str,
|
||||||
|
git_cache_dir: &Path,
|
||||||
|
pkgname: &str,
|
||||||
|
cherry_pick_revs: &[String],
|
||||||
|
) -> Result<()> {
|
||||||
|
crate::interrupts::install().context("Failed to enable Ctrl-C handling for git operations")?;
|
||||||
|
fs::create_dir_all(git_cache_dir).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create git cache dir: {}",
|
||||||
|
git_cache_dir.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mirror_dir = git_cache_dir.join(mirror_key(url));
|
||||||
|
ensure_mirror(url, &mirror_dir, pkgname, rev, cherry_pick_revs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive a default checkout directory name from a git repository URL.
|
||||||
|
pub fn default_checkout_dir_name(url: &str) -> String {
|
||||||
|
let without_fragment = url.split('#').next().unwrap_or(url).trim_end_matches('/');
|
||||||
|
let last_segment = Url::parse(without_fragment)
|
||||||
|
.ok()
|
||||||
|
.and_then(|parsed| {
|
||||||
|
parsed
|
||||||
|
.path_segments()?
|
||||||
|
.rfind(|segment| !segment.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
without_fragment
|
||||||
|
.rsplit(['/', ':'])
|
||||||
|
.find(|segment| !segment.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "repo".to_string());
|
||||||
|
|
||||||
|
last_segment
|
||||||
|
.strip_suffix(".git")
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.unwrap_or(&last_segment)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_cherry_picks(repo: &Repository, cherry_pick_revs: &[String]) -> Result<()> {
|
fn apply_cherry_picks(repo: &Repository, cherry_pick_revs: &[String]) -> Result<()> {
|
||||||
if cherry_pick_revs.is_empty() {
|
if cherry_pick_revs.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
+145
-7
@@ -6,6 +6,10 @@ mod git;
|
|||||||
pub mod hooks;
|
pub mod hooks;
|
||||||
|
|
||||||
pub(crate) use git::authenticated_remote_callbacks;
|
pub(crate) use git::authenticated_remote_callbacks;
|
||||||
|
pub(crate) use git::{
|
||||||
|
checkout as git_checkout, default_checkout_dir_name as git_default_checkout_dir_name,
|
||||||
|
prime_cache as git_prime_cache,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::package::PackageSpec;
|
use crate::package::PackageSpec;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
@@ -56,7 +60,8 @@ fn manual_url_entries(manual: &crate::package::ManualSource) -> Vec<String> {
|
|||||||
///
|
///
|
||||||
/// Local entries are checked for existence and optional checksum correctness.
|
/// Local entries are checked for existence and optional checksum correctness.
|
||||||
/// Remote entries are fetched into the manual-source cache so build-time source
|
/// Remote entries are fetched into the manual-source cache so build-time source
|
||||||
/// preparation can reuse the verified result later.
|
/// preparation can reuse the verified result later. Git manual sources prime
|
||||||
|
/// their mirror cache and validate revision reachability.
|
||||||
pub fn preflight_manual_sources(spec: &PackageSpec, cache_dir: &Path) -> Result<()> {
|
pub fn preflight_manual_sources(spec: &PackageSpec, cache_dir: &Path) -> Result<()> {
|
||||||
if spec.manual_sources.is_empty() {
|
if spec.manual_sources.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -97,6 +102,24 @@ pub fn preflight_manual_sources(spec: &PackageSpec, cache_dir: &Path) -> Result<
|
|||||||
if !url_entries.is_empty() {
|
if !url_entries.is_empty() {
|
||||||
for raw_url in url_entries {
|
for raw_url in url_entries {
|
||||||
let expanded_url = expand_manual_source_value(spec, &raw_url);
|
let expanded_url = expand_manual_source_value(spec, &raw_url);
|
||||||
|
if let Some((base, rev)) = split_git_url(&expanded_url) {
|
||||||
|
if let Some(expected_hash) = manual.sha256.as_ref().filter(|h| *h != "skip") {
|
||||||
|
bail!(
|
||||||
|
"Manual git source {} cannot use checksum {}; pin the desired revision in the URL fragment instead",
|
||||||
|
expanded_url,
|
||||||
|
expected_hash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
git_prime_cache(
|
||||||
|
&base,
|
||||||
|
&rev,
|
||||||
|
&cache_dir.join("manual").join("git"),
|
||||||
|
&spec.package.name,
|
||||||
|
&[],
|
||||||
|
)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let parsed = Url::parse(&expanded_url)
|
let parsed = Url::parse(&expanded_url)
|
||||||
.with_context(|| format!("Invalid URL: {}", expanded_url))?;
|
.with_context(|| format!("Invalid URL: {}", expanded_url))?;
|
||||||
|
|
||||||
@@ -192,6 +215,19 @@ pub fn copy_manual_sources(spec: &PackageSpec, cache_dir: &Path, build_dir: &Pat
|
|||||||
if !url_entries.is_empty() {
|
if !url_entries.is_empty() {
|
||||||
for raw_url in url_entries {
|
for raw_url in url_entries {
|
||||||
let expanded_url = expand_manual_source_value(spec, &raw_url);
|
let expanded_url = expand_manual_source_value(spec, &raw_url);
|
||||||
|
if let Some((base, rev)) = split_git_url(&expanded_url) {
|
||||||
|
checkout_manual_git_source(
|
||||||
|
spec,
|
||||||
|
manual,
|
||||||
|
build_dir,
|
||||||
|
cache_dir,
|
||||||
|
&expanded_url,
|
||||||
|
&base,
|
||||||
|
&rev,
|
||||||
|
)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let parsed = Url::parse(&expanded_url)
|
let parsed = Url::parse(&expanded_url)
|
||||||
.with_context(|| format!("Invalid URL: {}", expanded_url))?;
|
.with_context(|| format!("Invalid URL: {}", expanded_url))?;
|
||||||
|
|
||||||
@@ -255,11 +291,7 @@ fn copy_manual_source_file(
|
|||||||
manual: &crate::package::ManualSource,
|
manual: &crate::package::ManualSource,
|
||||||
default_dest: &str,
|
default_dest: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let dest_name = if let Some(dest) = manual.dest.as_ref() {
|
let dest_name = manual_source_dest_name(spec, manual, default_dest);
|
||||||
expand_manual_source_value(spec, dest)
|
|
||||||
} else {
|
|
||||||
default_dest.to_string()
|
|
||||||
};
|
|
||||||
let dest_path = build_dir.join(&dest_name);
|
let dest_path = build_dir.join(&dest_name);
|
||||||
|
|
||||||
if let Some(parent) = dest_path.parent() {
|
if let Some(parent) = dest_path.parent() {
|
||||||
@@ -277,6 +309,69 @@ fn copy_manual_source_file(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn manual_source_dest_name(
|
||||||
|
spec: &PackageSpec,
|
||||||
|
manual: &crate::package::ManualSource,
|
||||||
|
default_dest: &str,
|
||||||
|
) -> String {
|
||||||
|
if let Some(dest) = manual.dest.as_ref() {
|
||||||
|
expand_manual_source_value(spec, dest)
|
||||||
|
} else {
|
||||||
|
default_dest.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_existing_path(path: &Path) -> Result<()> {
|
||||||
|
if !path.exists() && fs::symlink_metadata(path).is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let meta =
|
||||||
|
fs::symlink_metadata(path).with_context(|| format!("Failed to stat {}", path.display()))?;
|
||||||
|
if meta.file_type().is_dir() && !meta.file_type().is_symlink() {
|
||||||
|
fs::remove_dir_all(path)
|
||||||
|
.with_context(|| format!("Failed to remove directory {}", path.display()))?;
|
||||||
|
} else {
|
||||||
|
fs::remove_file(path)
|
||||||
|
.with_context(|| format!("Failed to remove file {}", path.display()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkout_manual_git_source(
|
||||||
|
spec: &PackageSpec,
|
||||||
|
manual: &crate::package::ManualSource,
|
||||||
|
build_dir: &Path,
|
||||||
|
cache_dir: &Path,
|
||||||
|
expanded_url: &str,
|
||||||
|
base: &str,
|
||||||
|
rev: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(expected_hash) = manual.sha256.as_ref().filter(|h| *h != "skip") {
|
||||||
|
bail!(
|
||||||
|
"Manual git source {} cannot use checksum {}; pin the desired revision in the URL fragment instead",
|
||||||
|
expanded_url,
|
||||||
|
expected_hash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_dest = git_default_checkout_dir_name(base);
|
||||||
|
let dest_name = manual_source_dest_name(spec, manual, &default_dest);
|
||||||
|
let dest_path = build_dir.join(&dest_name);
|
||||||
|
if let Some(parent) = dest_path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
remove_existing_path(&dest_path)?;
|
||||||
|
crate::log_info!(" {} -> {}", expanded_url, dest_path.display());
|
||||||
|
git_checkout(
|
||||||
|
base,
|
||||||
|
rev,
|
||||||
|
&dest_path,
|
||||||
|
&cache_dir.join("manual").join("git"),
|
||||||
|
&spec.package.name,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify a file against an `expected` checksum string.
|
/// Verify a file against an `expected` checksum string.
|
||||||
///
|
///
|
||||||
/// Formats accepted: `sha256:HEX`, `sha512:HEX`, `sha1:HEX`, `md5:HEX`,
|
/// Formats accepted: `sha256:HEX`, `sha512:HEX`, `sha1:HEX`, `md5:HEX`,
|
||||||
@@ -554,7 +649,7 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_git_url(url: &str) -> Option<(String, String)> {
|
pub(crate) fn split_git_url(url: &str) -> Option<(String, String)> {
|
||||||
// Check for explicit revision with #
|
// Check for explicit revision with #
|
||||||
if let Some((base, rev)) = url.split_once('#') {
|
if let Some((base, rev)) = url.split_once('#') {
|
||||||
// Ignore fragment for obvious archives.
|
// Ignore fragment for obvious archives.
|
||||||
@@ -997,6 +1092,49 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preflight_manual_sources_accepts_git_url() {
|
||||||
|
let (_tmp, remote_url, _tagged, hashed) = make_remote_git_repo();
|
||||||
|
let spec = mk_spec_with_manuals(
|
||||||
|
PathBuf::from("."),
|
||||||
|
vec![ManualSource {
|
||||||
|
file: None,
|
||||||
|
files: Vec::new(),
|
||||||
|
url: Some(format!("{remote_url}#{hashed}")),
|
||||||
|
urls: Vec::new(),
|
||||||
|
sha256: None,
|
||||||
|
dest: None,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cache_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
preflight_manual_sources(&spec, cache_dir.path()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn copy_manual_sources_git_url_mode_checks_out_repository() {
|
||||||
|
let (_tmp, remote_url, _tagged, hashed) = make_remote_git_repo();
|
||||||
|
let spec = mk_spec_with_manuals(
|
||||||
|
PathBuf::from("."),
|
||||||
|
vec![ManualSource {
|
||||||
|
file: None,
|
||||||
|
files: Vec::new(),
|
||||||
|
url: Some(format!("{remote_url}#{hashed}")),
|
||||||
|
urls: Vec::new(),
|
||||||
|
sha256: None,
|
||||||
|
dest: None,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cache_dir = tempfile::tempdir().unwrap();
|
||||||
|
let build_dir = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
copy_manual_sources(&spec, cache_dir.path(), build_dir.path()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(build_dir.path().join("origin/README")).unwrap(),
|
||||||
|
"hashed\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn copy_manual_sources_multi_files_in_one_block() {
|
fn copy_manual_sources_multi_files_in_one_block() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user