feat: add support for Mercurial (hg) source handling in mod.rs
- Implemented `split_hg_url` to parse Mercurial URLs and revisions. - Added `checkout_hg` function to clone Mercurial repositories. - Integrated Mercurial handling into the `prepare_one` function to support resuming builds. - Added tests for `split_hg_url` to ensure correct parsing of URLs and revisions. --- feat: introduce legacy STARBUILD conversion support in starbuild.rs - Created `ConvertedStarbuild` and `ParsedStarbuild` structs to manage conversion data. - Implemented `convert_starbuild_file` to parse STARBUILD files and generate package specifications. - Added functions to handle parsing of various STARBUILD constructs, including dependencies and build phases. - Developed a build script generator for STARBUILD, supporting custom install functions. - Included tests to validate conversion of single and multi-output STARBUILD files.
This commit is contained in:
Generated
+3
-3
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
||||
|
||||
[[package]]
|
||||
name = "depot"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ar",
|
||||
@@ -3090,9 +3090,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.3.1"
|
||||
version = "8.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c546feb4481b0fbafb4ef0d79b6204fc41c6f9884b1b73b1d73f82442fc0845"
|
||||
checksum = "7756d0206d058333667493c4014f545f4b9603c4330ccd6d9b3f86dcab59f7d9"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "depot"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
edition = "2024"
|
||||
|
||||
[lints.rust]
|
||||
@@ -27,7 +27,7 @@ toml = "1.0.6"
|
||||
url = "2.5.8"
|
||||
walkdir = "2.5.0"
|
||||
xz2 = "0.1.7"
|
||||
zip = "8.3.1"
|
||||
zip = "8.4.0"
|
||||
zstd = { version = "0.13.3", features = ["zstdmt"] }
|
||||
inquire = "0.9.4"
|
||||
md5 = "0.8.0"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
project(
|
||||
'depot',
|
||||
version: '0.31.0',
|
||||
version: '0.31.1',
|
||||
meson_version: '>=0.60.0',
|
||||
default_options: ['buildtype=release'],
|
||||
)
|
||||
|
||||
@@ -117,6 +117,19 @@ pub fn build(
|
||||
"DEPOT_PRIMARY_DESTDIR",
|
||||
install_destdir.to_string_lossy().into_owned(),
|
||||
);
|
||||
let starbuild_workdir = if spec.sources().is_empty() {
|
||||
src_dir.to_path_buf()
|
||||
} else {
|
||||
src_dir
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| src_dir.to_path_buf())
|
||||
};
|
||||
crate::builder::set_env_var(
|
||||
&mut env_vars,
|
||||
"DEPOT_STARBUILD_WORKDIR",
|
||||
starbuild_workdir.to_string_lossy().into_owned(),
|
||||
);
|
||||
add_output_destdir_envs(spec, &install_destdir, &mut env_vars);
|
||||
|
||||
// Ensure build script path is absolute for when we are in a sub-build-dir
|
||||
|
||||
+40
-1
@@ -240,6 +240,17 @@ pub struct MakeSpecArgs {
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct ConvertArgs {
|
||||
/// Path to the legacy STARBUILD file
|
||||
#[arg(default_value = "STARBUILD")]
|
||||
pub input: PathBuf,
|
||||
|
||||
/// Output TOML file path (defaults to <mainpkgname>.toml beside the STARBUILD)
|
||||
#[arg(short, long)]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct InternalArgs {
|
||||
#[command(subcommand)]
|
||||
@@ -277,6 +288,8 @@ pub enum Commands {
|
||||
GenerateArtifacts(GenerateArtifactsArgs),
|
||||
/// Create a new package specification interactively
|
||||
MakeSpec(MakeSpecArgs),
|
||||
/// Convert a legacy STARBUILD into a Depot package spec
|
||||
Convert(ConvertArgs),
|
||||
#[command(hide = true)]
|
||||
Internal(InternalArgs),
|
||||
}
|
||||
@@ -430,7 +443,8 @@ pub enum RepoKindArg {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
BuildArgs, Cli, Commands, InstallArgs, RepoArgs, RepoCommands, SearchArgs, UpdateArgs,
|
||||
BuildArgs, Cli, Commands, ConvertArgs, InstallArgs, RepoArgs, RepoCommands, SearchArgs,
|
||||
UpdateArgs,
|
||||
};
|
||||
use clap::{CommandFactory, Parser};
|
||||
use std::path::PathBuf;
|
||||
@@ -517,6 +531,31 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_accepts_default_input() {
|
||||
let cli = Cli::try_parse_from(["depot", "convert"]).unwrap();
|
||||
match cli.command {
|
||||
Commands::Convert(ConvertArgs { input, output }) => {
|
||||
assert_eq!(input, PathBuf::from("STARBUILD"));
|
||||
assert!(output.is_none());
|
||||
}
|
||||
_ => panic!("expected convert command"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_accepts_custom_output() {
|
||||
let cli = Cli::try_parse_from(["depot", "convert", "legacy/STARBUILD", "-o", "pkg.toml"])
|
||||
.unwrap();
|
||||
match cli.command {
|
||||
Commands::Convert(ConvertArgs { input, output }) => {
|
||||
assert_eq!(input, PathBuf::from("legacy/STARBUILD"));
|
||||
assert_eq!(output, Some(PathBuf::from("pkg.toml")));
|
||||
}
|
||||
_ => panic!("expected convert command"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_cleanup_deps_flag_is_parsed() {
|
||||
let cli = Cli::try_parse_from(["depot", "build", "--cleanup-deps", "pkg.toml"]).unwrap();
|
||||
|
||||
+58
-2
@@ -1,6 +1,7 @@
|
||||
use crate::cli::{
|
||||
BuildArgs, Cli, Commands, ConfigArgs, InfoArgs, InstallArgs, InternalCommands, ListArgs,
|
||||
OwnsArgs, RemoveArgs, RepoArgs, RepoCommands, RepoKindArg, SearchArgs, SignArgs, UpdateArgs,
|
||||
BuildArgs, Cli, Commands, ConfigArgs, ConvertArgs, InfoArgs, InstallArgs, InternalCommands,
|
||||
ListArgs, OwnsArgs, RemoveArgs, RepoArgs, RepoCommands, RepoKindArg, SearchArgs, SignArgs,
|
||||
UpdateArgs,
|
||||
};
|
||||
use crate::{
|
||||
builder, cli_assets, config, cross, db, deps, index, install, locking, package, planner,
|
||||
@@ -63,6 +64,7 @@ fn command_rootfs(command: &Commands) -> Option<&Path> {
|
||||
Commands::Repo(args) => Some(repo_command_rootfs(&args.command)),
|
||||
Commands::Config(args) => Some(&args.rootfs_args.rootfs),
|
||||
Commands::Check(_)
|
||||
| Commands::Convert(_)
|
||||
| Commands::GenerateArtifacts(_)
|
||||
| Commands::MakeSpec(_)
|
||||
| Commands::Internal(_) => None,
|
||||
@@ -76,6 +78,7 @@ fn command_assume_yes(command: &Commands) -> bool {
|
||||
Commands::Build(args) => args.prompt_args.yes,
|
||||
Commands::Update(args) => args.prompt_args.yes,
|
||||
Commands::Check(_)
|
||||
| Commands::Convert(_)
|
||||
| Commands::Info(_)
|
||||
| Commands::Search(_)
|
||||
| Commands::Owns(_)
|
||||
@@ -4669,6 +4672,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
| Commands::Repo(_)
|
||||
| Commands::Config(_)
|
||||
| Commands::GenerateArtifacts(_)
|
||||
| Commands::Convert(_)
|
||||
| Commands::MakeSpec(_)
|
||||
| Commands::Internal(_) => false,
|
||||
};
|
||||
@@ -5901,6 +5905,58 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
output_path.display()
|
||||
));
|
||||
}
|
||||
Commands::Convert(ConvertArgs { input, output }) => {
|
||||
let converted = package::convert_starbuild_file(&input, output.as_deref())?;
|
||||
let mut outputs = vec![converted.output_path.clone()];
|
||||
if let Some(build_script_path) = &converted.build_script_path {
|
||||
outputs.push(build_script_path.clone());
|
||||
}
|
||||
|
||||
let existing: Vec<_> = outputs
|
||||
.iter()
|
||||
.filter(|path| path.exists())
|
||||
.map(|path| path.display().to_string())
|
||||
.collect();
|
||||
if !existing.is_empty() {
|
||||
ui::warn(format!(
|
||||
"Generated files already exist: {}",
|
||||
existing.join(", ")
|
||||
));
|
||||
if !ui::prompt_yes_no("Overwrite them?", false)? {
|
||||
anyhow::bail!("Aborted");
|
||||
}
|
||||
}
|
||||
|
||||
fs::write(&converted.output_path, converted.toml)
|
||||
.with_context(|| format!("Failed to write {}", converted.output_path.display()))?;
|
||||
if let (Some(build_script), Some(build_script_path)) =
|
||||
(converted.build_script, converted.build_script_path)
|
||||
{
|
||||
fs::write(&build_script_path, build_script)
|
||||
.with_context(|| format!("Failed to write {}", build_script_path.display()))?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&build_script_path)
|
||||
.with_context(|| format!("Failed to stat {}", build_script_path.display()))?
|
||||
.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&build_script_path, perms).with_context(|| {
|
||||
format!("Failed to chmod {}", build_script_path.display())
|
||||
})?;
|
||||
}
|
||||
ui::success(format!(
|
||||
"Converted STARBUILD into {} and {}",
|
||||
converted.output_path.display(),
|
||||
build_script_path.display()
|
||||
));
|
||||
} else {
|
||||
ui::success(format!(
|
||||
"Converted STARBUILD into {}",
|
||||
converted.output_path.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
Commands::Internal(args) => {
|
||||
let command = args.command;
|
||||
run_internal_command(command)?;
|
||||
|
||||
@@ -1544,6 +1544,107 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
||||
root.insert("dependencies".into(), Value::Table(dep_tbl));
|
||||
}
|
||||
|
||||
if !spec.package_dependencies.is_empty() {
|
||||
let mut outputs_tbl = Table::new();
|
||||
for (pkg_name, deps) in &spec.package_dependencies {
|
||||
let mut dep_tbl = Table::new();
|
||||
if !deps.build.is_empty() {
|
||||
dep_tbl.insert(
|
||||
"build".into(),
|
||||
Value::Array(
|
||||
deps.build
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !deps.runtime.is_empty() {
|
||||
dep_tbl.insert(
|
||||
"runtime".into(),
|
||||
Value::Array(
|
||||
deps.runtime
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !deps.test.is_empty() {
|
||||
dep_tbl.insert(
|
||||
"test".into(),
|
||||
Value::Array(deps.test.iter().map(|s| Value::String(s.clone())).collect()),
|
||||
);
|
||||
}
|
||||
if !deps.optional.is_empty() {
|
||||
dep_tbl.insert(
|
||||
"optional".into(),
|
||||
Value::Array(
|
||||
deps.optional
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !dep_tbl.is_empty() {
|
||||
outputs_tbl.insert(pkg_name.clone(), Value::Table(dep_tbl));
|
||||
}
|
||||
}
|
||||
if !outputs_tbl.is_empty() {
|
||||
root.insert("package_dependencies".into(), Value::Table(outputs_tbl));
|
||||
}
|
||||
}
|
||||
|
||||
if !spec.package_alternatives.is_empty() {
|
||||
let mut outputs_tbl = Table::new();
|
||||
for (pkg_name, alternatives) in &spec.package_alternatives {
|
||||
let mut alt_tbl = Table::new();
|
||||
if !alternatives.provides.is_empty() {
|
||||
alt_tbl.insert(
|
||||
"provides".into(),
|
||||
Value::Array(
|
||||
alternatives
|
||||
.provides
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !alternatives.conflicts.is_empty() {
|
||||
alt_tbl.insert(
|
||||
"conflicts".into(),
|
||||
Value::Array(
|
||||
alternatives
|
||||
.conflicts
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !alternatives.replaces.is_empty() {
|
||||
alt_tbl.insert(
|
||||
"replaces".into(),
|
||||
Value::Array(
|
||||
alternatives
|
||||
.replaces
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if !alt_tbl.is_empty() {
|
||||
outputs_tbl.insert(pkg_name.clone(), Value::Table(alt_tbl));
|
||||
}
|
||||
}
|
||||
if !outputs_tbl.is_empty() {
|
||||
root.insert("package_alternatives".into(), Value::Table(outputs_tbl));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(toml::to_string_pretty(&Value::Table(root))?)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
mod interactive;
|
||||
mod packager;
|
||||
mod spec;
|
||||
mod starbuild;
|
||||
|
||||
pub use interactive::*;
|
||||
pub use packager::Packager;
|
||||
pub use spec::*;
|
||||
pub(crate) use starbuild::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::os::unix::fs as unix_fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use url::Url;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -418,6 +419,20 @@ fn prepare_one(
|
||||
|
||||
// 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_hg_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 hg directory: {}",
|
||||
checkout_dir.display()
|
||||
);
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
checkout_hg(&base, &rev, &checkout_dir)?;
|
||||
hooks::post_extract(spec, source, &checkout_dir, cache_dir)?;
|
||||
return Ok(checkout_dir);
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -556,6 +571,45 @@ fn split_git_url(url: &str) -> Option<(String, String)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn split_hg_url(url: &str) -> Option<(String, String)> {
|
||||
let rest = url.strip_prefix("hg+")?;
|
||||
if let Some((base, rev)) = rest.split_once('#') {
|
||||
let revision = if rev.trim().is_empty() {
|
||||
"tip"
|
||||
} else {
|
||||
rev.trim()
|
||||
};
|
||||
return Some((base.to_string(), revision.to_string()));
|
||||
}
|
||||
Some((rest.to_string(), "tip".to_string()))
|
||||
}
|
||||
|
||||
fn checkout_hg(url: &str, rev: &str, checkout_dir: &Path) -> Result<()> {
|
||||
if checkout_dir.exists() {
|
||||
fs::remove_dir_all(checkout_dir).with_context(|| {
|
||||
format!(
|
||||
"Failed to remove existing Mercurial checkout dir: {}",
|
||||
checkout_dir.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
crate::log_info!("Cloning Mercurial source {} @ {}...", url, rev);
|
||||
let mut cmd = Command::new("hg");
|
||||
cmd.arg("clone")
|
||||
.arg("-u")
|
||||
.arg(rev)
|
||||
.arg(url)
|
||||
.arg(checkout_dir);
|
||||
cmd.env("PATH", crate::runtime_env::safe_script_path());
|
||||
let status = crate::interrupts::command_status(&mut cmd)
|
||||
.with_context(|| format!("Failed to run hg clone for {}", url))?;
|
||||
if !status.success() {
|
||||
bail!("Mercurial clone failed for {} @ {}", url, rev);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -755,6 +809,17 @@ mod tests {
|
||||
assert_eq!(rev, "0123456789abcdef");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_hg_url_accepts_revision_and_default_tip() {
|
||||
let (base, rev) = split_hg_url("hg+https://hg.example.test/repo#v1").unwrap();
|
||||
assert_eq!(base, "https://hg.example.test/repo");
|
||||
assert_eq!(rev, "v1");
|
||||
|
||||
let (base, rev) = split_hg_url("hg+https://hg.example.test/repo").unwrap();
|
||||
assert_eq!(base, "https://hg.example.test/repo");
|
||||
assert_eq!(rev, "tip");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_one_rejects_cherry_pick_for_non_git_sources() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user