feat(swm): source_patch modela tarballs además de git (Fase 4)
Mutation::SourcePatch y RecipeInline ganan campos repo/commit XOR tarball/sha256 (Option, serde-default ⇒ compat con .swm git existentes), resueltos por swm::swm_source_kind con la misma regla que recipe::Source::kind. swm_bridge sintetiza la receta según el modo; hammer export reconstruye fuentes tarball como source_patch en vez de caer a file_drop (provenance fina recuperada). Prompt del traductor y roadmap actualizados. Tests nuevos para tarball + XOR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7c099afe4f
commit
0f42c38106
@@ -27,33 +27,54 @@ pub fn build_source_patch(
|
||||
store: &Store,
|
||||
scratch_root: Option<&Path>,
|
||||
) -> hammer_core::Result<ArtifactHash> {
|
||||
let (repo, commit, patch, patch_url, build_cfg, target_bin, expected) = match mutation {
|
||||
Mutation::SourcePatch {
|
||||
repo,
|
||||
commit,
|
||||
patch,
|
||||
patch_url,
|
||||
build,
|
||||
target_bin,
|
||||
expected_hash,
|
||||
} => (repo, commit, patch, patch_url, build, target_bin, expected_hash),
|
||||
_ => {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
"build_source_patch: la mutación no es 'source_patch'".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let (repo, commit, tarball, sha256, patch, patch_url, build_cfg, target_bin, expected) =
|
||||
match mutation {
|
||||
Mutation::SourcePatch {
|
||||
repo,
|
||||
commit,
|
||||
tarball,
|
||||
sha256,
|
||||
patch,
|
||||
patch_url,
|
||||
build,
|
||||
target_bin,
|
||||
expected_hash,
|
||||
} => (
|
||||
repo, commit, tarball, sha256, patch, patch_url, build, target_bin, expected_hash,
|
||||
),
|
||||
_ => {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
"build_source_patch: la mutación no es 'source_patch'".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// git xor tarball — misma regla que el schema (`swm_source_kind`).
|
||||
let source_kind = hammer_core::swm::swm_source_kind(
|
||||
repo.as_deref(),
|
||||
commit.as_deref(),
|
||||
tarball.as_deref(),
|
||||
sha256.as_deref(),
|
||||
)
|
||||
.map_err(hammer_core::Error::Recipe)?;
|
||||
|
||||
let scratch = scratch_root.unwrap_or(&cfg.work_root).to_path_buf();
|
||||
let recipe_dir = scratch.join("swm-recipes");
|
||||
std::fs::create_dir_all(&recipe_dir)?;
|
||||
|
||||
// Materializamos el patch a disco bajo un nombre derivado del commit (estable: si el .swm
|
||||
// se reaplica, reusamos el mismo archivo y el hash de la receta no depende de
|
||||
// Clave estable del origen para nombrar artefactos a disco: el commit (git) o el
|
||||
// sha256 (tarball). Idéntica reaplicación ⇒ mismo nombre ⇒ hash de receta determinista.
|
||||
let source_key: &str = match &source_kind {
|
||||
hammer_core::SourceKind::Git { commit, .. } => commit,
|
||||
hammer_core::SourceKind::Tarball { sha256, .. } => sha256,
|
||||
};
|
||||
|
||||
// Materializamos el patch a disco bajo un nombre derivado de la clave de origen (estable:
|
||||
// si el .swm se reaplica, reusamos el mismo archivo y el hash de la receta no depende de
|
||||
// aleatoriedad). Origen: inline (`patch`) o remoto (`patch_url`); el schema garantiza que
|
||||
// no vengan ambos.
|
||||
let mut patches: Vec<String> = Vec::new();
|
||||
let patch_path = recipe_dir.join(format!("{commit}.patch"));
|
||||
let patch_path = recipe_dir.join(format!("{source_key}.patch"));
|
||||
if let Some(text) = patch {
|
||||
std::fs::write(&patch_path, text.as_bytes())?;
|
||||
patches.push(patch_path.to_string_lossy().into_owned());
|
||||
@@ -64,14 +85,7 @@ pub fn build_source_patch(
|
||||
}
|
||||
|
||||
let name = derive_name(target_bin);
|
||||
let recipe = synthesize_recipe(
|
||||
&name,
|
||||
commit,
|
||||
repo,
|
||||
build_cfg,
|
||||
patches,
|
||||
&recipe_dir,
|
||||
)?;
|
||||
let recipe = synthesize_recipe(&name, &source_kind, build_cfg, patches, &recipe_dir)?;
|
||||
|
||||
let hash = build(&recipe, cfg, store)?;
|
||||
if let Some(want) = expected {
|
||||
@@ -102,24 +116,33 @@ fn derive_name(target_bin: &str) -> String {
|
||||
|
||||
fn synthesize_recipe(
|
||||
name: &str,
|
||||
commit: &str,
|
||||
repo: &str,
|
||||
source: &hammer_core::SourceKind<'_>,
|
||||
build_cfg: &SwmBuild,
|
||||
patches: Vec<String>,
|
||||
base_dir: &Path,
|
||||
) -> hammer_core::Result<Recipe> {
|
||||
let compiler = parse_compiler(&build_cfg.compiler)?;
|
||||
let link = parse_link(&build_cfg.link)?;
|
||||
// Sección `[source]` y sufijo de versión según el modo de origen. El validador de
|
||||
// `Source::kind` ya garantiza git xor tarball; acá sólo emitimos el TOML correspondiente.
|
||||
let (version_key, source_block) = match source {
|
||||
hammer_core::SourceKind::Git { repo, commit } => (
|
||||
*commit,
|
||||
format!("repo = \"{repo}\"\ncommit = \"{commit}\"\n"),
|
||||
),
|
||||
hammer_core::SourceKind::Tarball { url, sha256 } => (
|
||||
*sha256,
|
||||
format!("tarball = \"{url}\"\nsha256 = \"{sha256}\"\n"),
|
||||
),
|
||||
};
|
||||
// Construimos la receta vía TOML para reusar las defaults y el validador de `Source`.
|
||||
let toml_text = format!(
|
||||
r#"
|
||||
name = "{name}"
|
||||
version = "swm-{commit_short}"
|
||||
version = "swm-{version_short}"
|
||||
|
||||
[source]
|
||||
repo = "{repo}"
|
||||
commit = "{commit}"
|
||||
|
||||
{source_block}
|
||||
[build]
|
||||
compiler = "{compiler}"
|
||||
target = "{target}"
|
||||
@@ -127,9 +150,8 @@ link = "{link}"
|
||||
flags = []
|
||||
"#,
|
||||
name = name,
|
||||
commit_short = &commit[..commit.len().min(12)],
|
||||
repo = repo,
|
||||
commit = commit,
|
||||
version_short = &version_key[..version_key.len().min(12)],
|
||||
source_block = source_block,
|
||||
compiler = compiler.as_str(),
|
||||
target = build_cfg.target,
|
||||
link = link.as_str(),
|
||||
@@ -199,8 +221,10 @@ mod tests {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let r = synthesize_recipe(
|
||||
"grep",
|
||||
"a1b2c3d4e5f6a7b8c9",
|
||||
"git://example/grep.git",
|
||||
&hammer_core::SourceKind::Git {
|
||||
repo: "git://example/grep.git",
|
||||
commit: "a1b2c3d4e5f6a7b8c9",
|
||||
},
|
||||
&fake_swm_build(),
|
||||
vec![],
|
||||
d.path(),
|
||||
@@ -218,6 +242,31 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthesize_recipe_tarball() {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let r = synthesize_recipe(
|
||||
"grep",
|
||||
&hammer_core::SourceKind::Tarball {
|
||||
url: "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz",
|
||||
sha256: "abcdef0123456789",
|
||||
},
|
||||
&fake_swm_build(),
|
||||
vec![],
|
||||
d.path(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.name, "grep");
|
||||
assert!(r.version.starts_with("swm-abcdef012345"));
|
||||
match r.source.kind().unwrap() {
|
||||
hammer_core::SourceKind::Tarball { url, sha256 } => {
|
||||
assert_eq!(url, "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz");
|
||||
assert_eq!(sha256, "abcdef0123456789");
|
||||
}
|
||||
_ => panic!("modo tarball esperado"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_compiler_and_link() {
|
||||
assert_eq!(parse_compiler("zig-cc").unwrap(), Compiler::ZigCc);
|
||||
@@ -249,8 +298,10 @@ mod tests {
|
||||
std::fs::write(&patch_src, b"--- a\n+++ b\n").unwrap();
|
||||
let commit = "deadbeefcafe";
|
||||
let m = Mutation::SourcePatch {
|
||||
repo: "git://x".into(),
|
||||
commit: commit.into(),
|
||||
repo: Some("git://x".into()),
|
||||
commit: Some(commit.into()),
|
||||
tarball: None,
|
||||
sha256: None,
|
||||
patch: None,
|
||||
patch_url: Some(format!("file://{}", patch_src.display())),
|
||||
build: fake_swm_build(),
|
||||
|
||||
Reference in New Issue
Block a user