Etapa G capa-de-adaptación #3: traducir mirror:// de nix a URLs concretas
nix resuelve `mirror://<sitio>/...` en eval; un import los deja literales y el curl de hammer no
los entiende. expand_nix_mirror() mapea los comunes (gnu/savannah/kernel/sourceforge/gnome/
apache/xorg/pypi/cpan/debian) a un espejo real; lo no mapeado se deja igual. +1 test.
Validado: import hello → tarball https://ftp.gnu.org/gnu/hello/... (antes mirror://gnu/...).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
b66cae91ae9fee19134827d0f3e04d3d37426166 · por sergio · 2026-06-21T12:20:08Z (hace 4 meses) · padre 8e6c878d3 · ver el árbol en este commit
Delta semántico · 4
crates/hammer-cli/src/nix_import.rs
diff --git a/crates/hammer-cli/src/nix_import.rs b/crates/hammer-cli/src/nix_import.rs
index 12402b37..26bad405 100644
--- a/crates/hammer-cli/src/nix_import.rs
+++ b/crates/hammer-cli/src/nix_import.rs
@@ -63,7 +63,7 @@ pub fn classify_source(raw: &RawSource) -> Result<NixSource, String> {
return Err(format!("source flat sin hash: {}", raw.url));
}
let hex = nix_hash_to_hex(&raw.output_hash)?;
- Ok(NixSource::Tarball { url: raw.url.clone(), sha256: hex })
+ Ok(NixSource::Tarball { url: expand_nix_mirror(&raw.url), sha256: hex })
}
"recursive" => Err(format!(
"source recursivo no-github (hash NAR no reusable): {} — fijá repo+commit a mano",
@@ -73,6 +73,33 @@ pub fn classify_source(raw: &RawSource) -> Result<NixSource, String> {
}
}
+/// Traduce el esquema `mirror://<sitio>/...` de nix a una URL concreta. nix resuelve estos
+/// mirrors en tiempo de eval; un import los deja literales y el fetch de hammer (curl) no los
+/// entiende. Mapeamos los más comunes a un mirror real; lo no mapeado se deja igual + el usuario
+/// elige espejo. (nixpkgs tiene la lista completa en `pkgs/build-support/fetchurl/mirrors.nix`.)
+fn expand_nix_mirror(url: &str) -> String {
+ let Some(rest) = url.strip_prefix("mirror://") else {
+ return url.to_string();
+ };
+ let Some((site, path)) = rest.split_once('/') else {
+ return url.to_string();
+ };
+ let base = match site {
+ "gnu" => "https://ftp.gnu.org/gnu/",
+ "savannah" => "https://download.savannah.gnu.org/releases/",
+ "kernel" => "https://www.kernel.org/pub/",
+ "sourceforge" => "https://downloads.sourceforge.net/",
+ "gnome" => "https://download.gnome.org/",
+ "apache" => "https://dlcdn.apache.org/",
+ "xorg" => "https://www.x.org/releases/",
+ "pypi" => "https://files.pythonhosted.org/packages/",
+ "cpan" => "https://cpan.metacpan.org/",
+ "debian" => "https://deb.debian.org/debian/",
+ _ => return url.to_string(), // desconocido: dejamos el mirror:// (el humano resuelve)
+ };
+ format!("{base}{path}")
+}
+
/// Extrae `(owner, repo, rev)` de una URL de descarga de GitHub (las que arma `fetchFromGitHub`):
/// `https://github.com/<owner>/<repo>/archive/<rev>.tar.gz` o
/// `https://codeload.github.com/<owner>/<repo>/tar.gz/<rev>`.
@@ -335,7 +362,8 @@ mod tests {
let recipe = hammer_core::Recipe::from_toml(&toml).expect("recipe válida");
assert_eq!(recipe.name, "hello");
assert_eq!(recipe.version, "2.12.1");
- assert_eq!(recipe.source.tarball.as_deref(), Some("mirror://gnu/hello/hello-2.12.1.tar.gz"));
+ // mirror://gnu/ se traduce a una URL concreta que el fetch de hammer entiende.
+ assert_eq!(recipe.source.tarball.as_deref(), Some("https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz"));
assert_eq!(
recipe.source.sha256.as_deref(),
Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
@@ -360,6 +388,22 @@ mod tests {
assert_eq!(recipe.deps.build, vec!["pcre2", "rustc"]);
}
+ #[test]
+ fn expands_common_nix_mirrors() {
+ assert_eq!(
+ expand_nix_mirror("mirror://gnu/hello/hello-2.12.1.tar.gz"),
+ "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz"
+ );
+ assert_eq!(
+ expand_nix_mirror("mirror://savannah/foo/foo-1.tar.gz"),
+ "https://download.savannah.gnu.org/releases/foo/foo-1.tar.gz"
+ );
+ // desconocido se deja igual (no rompe, el humano resuelve).
+ assert_eq!(expand_nix_mirror("mirror://raro/x"), "mirror://raro/x");
+ // url normal pasa sin tocar.
+ assert_eq!(expand_nix_mirror("https://x/y.tar.gz"), "https://x/y.tar.gz");
+ }
+
#[test]
fn import_github_uses_git() {
let json = r#"{