takana etapa 4: los 10 crates de librería y el CLI pasan a takana-*
hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
instalados y hornea un hook de arranque que lo invoca por ese nombre:
renombrarlo rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
This commit is contained in:
Generated
+162
-162
@@ -483,179 +483,21 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-agent"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"base64",
|
|
||||||
"hammer-core",
|
|
||||||
"hammer-journal",
|
|
||||||
"hammer-overlay",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
"ureq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-bootstrap"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"hammer-build",
|
|
||||||
"hammer-core",
|
|
||||||
"hex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-build"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"hammer-core",
|
|
||||||
"hex",
|
|
||||||
"libc",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-cli"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"base64",
|
|
||||||
"clap",
|
|
||||||
"hammer-agent",
|
|
||||||
"hammer-bootstrap",
|
|
||||||
"hammer-build",
|
|
||||||
"hammer-core",
|
|
||||||
"hammer-journal",
|
|
||||||
"hammer-mirror",
|
|
||||||
"hammer-overlay",
|
|
||||||
"hammer-upgrade",
|
|
||||||
"hex",
|
|
||||||
"nix",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tempfile",
|
|
||||||
"toml",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-core"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"base64",
|
|
||||||
"blake3",
|
|
||||||
"ed25519-dalek",
|
|
||||||
"getrandom 0.2.17",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-journal"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"blake3",
|
|
||||||
"hammer-core",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-mirror"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"hammer-core",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-overlay"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"hammer-core",
|
|
||||||
"hammer-journal",
|
|
||||||
"libc",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-recover"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"hammer-journal",
|
|
||||||
"hammer-upgrade",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hammer-upgrade"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"hammer-core",
|
|
||||||
"hammer-journal",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hammerd"
|
name = "hammerd"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"hammer-build",
|
|
||||||
"hammer-core",
|
|
||||||
"hammer-journal",
|
|
||||||
"hammer-overlay",
|
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"postcard",
|
"postcard",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"takana-build",
|
||||||
|
"takana-core",
|
||||||
|
"takana-journal",
|
||||||
|
"takana-overlay",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1381,6 +1223,164 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-agent"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"takana-core",
|
||||||
|
"takana-journal",
|
||||||
|
"takana-overlay",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
"ureq",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-bootstrap"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"hex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"takana-build",
|
||||||
|
"takana-core",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-build"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"hex",
|
||||||
|
"libc",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"takana-core",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-cli"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"clap",
|
||||||
|
"hex",
|
||||||
|
"nix",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"takana-agent",
|
||||||
|
"takana-bootstrap",
|
||||||
|
"takana-build",
|
||||||
|
"takana-core",
|
||||||
|
"takana-journal",
|
||||||
|
"takana-mirror",
|
||||||
|
"takana-overlay",
|
||||||
|
"takana-upgrade",
|
||||||
|
"tempfile",
|
||||||
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-core"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"blake3",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-journal"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"blake3",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"takana-core",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-mirror"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"takana-core",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-overlay"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"libc",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"takana-core",
|
||||||
|
"takana-journal",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-recover"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"takana-journal",
|
||||||
|
"takana-upgrade",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "takana-upgrade"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"takana-core",
|
||||||
|
"takana-journal",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
|
|||||||
+18
-18
@@ -1,16 +1,16 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/hammer-core",
|
"crates/takana-core",
|
||||||
"crates/hammer-build",
|
"crates/takana-build",
|
||||||
"crates/hammer-bootstrap",
|
"crates/takana-bootstrap",
|
||||||
"crates/hammer-overlay",
|
"crates/takana-overlay",
|
||||||
"crates/hammer-journal",
|
"crates/takana-journal",
|
||||||
"crates/hammer-mirror",
|
"crates/takana-mirror",
|
||||||
"crates/hammer-upgrade",
|
"crates/takana-upgrade",
|
||||||
"crates/hammer-recover",
|
"crates/takana-recover",
|
||||||
"crates/hammer-agent",
|
"crates/takana-agent",
|
||||||
"crates/hammer-cli",
|
"crates/takana-cli",
|
||||||
"crates/hammerd",
|
"crates/hammerd",
|
||||||
"crates/netup",
|
"crates/netup",
|
||||||
]
|
]
|
||||||
@@ -23,14 +23,14 @@ authors = ["sergio <gerencia@jlsoltech.com>"]
|
|||||||
repository = "https://gitea.gioser.net/sergio/hammer"
|
repository = "https://gitea.gioser.net/sergio/hammer"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
hammer-core = { path = "crates/hammer-core" }
|
takana-core = { path = "crates/takana-core" }
|
||||||
hammer-build = { path = "crates/hammer-build" }
|
takana-build = { path = "crates/takana-build" }
|
||||||
hammer-bootstrap = { path = "crates/hammer-bootstrap" }
|
takana-bootstrap = { path = "crates/takana-bootstrap" }
|
||||||
hammer-overlay = { path = "crates/hammer-overlay" }
|
takana-overlay = { path = "crates/takana-overlay" }
|
||||||
hammer-journal = { path = "crates/hammer-journal" }
|
takana-journal = { path = "crates/takana-journal" }
|
||||||
hammer-mirror = { path = "crates/hammer-mirror" }
|
takana-mirror = { path = "crates/takana-mirror" }
|
||||||
hammer-upgrade = { path = "crates/hammer-upgrade" }
|
takana-upgrade = { path = "crates/takana-upgrade" }
|
||||||
hammer-agent = { path = "crates/hammer-agent" }
|
takana-agent = { path = "crates/takana-agent" }
|
||||||
|
|
||||||
# shared third-party deps, pinned at the workspace level
|
# shared third-party deps, pinned at the workspace level
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ name = "hammerd"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-build.workspace = true
|
takana-build.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
hammer-overlay.workspace = true
|
takana-overlay.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|||||||
+26
-26
@@ -25,7 +25,7 @@ use std::thread;
|
|||||||
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
|
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
|
||||||
|
|
||||||
use crate::events::EventBus;
|
use crate::events::EventBus;
|
||||||
use hammer_core::proto::{
|
use takana_core::proto::{
|
||||||
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, RecipeInline,
|
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, RecipeInline,
|
||||||
PROTOCOL_VERSION,
|
PROTOCOL_VERSION,
|
||||||
};
|
};
|
||||||
@@ -49,7 +49,7 @@ pub fn default_policy() -> CapsPolicy {
|
|||||||
|
|
||||||
/// Política derivada de un fichero `agent-caps.toml` (SDD 07 §4). El peer no elige sus caps;
|
/// Política derivada de un fichero `agent-caps.toml` (SDD 07 §4). El peer no elige sus caps;
|
||||||
/// las resuelve la config por `(uid, gid)`.
|
/// las resuelve la config por `(uid, gid)`.
|
||||||
pub fn policy_from_config(cfg: hammer_core::AgentCapsConfig) -> CapsPolicy {
|
pub fn policy_from_config(cfg: takana_core::AgentCapsConfig) -> CapsPolicy {
|
||||||
Arc::new(move |peer: &Peer| cfg.caps_for(peer.uid, peer.gid))
|
Arc::new(move |peer: &Peer| cfg.caps_for(peer.uid, peer.gid))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ fn dispatch(
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
Command::Inject { artifact, target, overlay: _ } => {
|
Command::Inject { artifact, target, overlay: _ } => {
|
||||||
let store = match hammer_core::Store::open(&ctx.store_root) {
|
let store = match takana_core::Store::open(&ctx.store_root) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = tx.send(Event::Error {
|
let _ = tx.send(Event::Error {
|
||||||
@@ -279,10 +279,10 @@ fn dispatch(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let report = match hammer_build::run_hydrate(
|
let report = match takana_build::run_hydrate(
|
||||||
&artifact_dir,
|
&artifact_dir,
|
||||||
Path::new(&target),
|
Path::new(&target),
|
||||||
hammer_core::LinkMode::Static,
|
takana_core::LinkMode::Static,
|
||||||
None,
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
@@ -333,7 +333,7 @@ fn dispatch(
|
|||||||
// El daemon evalúa con el `fs_root` del sistema host (None ⇒ rutas
|
// El daemon evalúa con el `fs_root` del sistema host (None ⇒ rutas
|
||||||
// absolutas tal y como vienen). Si el .swm necesitara otra base, el
|
// absolutas tal y como vienen). Si el .swm necesitara otra base, el
|
||||||
// cliente puede evaluar localmente con un EvalContext distinto.
|
// cliente puede evaluar localmente con un EvalContext distinto.
|
||||||
hammer_core::query::eval_str(e, &hammer_core::query::EvalContext::new())
|
takana_core::query::eval_str(e, &takana_core::query::EvalContext::new())
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
let _ = tx.send(Event::Error {
|
let _ = tx.send(Event::Error {
|
||||||
@@ -363,7 +363,7 @@ fn dispatch(
|
|||||||
|
|
||||||
fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
||||||
let name = recipe.name.clone();
|
let name = recipe.name.clone();
|
||||||
let mutation = hammer_core::swm::Mutation::SourcePatch {
|
let mutation = takana_core::swm::Mutation::SourcePatch {
|
||||||
repo: recipe.repo,
|
repo: recipe.repo,
|
||||||
commit: recipe.commit,
|
commit: recipe.commit,
|
||||||
tarball: recipe.tarball,
|
tarball: recipe.tarball,
|
||||||
@@ -371,7 +371,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
|||||||
strip_components: None,
|
strip_components: None,
|
||||||
patch: recipe.patch,
|
patch: recipe.patch,
|
||||||
patch_url: None,
|
patch_url: None,
|
||||||
build: hammer_core::swm::SwmBuild {
|
build: takana_core::swm::SwmBuild {
|
||||||
compiler: recipe.compiler,
|
compiler: recipe.compiler,
|
||||||
target: recipe.target,
|
target: recipe.target,
|
||||||
link: recipe.link,
|
link: recipe.link,
|
||||||
@@ -389,7 +389,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
|||||||
// de compatibilidad vive en `install` (CLI) contra el estado instalado. Vacío aquí.
|
// de compatibilidad vive en `install` (CLI) contra el estado instalado. Vacío aquí.
|
||||||
slots: Default::default(),
|
slots: Default::default(),
|
||||||
};
|
};
|
||||||
let store = match hammer_core::Store::open(&store_root) {
|
let store = match takana_core::Store::open(&store_root) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = tx.send(Event::BuildFailed {
|
let _ = tx.send(Event::BuildFailed {
|
||||||
@@ -400,8 +400,8 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(store.root());
|
let cfg = takana_build::BuildConfig::from_env_or_defaults(store.root());
|
||||||
match hammer_build::build_source_patch(&mutation, &cfg, &store, None, None) {
|
match takana_build::build_source_patch(&mutation, &cfg, &store, None, None) {
|
||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
// [H1c] El artefacto está sellado; ahora EJECUTAMOS la evidencia declarada y
|
// [H1c] El artefacto está sellado; ahora EJECUTAMOS la evidencia declarada y
|
||||||
// adjuntamos el veredicto. El gate ("no proponer" si falla) vive en el orquestador
|
// adjuntamos el veredicto. El gate ("no proponer" si falla) vive en el orquestador
|
||||||
@@ -417,7 +417,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Si el fallo trae la cola del log del lab, la separamos de la razón para que la
|
// Si el fallo trae la cola del log del lab, la separamos de la razón para que la
|
||||||
// IA pueda leer el error real del compilador, no sólo el mensaje de Rust.
|
// IA pueda leer el error real del compilador, no sólo el mensaje de Rust.
|
||||||
let (reason, log_tail) = match hammer_build::sandbox::BuildFailure::from_error(&e) {
|
let (reason, log_tail) = match takana_build::sandbox::BuildFailure::from_error(&e) {
|
||||||
Some(bf) => (bf.reason.clone(), bf.log_tail.clone()),
|
Some(bf) => (bf.reason.clone(), bf.log_tail.clone()),
|
||||||
None => (e.to_string(), None),
|
None => (e.to_string(), None),
|
||||||
};
|
};
|
||||||
@@ -437,13 +437,13 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
|||||||
/// - `Some(all_passed=false, …)` sintético si la evidencia ni siquiera pudo ejecutarse (falta una
|
/// - `Some(all_passed=false, …)` sintético si la evidencia ni siquiera pudo ejecutarse (falta una
|
||||||
/// build-dep, error de sandbox…): eso también debe bloquear la propuesta, no pasar en silencio.
|
/// build-dep, error de sandbox…): eso también debe bloquear la propuesta, no pasar en silencio.
|
||||||
fn evidence_verdict(
|
fn evidence_verdict(
|
||||||
mutation: &hammer_core::swm::Mutation,
|
mutation: &takana_core::swm::Mutation,
|
||||||
cfg: &hammer_build::BuildConfig,
|
cfg: &takana_build::BuildConfig,
|
||||||
store: &hammer_core::Store,
|
store: &takana_core::Store,
|
||||||
) -> Option<EvidenceVerdict> {
|
) -> Option<EvidenceVerdict> {
|
||||||
let has_evidence = matches!(
|
let has_evidence = matches!(
|
||||||
mutation,
|
mutation,
|
||||||
hammer_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
|
takana_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
|
||||||
);
|
);
|
||||||
if !has_evidence {
|
if !has_evidence {
|
||||||
return None;
|
return None;
|
||||||
@@ -456,7 +456,7 @@ fn evidence_verdict(
|
|||||||
all_passed: false,
|
all_passed: false,
|
||||||
max_level: None,
|
max_level: None,
|
||||||
checks: vec![EvidenceCheckVerdict {
|
checks: vec![EvidenceCheckVerdict {
|
||||||
kind: hammer_core::EvidenceKind::CmdExit,
|
kind: takana_core::EvidenceKind::CmdExit,
|
||||||
cmd: "<runner>".into(),
|
cmd: "<runner>".into(),
|
||||||
passed: false,
|
passed: false,
|
||||||
detail: format!("la evidencia no pudo ejecutarse: {e}"),
|
detail: format!("la evidencia no pudo ejecutarse: {e}"),
|
||||||
@@ -467,14 +467,14 @@ fn evidence_verdict(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_evidence_inner(
|
fn run_evidence_inner(
|
||||||
mutation: &hammer_core::swm::Mutation,
|
mutation: &takana_core::swm::Mutation,
|
||||||
cfg: &hammer_build::BuildConfig,
|
cfg: &takana_build::BuildConfig,
|
||||||
store: &hammer_core::Store,
|
store: &takana_core::Store,
|
||||||
) -> hammer_core::Result<EvidenceVerdict> {
|
) -> takana_core::Result<EvidenceVerdict> {
|
||||||
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(cfg, None);
|
let catalog_dir = takana_build::swm_bridge::catalog_dir_for(cfg, None);
|
||||||
std::fs::create_dir_all(&catalog_dir)?;
|
std::fs::create_dir_all(&catalog_dir)?;
|
||||||
let recipe = hammer_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
|
let recipe = takana_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
|
||||||
let report = hammer_build::run_evidence(&recipe, cfg, store)?;
|
let report = takana_build::run_evidence(&recipe, cfg, store)?;
|
||||||
Ok(EvidenceVerdict {
|
Ok(EvidenceVerdict {
|
||||||
all_passed: report.all_passed(),
|
all_passed: report.all_passed(),
|
||||||
max_level: report.max_level_passed(),
|
max_level: report.max_level_passed(),
|
||||||
@@ -510,7 +510,7 @@ fn query_file(path: &str) -> serde_json::Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn query_artifact(store_root: &Path, hash: &str) -> serde_json::Value {
|
fn query_artifact(store_root: &Path, hash: &str) -> serde_json::Value {
|
||||||
let store = match hammer_core::Store::open(store_root) {
|
let store = match takana_core::Store::open(store_root) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return serde_json::json!({"error": e.to_string()}),
|
Err(e) => return serde_json::json!({"error": e.to_string()}),
|
||||||
};
|
};
|
||||||
@@ -552,7 +552,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_policy_resolves_by_uid_gid() {
|
fn config_policy_resolves_by_uid_gid() {
|
||||||
let cfg = hammer_core::AgentCapsConfig::from_toml(
|
let cfg = takana_core::AgentCapsConfig::from_toml(
|
||||||
r#"
|
r#"
|
||||||
default = ["query"]
|
default = ["query"]
|
||||||
[[rule]]
|
[[rule]]
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
use hammer_core::proto::Event;
|
use takana_core::proto::Event;
|
||||||
|
|
||||||
use crate::events::EventBus;
|
use crate::events::EventBus;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
use std::sync::mpsc::{self, Receiver, Sender};
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use hammer_core::proto::Event;
|
use takana_core::proto::Event;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventBus {
|
pub struct EventBus {
|
||||||
@@ -58,7 +58,7 @@ impl EventBus {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use hammer_core::proto::Event;
|
use takana_core::proto::Event;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn publish_reaches_all_subs() {
|
fn publish_reaches_all_subs() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct Args {
|
|||||||
journal: String,
|
journal: String,
|
||||||
/// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
|
/// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
|
||||||
/// activos). Default coincide con `hammer try`.
|
/// activos). Default coincide con `hammer try`.
|
||||||
#[arg(long, default_value = hammer_overlay::DEFAULT_STATE_ROOT)]
|
#[arg(long, default_value = takana_overlay::DEFAULT_STATE_ROOT)]
|
||||||
overlay_state_root: String,
|
overlay_state_root: String,
|
||||||
/// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
|
/// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
|
||||||
#[arg(long = "watch")]
|
#[arg(long = "watch")]
|
||||||
@@ -97,7 +97,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Watcher fanotify: opcional. Si falla (no caps), el daemon sigue con el bus solo.
|
// Watcher fanotify: opcional. Si falla (no caps), el daemon sigue con el bus solo.
|
||||||
if !args.no_watcher {
|
if !args.no_watcher {
|
||||||
let journal = hammer_journal::Journal::open(&args.journal)?;
|
let journal = takana_journal::Journal::open(&args.journal)?;
|
||||||
let mut dirs = watcher::default_watch_dirs();
|
let mut dirs = watcher::default_watch_dirs();
|
||||||
dirs.extend(args.extra_watch.clone());
|
dirs.extend(args.extra_watch.clone());
|
||||||
let overlay_root = PathBuf::from(&args.overlay_state_root);
|
let overlay_root = PathBuf::from(&args.overlay_state_root);
|
||||||
@@ -132,7 +132,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Bus de agente: bloqueante en este thread. Si falla, fin del daemon.
|
// Bus de agente: bloqueante en este thread. Si falla, fin del daemon.
|
||||||
// Política de caps: del fichero si existe, si no la heurística por-UID por defecto.
|
// Política de caps: del fichero si existe, si no la heurística por-UID por defecto.
|
||||||
let policy = match hammer_core::AgentCapsConfig::load(&args.agent_caps) {
|
let policy = match takana_core::AgentCapsConfig::load(&args.agent_caps) {
|
||||||
Ok(Some(cfg)) => {
|
Ok(Some(cfg)) => {
|
||||||
tracing::info!(path = %args.agent_caps.display(), rules = cfg.rule.len(),
|
tracing::info!(path = %args.agent_caps.display(), rules = cfg.rule.len(),
|
||||||
"bus: política de caps cargada de fichero");
|
"bus: política de caps cargada de fichero");
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! * Marcamos como `FAN_MARK_ADD | FAN_MARK_ONLYDIR` cada directorio vigilado. La cobertura
|
//! * Marcamos como `FAN_MARK_ADD | FAN_MARK_ONLYDIR` cada directorio vigilado. La cobertura
|
||||||
//! por defecto (ver `default_watch_dirs`) cubre el FHS clásico de userland.
|
//! por defecto (ver `default_watch_dirs`) cubre el FHS clásico de userland.
|
||||||
//! * Filtro mínimo: ignoramos eventos cuya ruta resuelta cae dentro de un overlay activo
|
//! * Filtro mínimo: ignoramos eventos cuya ruta resuelta cae dentro de un overlay activo
|
||||||
//! (consultamos `hammer_overlay::status` en cada batch — para Fase 3 el coste es
|
//! (consultamos `takana_overlay::status` en cada batch — para Fase 3 el coste es
|
||||||
//! despreciable; cuando los overlays se vuelvan numerosos cachearemos los paths).
|
//! despreciable; cuando los overlays se vuelvan numerosos cachearemos los paths).
|
||||||
//! * Cada evento se materializa como `MutationEvent` con `source = External` y la `op`
|
//! * Cada evento se materializa como `MutationEvent` con `source = External` y la `op`
|
||||||
//! se deriva (`classify_op`): si el fd resuelve a un path borrado (`" (deleted)"`) es
|
//! se deriva (`classify_op`): si el fd resuelve a un path borrado (`" (deleted)"`) es
|
||||||
@@ -27,7 +27,7 @@ use std::os::fd::AsFd;
|
|||||||
use std::os::unix::ffi::OsStringExt;
|
use std::os::unix::ffi::OsStringExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use hammer_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
use takana_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
||||||
use nix::fcntl::AT_FDCWD;
|
use nix::fcntl::AT_FDCWD;
|
||||||
use nix::sys::fanotify::{EventFFlags, Fanotify, InitFlags, MarkFlags, MaskFlags};
|
use nix::sys::fanotify::{EventFFlags, Fanotify, InitFlags, MarkFlags, MaskFlags};
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ pub enum Error {
|
|||||||
#[error("read_events: {0}")]
|
#[error("read_events: {0}")]
|
||||||
Read(nix::errno::Errno),
|
Read(nix::errno::Errno),
|
||||||
#[error("journal: {0}")]
|
#[error("journal: {0}")]
|
||||||
Journal(#[from] hammer_journal::Error),
|
Journal(#[from] takana_journal::Error),
|
||||||
#[error("io: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ impl Watcher {
|
|||||||
let content_hash = if deleted {
|
let content_hash = if deleted {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
match hammer_journal::hash_file(&path) {
|
match takana_journal::hash_file(&path) {
|
||||||
Ok(h) => h,
|
Ok(h) => h,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(path = %path.display(), error = %e, "watcher: no pude hashear contenido");
|
tracing::warn!(path = %path.display(), error = %e, "watcher: no pude hashear contenido");
|
||||||
@@ -170,7 +170,7 @@ impl Watcher {
|
|||||||
};
|
};
|
||||||
let op = self.classify_op(&path, deleted)?;
|
let op = self.classify_op(&path, deleted)?;
|
||||||
let me = MutationEvent {
|
let me = MutationEvent {
|
||||||
ts: hammer_journal::now_rfc3339(),
|
ts: takana_journal::now_rfc3339(),
|
||||||
op,
|
op,
|
||||||
path,
|
path,
|
||||||
by: Actor {
|
by: Actor {
|
||||||
@@ -188,7 +188,7 @@ impl Watcher {
|
|||||||
}
|
}
|
||||||
recorded += 1;
|
recorded += 1;
|
||||||
if let Some(bus) = &self.events {
|
if let Some(bus) = &self.events {
|
||||||
bus.publish(&hammer_core::proto::Event::Modified {
|
bus.publish(&takana_core::proto::Event::Modified {
|
||||||
path: me.path.display().to_string(),
|
path: me.path.display().to_string(),
|
||||||
op: me.op.as_str().to_string(),
|
op: me.op.as_str().to_string(),
|
||||||
ts: me.ts.clone(),
|
ts: me.ts.clone(),
|
||||||
@@ -216,7 +216,7 @@ impl Watcher {
|
|||||||
let Some(root) = &self.overlay_state_root else {
|
let Some(root) = &self.overlay_state_root else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
match hammer_overlay::status(root) {
|
match takana_overlay::status(root) {
|
||||||
Ok(overlays) => overlays
|
Ok(overlays) => overlays
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|o| o.mounts.into_iter().map(|m| m.target))
|
.flat_map(|o| o.mounts.into_iter().map(|m| m.target))
|
||||||
@@ -301,7 +301,7 @@ mod tests {
|
|||||||
|
|
||||||
fn me(op: MutationOp, path: &str, hash: Option<&str>) -> MutationEvent {
|
fn me(op: MutationOp, path: &str, hash: Option<&str>) -> MutationEvent {
|
||||||
MutationEvent {
|
MutationEvent {
|
||||||
ts: hammer_journal::now_rfc3339(),
|
ts: takana_journal::now_rfc3339(),
|
||||||
op,
|
op,
|
||||||
path: PathBuf::from(path),
|
path: PathBuf::from(path),
|
||||||
by: Actor { source: Source::External, pid: Some(1), uid: Some(0) },
|
by: Actor { source: Source::External, pid: Some(1), uid: Some(0) },
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use std::path::PathBuf;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_core::proto::Event;
|
use takana_core::proto::Event;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use std::sync::Arc;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline};
|
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline};
|
||||||
|
|
||||||
/// Helper: lanza un bus en background y devuelve la ruta del socket. Limpia al final del
|
/// Helper: lanza un bus en background y devuelve la ruta del socket. Limpia al final del
|
||||||
/// test gracias al `tempfile::TempDir` que se le pasa al caller.
|
/// test gracias al `tempfile::TempDir` que se le pasa al caller.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-agent"
|
name = "takana-agent"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -14,8 +14,8 @@ default = []
|
|||||||
llm-claude = ["dep:ureq"]
|
llm-claude = ["dep:ureq"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-overlay.workspace = true
|
takana-overlay.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
@@ -26,5 +26,5 @@ ureq = { version = "2", optional = true, default-features = false, features = ["
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
@@ -17,7 +17,7 @@ use std::sync::{Arc, Condvar, Mutex};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline};
|
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
@@ -49,7 +49,7 @@ pub struct Welcome {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CompileOutcome {
|
pub struct CompileOutcome {
|
||||||
pub artifact: String,
|
pub artifact: String,
|
||||||
pub verdict: Option<hammer_core::proto::EvidenceVerdict>,
|
pub verdict: Option<takana_core::proto::EvidenceVerdict>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Estado compartido entre el reader thread y el caller.
|
/// Estado compartido entre el reader thread y el caller.
|
||||||
@@ -111,7 +111,7 @@ impl AgentClient {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
me.send(&Command::Hello {
|
me.send(&Command::Hello {
|
||||||
ver: hammer_core::proto::PROTOCOL_VERSION,
|
ver: takana_core::proto::PROTOCOL_VERSION,
|
||||||
client: client.to_string(),
|
client: client.to_string(),
|
||||||
})?;
|
})?;
|
||||||
let ev = me.recv_pending(Duration::from_secs(5))?;
|
let ev = me.recv_pending(Duration::from_secs(5))?;
|
||||||
@@ -182,7 +182,7 @@ impl AgentClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evalúa una expresión del mini-lenguaje (`bin:grep`, `pin:musl`, …) en el daemon.
|
/// Evalúa una expresión del mini-lenguaje (`bin:grep`, `pin:musl`, …) en el daemon.
|
||||||
/// El JSON devuelto sigue el schema de `hammer_core::query::eval`.
|
/// El JSON devuelto sigue el schema de `takana_core::query::eval`.
|
||||||
pub fn query_expr(&mut self, expr: &str, timeout: Duration) -> Result<serde_json::Value> {
|
pub fn query_expr(&mut self, expr: &str, timeout: Duration) -> Result<serde_json::Value> {
|
||||||
self.send(&Command::Query {
|
self.send(&Command::Query {
|
||||||
what: "expr".into(),
|
what: "expr".into(),
|
||||||
@@ -38,11 +38,11 @@ pub enum Error {
|
|||||||
#[error("io: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("overlay: {0}")]
|
#[error("overlay: {0}")]
|
||||||
Overlay(#[from] hammer_overlay::Error),
|
Overlay(#[from] takana_overlay::Error),
|
||||||
#[error("apply: {0}")]
|
#[error("apply: {0}")]
|
||||||
Apply(#[from] hammer_core::apply::ApplyError),
|
Apply(#[from] takana_core::apply::ApplyError),
|
||||||
#[error("core: {0}")]
|
#[error("core: {0}")]
|
||||||
Core(#[from] hammer_core::Error),
|
Core(#[from] takana_core::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
//! schema → swm.verify_schema()
|
//! schema → swm.verify_schema()
|
||||||
//! base → swm.verify_base(local) → debe ser Ok
|
//! base → swm.verify_base(local) → debe ser Ok
|
||||||
//! build → para cada source_patch: AgentClient.compile() vía el bus (o saltar si --no-bus)
|
//! build → para cada source_patch: AgentClient.compile() vía el bus (o saltar si --no-bus)
|
||||||
//! try → hammer_overlay::try_overlay() (o usar --prefix)
|
//! try → takana_overlay::try_overlay() (o usar --prefix)
|
||||||
//! apply → hammer-core::apply para config_edit/file_drop + hydrate para source_patch
|
//! apply → hammer-core::apply para config_edit/file_drop + hydrate para source_patch
|
||||||
//! verify → spot-checks post-condición sobre disco
|
//! verify → spot-checks post-condición sobre disco
|
||||||
//! propose → devuelve Proposal con overlay_id + log de checks
|
//! propose → devuelve Proposal con overlay_id + log de checks
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
|
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
|
||||||
use hammer_core::proto::RecipeInline;
|
use takana_core::proto::RecipeInline;
|
||||||
use hammer_core::swm::{Mutation, Swm};
|
use takana_core::swm::{Mutation, Swm};
|
||||||
use hammer_core::BaseRef;
|
use takana_core::BaseRef;
|
||||||
use hammer_overlay::OverlayId;
|
use takana_overlay::OverlayId;
|
||||||
|
|
||||||
use crate::client::AgentClient;
|
use crate::client::AgentClient;
|
||||||
use crate::translator::{IntentTranslator, SystemContext};
|
use crate::translator::{IntentTranslator, SystemContext};
|
||||||
@@ -79,7 +79,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
|||||||
|
|
||||||
// 3. BASE
|
// 3. BASE
|
||||||
match swm.verify_base(&self.base) {
|
match swm.verify_base(&self.base) {
|
||||||
hammer_core::BaseCompat::Ok => {}
|
takana_core::BaseCompat::Ok => {}
|
||||||
other => {
|
other => {
|
||||||
return Err(Error::Orchestrate(format!(
|
return Err(Error::Orchestrate(format!(
|
||||||
"base incompatible: {other:?}"
|
"base incompatible: {other:?}"
|
||||||
@@ -91,7 +91,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
|||||||
let (overlay_id, prefix): (Option<OverlayId>, Option<PathBuf>) = match &self.apply {
|
let (overlay_id, prefix): (Option<OverlayId>, Option<PathBuf>) = match &self.apply {
|
||||||
ApplyTarget::Overlay { state_root } => {
|
ApplyTarget::Overlay { state_root } => {
|
||||||
std::fs::create_dir_all(state_root)?;
|
std::fs::create_dir_all(state_root)?;
|
||||||
let id = hammer_overlay::try_overlay(&[], state_root)?;
|
let id = takana_overlay::try_overlay(&[], state_root)?;
|
||||||
(Some(id), None)
|
(Some(id), None)
|
||||||
}
|
}
|
||||||
ApplyTarget::Prefix { prefix } => {
|
ApplyTarget::Prefix { prefix } => {
|
||||||
@@ -147,8 +147,8 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
|||||||
}
|
}
|
||||||
Mutation::InitRule { action, service, command } => {
|
Mutation::InitRule { action, service, command } => {
|
||||||
let rules_dir =
|
let rules_dir =
|
||||||
rebase_path(hammer_core::apply::INIT_RULES_DIR, prefix.as_deref());
|
rebase_path(takana_core::apply::INIT_RULES_DIR, prefix.as_deref());
|
||||||
let p = hammer_core::apply::apply_init_rule(
|
let p = takana_core::apply::apply_init_rule(
|
||||||
&rules_dir, action, service, command,
|
&rules_dir, action, service, command,
|
||||||
)
|
)
|
||||||
.map_err(|e| Error::Orchestrate(format!("init_rule '{action} {service}': {e}")))?;
|
.map_err(|e| Error::Orchestrate(format!("init_rule '{action} {service}': {e}")))?;
|
||||||
@@ -246,7 +246,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
|||||||
|
|
||||||
// Hidratamos localmente desde el store (sin pasar por el bus, evita un
|
// Hidratamos localmente desde el store (sin pasar por el bus, evita un
|
||||||
// segundo Inject que duplicaría trabajo).
|
// segundo Inject que duplicaría trabajo).
|
||||||
let store = hammer_core::Store::open(store)?;
|
let store = takana_core::Store::open(store)?;
|
||||||
let artifact_dir = store
|
let artifact_dir = store
|
||||||
.find_by_hash(&artifact)
|
.find_by_hash(&artifact)
|
||||||
.map_err(|e| Error::Orchestrate(format!("find_by_hash: {e}")))?;
|
.map_err(|e| Error::Orchestrate(format!("find_by_hash: {e}")))?;
|
||||||
@@ -340,13 +340,13 @@ fn wait_for_crash(policy: &RepairPolicy) -> Option<CrashInfo> {
|
|||||||
let deadline = std::time::Instant::now() + policy.crash_window;
|
let deadline = std::time::Instant::now() + policy.crash_window;
|
||||||
// Drenamos primero la cola asíncrona ya acumulada (eventos llegados durante el `run`).
|
// Drenamos primero la cola asíncrona ya acumulada (eventos llegados durante el `run`).
|
||||||
for ev in client.drain_async() {
|
for ev in client.drain_async() {
|
||||||
if let hammer_core::proto::Event::Crashed { service, code } = ev {
|
if let takana_core::proto::Event::Crashed { service, code } = ev {
|
||||||
return Some(CrashInfo { service, code });
|
return Some(CrashInfo { service, code });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while let Some(remaining) = deadline.checked_duration_since(std::time::Instant::now()) {
|
while let Some(remaining) = deadline.checked_duration_since(std::time::Instant::now()) {
|
||||||
match client.next_async(remaining) {
|
match client.next_async(remaining) {
|
||||||
Ok(hammer_core::proto::Event::Crashed { service, code }) => {
|
Ok(takana_core::proto::Event::Crashed { service, code }) => {
|
||||||
return Some(CrashInfo { service, code });
|
return Some(CrashInfo { service, code });
|
||||||
}
|
}
|
||||||
Ok(_other) => continue,
|
Ok(_other) => continue,
|
||||||
@@ -554,7 +554,7 @@ impl Default for RepairPolicy {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::translator::MockTranslator;
|
use crate::translator::MockTranslator;
|
||||||
use hammer_core::swm::{Base, Mutation};
|
use takana_core::swm::{Base, Mutation};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn baseref() -> BaseRef {
|
fn baseref() -> BaseRef {
|
||||||
@@ -626,7 +626,7 @@ mod tests {
|
|||||||
strip_components: None,
|
strip_components: None,
|
||||||
patch: None,
|
patch: None,
|
||||||
patch_url: None,
|
patch_url: None,
|
||||||
build: hammer_core::swm::SwmBuild {
|
build: takana_core::swm::SwmBuild {
|
||||||
compiler: "zig-cc".into(),
|
compiler: "zig-cc".into(),
|
||||||
target: "x86_64-linux-musl".into(),
|
target: "x86_64-linux-musl".into(),
|
||||||
link: "static".into(),
|
link: "static".into(),
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use hammer_core::{BaseRef, Swm};
|
use takana_core::{BaseRef, Swm};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TranslateError {
|
pub enum TranslateError {
|
||||||
@@ -165,7 +165,7 @@ impl IntentTranslator for MockTranslator {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use hammer_core::swm::{Base, Mutation};
|
use takana_core::swm::{Base, Mutation};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn empty_ctx() -> SystemContext {
|
fn empty_ctx() -> SystemContext {
|
||||||
+2
-2
@@ -31,7 +31,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::translator::{IntentTranslator, SystemContext, TranslateError};
|
use crate::translator::{IntentTranslator, SystemContext, TranslateError};
|
||||||
use hammer_core::Swm;
|
use takana_core::Swm;
|
||||||
|
|
||||||
/// Inyección HTTP. La implementación por defecto ([`UreqHttp`]) usa `ureq` síncrono;
|
/// Inyección HTTP. La implementación por defecto ([`UreqHttp`]) usa `ureq` síncrono;
|
||||||
/// los tests pasan una fake.
|
/// los tests pasan una fake.
|
||||||
@@ -279,7 +279,7 @@ mod tests {
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use hammer_core::BaseRef;
|
use takana_core::BaseRef;
|
||||||
|
|
||||||
/// Fake HTTP que captura el request y devuelve un body pre-armado.
|
/// Fake HTTP que captura el request y devuelve un body pre-armado.
|
||||||
struct FakeHttp {
|
struct FakeHttp {
|
||||||
@@ -11,12 +11,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
use hammer_agent::{
|
use takana_agent::{
|
||||||
orchestrator::{ApplyTarget, CompileMode},
|
orchestrator::{ApplyTarget, CompileMode},
|
||||||
MockTranslator, Orchestrator,
|
MockTranslator, Orchestrator,
|
||||||
};
|
};
|
||||||
use hammer_core::swm::{Base, Mutation, Swm};
|
use takana_core::swm::{Base, Mutation, Swm};
|
||||||
use hammer_core::{ArtifactHash, BaseRef};
|
use takana_core::{ArtifactHash, BaseRef};
|
||||||
|
|
||||||
fn baseref() -> BaseRef {
|
fn baseref() -> BaseRef {
|
||||||
let mut pins = BTreeMap::new();
|
let mut pins = BTreeMap::new();
|
||||||
@@ -86,7 +86,7 @@ fn intent_to_overlay_proposal_via_mock_translator() {
|
|||||||
proposal
|
proposal
|
||||||
.checks
|
.checks
|
||||||
.iter()
|
.iter()
|
||||||
.all(|c| matches!(c.level, hammer_agent::orchestrator::CheckLevel::Pass)),
|
.all(|c| matches!(c.level, takana_agent::orchestrator::CheckLevel::Pass)),
|
||||||
"todas las checks deben pasar"
|
"todas las checks deben pasar"
|
||||||
);
|
);
|
||||||
|
|
||||||
+8
-8
@@ -10,7 +10,7 @@ use std::path::PathBuf;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline, PROTOCOL_VERSION};
|
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline, PROTOCOL_VERSION};
|
||||||
|
|
||||||
fn start_stub_bus(sock: PathBuf) -> std::sync::mpsc::Sender<Event> {
|
fn start_stub_bus(sock: PathBuf) -> std::sync::mpsc::Sender<Event> {
|
||||||
let (tx_async, rx_async) = std::sync::mpsc::channel::<Event>();
|
let (tx_async, rx_async) = std::sync::mpsc::channel::<Event>();
|
||||||
@@ -86,14 +86,14 @@ fn stub_serve(stream: UnixStream, rx_async: std::sync::mpsc::Receiver<Event>) {
|
|||||||
// El stub refleja la evidencia declarada como un veredicto "todo pasa" para
|
// El stub refleja la evidencia declarada como un veredicto "todo pasa" para
|
||||||
// ejercitar el camino H1c del cliente (sin lab real que la ejecute).
|
// ejercitar el camino H1c del cliente (sin lab real que la ejecute).
|
||||||
let verdict = (!recipe.evidence.is_empty()).then(|| {
|
let verdict = (!recipe.evidence.is_empty()).then(|| {
|
||||||
hammer_core::proto::EvidenceVerdict {
|
takana_core::proto::EvidenceVerdict {
|
||||||
all_passed: true,
|
all_passed: true,
|
||||||
max_level: recipe.evidence.checks.iter().map(|c| c.kind).max(),
|
max_level: recipe.evidence.checks.iter().map(|c| c.kind).max(),
|
||||||
checks: recipe
|
checks: recipe
|
||||||
.evidence
|
.evidence
|
||||||
.checks
|
.checks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| hammer_core::proto::EvidenceCheckVerdict {
|
.map(|c| takana_core::proto::EvidenceCheckVerdict {
|
||||||
kind: c.kind,
|
kind: c.kind,
|
||||||
cmd: c.cmd.clone(),
|
cmd: c.cmd.clone(),
|
||||||
passed: true,
|
passed: true,
|
||||||
@@ -152,7 +152,7 @@ fn client_handshake_compile_and_async_modified() {
|
|||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client = hammer_agent::AgentClient::connect(&sock).expect("connect");
|
let mut client = takana_agent::AgentClient::connect(&sock).expect("connect");
|
||||||
assert_eq!(client.welcome.ver, PROTOCOL_VERSION);
|
assert_eq!(client.welcome.ver, PROTOCOL_VERSION);
|
||||||
assert!(client.welcome.caps.contains(&Cap::Compile));
|
assert!(client.welcome.caps.contains(&Cap::Compile));
|
||||||
|
|
||||||
@@ -167,9 +167,9 @@ fn client_handshake_compile_and_async_modified() {
|
|||||||
target: "x86_64-linux-musl".into(),
|
target: "x86_64-linux-musl".into(),
|
||||||
link: "static".into(),
|
link: "static".into(),
|
||||||
flags: vec![],
|
flags: vec![],
|
||||||
evidence: hammer_core::Evidence {
|
evidence: takana_core::Evidence {
|
||||||
checks: vec![hammer_core::EvidenceCheck {
|
checks: vec![takana_core::EvidenceCheck {
|
||||||
kind: hammer_core::EvidenceKind::CmdExit,
|
kind: takana_core::EvidenceKind::CmdExit,
|
||||||
cmd: "grep --version".into(),
|
cmd: "grep --version".into(),
|
||||||
expected_exit: 0,
|
expected_exit: 0,
|
||||||
expected_output: None,
|
expected_output: None,
|
||||||
@@ -184,7 +184,7 @@ fn client_handshake_compile_and_async_modified() {
|
|||||||
let verdict = outcome.verdict.expect("verdict presente (declaramos evidencia)");
|
let verdict = outcome.verdict.expect("verdict presente (declaramos evidencia)");
|
||||||
assert!(verdict.all_passed);
|
assert!(verdict.all_passed);
|
||||||
assert_eq!(verdict.checks.len(), 1);
|
assert_eq!(verdict.checks.len(), 1);
|
||||||
assert_eq!(verdict.max_level, Some(hammer_core::EvidenceKind::CmdExit));
|
assert_eq!(verdict.max_level, Some(takana_core::EvidenceKind::CmdExit));
|
||||||
|
|
||||||
// El stub también puede empujar eventos asíncronos. Inyectamos un Modified y vemos
|
// El stub también puede empujar eventos asíncronos. Inyectamos un Modified y vemos
|
||||||
// que el cliente lo entrega por drain_async()/next_async().
|
// que el cliente lo entrega por drain_async()/next_async().
|
||||||
+5
-5
@@ -10,13 +10,13 @@ use std::path::PathBuf;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
|
use takana_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
|
||||||
use hammer_agent::MockTranslator;
|
use takana_agent::MockTranslator;
|
||||||
use hammer_core::proto::{
|
use takana_core::proto::{
|
||||||
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, PROTOCOL_VERSION,
|
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, PROTOCOL_VERSION,
|
||||||
};
|
};
|
||||||
use hammer_core::swm::{Base, Mutation, Swm, SwmBuild};
|
use takana_core::swm::{Base, Mutation, Swm, SwmBuild};
|
||||||
use hammer_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
|
use takana_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Bus stub que responde a `Compile` con `BuildReady` cuyo veredicto tiene `all_passed=false`.
|
/// Bus stub que responde a `Compile` con `BuildReady` cuyo veredicto tiene `all_passed=false`.
|
||||||
@@ -11,12 +11,12 @@ use std::sync::{Arc, Mutex};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use hammer_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
|
use takana_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
|
||||||
use hammer_core::swm::{Base, Mutation};
|
use takana_core::swm::{Base, Mutation};
|
||||||
use hammer_core::{BaseRef, Swm};
|
use takana_core::{BaseRef, Swm};
|
||||||
|
|
||||||
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
|
use takana_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
|
||||||
use hammer_agent::{MockTranslator, Orchestrator};
|
use takana_agent::{MockTranslator, Orchestrator};
|
||||||
|
|
||||||
/// El plan por conexión: lista de eventos a empujar tras el Welcome. El stub itera
|
/// El plan por conexión: lista de eventos a empujar tras el Welcome. El stub itera
|
||||||
/// por las conexiones en orden y consume un slot del plan por cada una.
|
/// por las conexiones en orden y consume un slot del plan por cada una.
|
||||||
@@ -112,7 +112,7 @@ fn swm_touch(path: &str) -> Swm {
|
|||||||
mutations: vec![Mutation::FileDrop {
|
mutations: vec![Mutation::FileDrop {
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
// hash de "X" (un byte 0x58) — calculado con BLAKE3.
|
// hash de "X" (un byte 0x58) — calculado con BLAKE3.
|
||||||
content_hash: hammer_core::ArtifactHash::of_inputs(&[b"X"]).as_str().to_string(),
|
content_hash: takana_core::ArtifactHash::of_inputs(&[b"X"]).as_str().to_string(),
|
||||||
content_b64: Some("WA==".into()),
|
content_b64: Some("WA==".into()),
|
||||||
content_url: None,
|
content_url: None,
|
||||||
}],
|
}],
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-bootstrap"
|
name = "takana-bootstrap"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,8 +8,8 @@ repository.workspace = true
|
|||||||
description = "Bootstrap from-scratch de hammer: Stage 0/1/2 hacia el auto-alojamiento (SDD 11)."
|
description = "Bootstrap from-scratch de hammer: Stage 0/1/2 hacia el auto-alojamiento (SDD 11)."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-build.workspace = true
|
takana-build.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -17,8 +17,8 @@ pub mod manifest;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_build::download;
|
use takana_build::download;
|
||||||
use hammer_core::{ArtifactHash, Recipe, Store};
|
use takana_core::{ArtifactHash, Recipe, Store};
|
||||||
|
|
||||||
pub use manifest::{BootstrapManifest, StageEntry};
|
pub use manifest::{BootstrapManifest, StageEntry};
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ pub enum Error {
|
|||||||
#[error("io: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("core: {0}")]
|
#[error("core: {0}")]
|
||||||
Core(#[from] hammer_core::Error),
|
Core(#[from] takana_core::Error),
|
||||||
#[error("tar -x de la semilla falló (exit {0:?})")]
|
#[error("tar -x de la semilla falló (exit {0:?})")]
|
||||||
Untar(Option<i32>),
|
Untar(Option<i32>),
|
||||||
#[error("bootstrap: {0}")]
|
#[error("bootstrap: {0}")]
|
||||||
@@ -147,17 +147,17 @@ fn locate_tool_dir(base: &Path, tool: &str) -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deriva una [`hammer_build::BuildConfig`] desde `base` cuyo `zig_dir` apunta a la **semilla
|
/// Deriva una [`takana_build::BuildConfig`] desde `base` cuyo `zig_dir` apunta a la **semilla
|
||||||
/// sellada** en vez del toolchain del host: así las recetas del Stage 1 cross-compilan con el
|
/// sellada** en vez del toolchain del host: así las recetas del Stage 1 cross-compilan con el
|
||||||
/// toolchain fijado por hash (`seed_hash`), no con el `.dev-fs/tools/zig` de desarrollo. El resto
|
/// toolchain fijado por hash (`seed_hash`), no con el `.dev-fs/tools/zig` de desarrollo. El resto
|
||||||
/// de la config (rootfs, work, cache) se hereda intacto.
|
/// de la config (rootfs, work, cache) se hereda intacto.
|
||||||
pub fn seed_build_config(
|
pub fn seed_build_config(
|
||||||
base: &hammer_build::BuildConfig,
|
base: &takana_build::BuildConfig,
|
||||||
seed: &SeedSpec,
|
seed: &SeedSpec,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<hammer_build::BuildConfig> {
|
) -> Result<takana_build::BuildConfig> {
|
||||||
let zig_dir = seed.toolchain_dir(store)?;
|
let zig_dir = seed.toolchain_dir(store)?;
|
||||||
Ok(hammer_build::BuildConfig { zig_dir, ..base.clone() })
|
Ok(takana_build::BuildConfig { zig_dir, ..base.clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hash de un rootfs sellado por Stage 1. Es un `ArtifactHash` derivado del **contenido lógico**
|
/// Hash de un rootfs sellado por Stage 1. Es un `ArtifactHash` derivado del **contenido lógico**
|
||||||
@@ -265,15 +265,15 @@ pub struct Stage1Spec {
|
|||||||
///
|
///
|
||||||
/// Idempotente: si el rootfs (mismo hash de contenido) ya está sellado, no reconstruye. El
|
/// Idempotente: si el rootfs (mismo hash de contenido) ya está sellado, no reconstruye. El
|
||||||
/// cross-compile real requiere el rootfs base + toolchain del lab; en una máquina sin `.dev-fs`
|
/// cross-compile real requiere el rootfs base + toolchain del lab; en una máquina sin `.dev-fs`
|
||||||
/// `hammer_build::build` falla limpio antes de ensamblar nada.
|
/// `takana_build::build` falla limpio antes de ensamblar nada.
|
||||||
pub fn stage1(
|
pub fn stage1(
|
||||||
spec: &Stage1Spec,
|
spec: &Stage1Spec,
|
||||||
base_cfg: &hammer_build::BuildConfig,
|
base_cfg: &takana_build::BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<RootfsHash> {
|
) -> Result<RootfsHash> {
|
||||||
// Toolchain = la semilla sellada, no el zig del host.
|
// Toolchain = la semilla sellada, no el zig del host.
|
||||||
let zig_dir = seed_toolchain_dir(store, &spec.seed_hash, spec.seed_kind)?;
|
let zig_dir = seed_toolchain_dir(store, &spec.seed_hash, spec.seed_kind)?;
|
||||||
let cfg = hammer_build::BuildConfig { zig_dir, ..base_cfg.clone() };
|
let cfg = takana_build::BuildConfig { zig_dir, ..base_cfg.clone() };
|
||||||
|
|
||||||
// 1) Construir cada componente (cacheado por el lab si ya existe).
|
// 1) Construir cada componente (cacheado por el lab si ya existe).
|
||||||
let mut components: Vec<(String, ArtifactHash)> = Vec::with_capacity(STAGE1_COMPONENTS.len());
|
let mut components: Vec<(String, ArtifactHash)> = Vec::with_capacity(STAGE1_COMPONENTS.len());
|
||||||
@@ -282,7 +282,7 @@ pub fn stage1(
|
|||||||
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
|
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
|
||||||
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
|
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
|
||||||
})?;
|
})?;
|
||||||
let h = hammer_build::build(&recipe, &cfg, store)?;
|
let h = takana_build::build(&recipe, &cfg, store)?;
|
||||||
components.push((name.to_string(), h));
|
components.push((name.to_string(), h));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +367,7 @@ fn assemble_rootfs(
|
|||||||
// Cada componente se proyecta al rootfs por hardlink (mismo store, mismo filesystem).
|
// Cada componente se proyecta al rootfs por hardlink (mismo store, mismo filesystem).
|
||||||
for (name, h) in components {
|
for (name, h) in components {
|
||||||
let dir = store.path_of(h, name);
|
let dir = store.path_of(h, name);
|
||||||
hammer_build::run_hydrate(&dir, staging, hammer_core::LinkMode::Static, None)?;
|
takana_build::run_hydrate(&dir, staging, takana_core::LinkMode::Static, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// El sidecar de provenance de cada receta (`.hammer/recipe.toml`) no pertenece al rootfs
|
// El sidecar de provenance de cada receta (`.hammer/recipe.toml`) no pertenece al rootfs
|
||||||
@@ -605,14 +605,14 @@ fn assemble_product_rootfs(
|
|||||||
base_dir.display()
|
base_dir.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
hammer_build::run_hydrate(&base_dir, staging, hammer_core::LinkMode::Static, None)?;
|
takana_build::run_hydrate(&base_dir, staging, takana_core::LinkMode::Static, None)?;
|
||||||
|
|
||||||
// 2) userland Rust + servicios ENCIMA (hardlink). El userland va DESPUÉS de la base: sus symlinks
|
// 2) userland Rust + servicios ENCIMA (hardlink). El userland va DESPUÉS de la base: sus symlinks
|
||||||
// en /usr/bin reemplazan a los de busybox (run_hydrate sustituye el symlink existente) ⇒ el
|
// en /usr/bin reemplazan a los de busybox (run_hydrate sustituye el symlink existente) ⇒ el
|
||||||
// producto corre las tools Rust. Orden userland→servicios (sin colisiones entre sí).
|
// producto corre las tools Rust. Orden userland→servicios (sin colisiones entre sí).
|
||||||
for (name, h) in userland.iter().chain(services.iter()) {
|
for (name, h) in userland.iter().chain(services.iter()) {
|
||||||
let dir = store.path_of(h, name);
|
let dir = store.path_of(h, name);
|
||||||
hammer_build::run_hydrate(&dir, staging, hammer_core::LinkMode::Static, None)?;
|
takana_build::run_hydrate(&dir, staging, takana_core::LinkMode::Static, None)?;
|
||||||
}
|
}
|
||||||
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
|
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
|
||||||
|
|
||||||
@@ -681,7 +681,7 @@ fn assemble_product_rootfs(
|
|||||||
fn build_components(
|
fn build_components(
|
||||||
names: &[&str],
|
names: &[&str],
|
||||||
recipes_dir: &Path,
|
recipes_dir: &Path,
|
||||||
cfg: &hammer_build::BuildConfig,
|
cfg: &takana_build::BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<Vec<(String, ArtifactHash)>> {
|
) -> Result<Vec<(String, ArtifactHash)>> {
|
||||||
let mut out = Vec::with_capacity(names.len());
|
let mut out = Vec::with_capacity(names.len());
|
||||||
@@ -690,7 +690,7 @@ fn build_components(
|
|||||||
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
|
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
|
||||||
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
|
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
|
||||||
})?;
|
})?;
|
||||||
let h = hammer_build::build(&recipe, cfg, store)?;
|
let h = takana_build::build(&recipe, cfg, store)?;
|
||||||
out.push((name.to_string(), h));
|
out.push((name.to_string(), h));
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@@ -703,7 +703,7 @@ fn build_components(
|
|||||||
/// semilla): cada componente usa su `zig_version` propia, y si ya está sellado sale cacheado.
|
/// semilla): cada componente usa su `zig_version` propia, y si ya está sellado sale cacheado.
|
||||||
pub fn product(
|
pub fn product(
|
||||||
base: &RootfsHash,
|
base: &RootfsHash,
|
||||||
base_cfg: &hammer_build::BuildConfig,
|
base_cfg: &takana_build::BuildConfig,
|
||||||
recipes_dir: &Path,
|
recipes_dir: &Path,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<RootfsHash> {
|
) -> Result<RootfsHash> {
|
||||||
@@ -725,7 +725,7 @@ pub fn product_from_repo(
|
|||||||
base: &RootfsHash,
|
base: &RootfsHash,
|
||||||
repo_dir: &Path,
|
repo_dir: &Path,
|
||||||
trust_dir: &Path,
|
trust_dir: &Path,
|
||||||
base_cfg: &hammer_build::BuildConfig,
|
base_cfg: &takana_build::BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<RootfsHash> {
|
) -> Result<RootfsHash> {
|
||||||
// 1) userland + servicios REPRODUCIDOS desde el repo firmado (mismo shape que build_components).
|
// 1) userland + servicios REPRODUCIDOS desde el repo firmado (mismo shape que build_components).
|
||||||
@@ -780,10 +780,10 @@ pub fn install_components_from_repo(
|
|||||||
names: &[&str],
|
names: &[&str],
|
||||||
repo_dir: &Path,
|
repo_dir: &Path,
|
||||||
trust_dir: &Path,
|
trust_dir: &Path,
|
||||||
cfg: &hammer_build::BuildConfig,
|
cfg: &takana_build::BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<Vec<(String, ArtifactHash)>> {
|
) -> Result<Vec<(String, ArtifactHash)>> {
|
||||||
use hammer_core::{RepoIndex, SigStatus, TrustStore};
|
use takana_core::{RepoIndex, SigStatus, TrustStore};
|
||||||
|
|
||||||
let index = RepoIndex::load(repo_dir)?;
|
let index = RepoIndex::load(repo_dir)?;
|
||||||
// Firma del release: exigimos TRUSTED. Un release no firmado / con clave desconocida / con firma
|
// Firma del release: exigimos TRUSTED. Un release no firmado / con clave desconocida / con firma
|
||||||
@@ -801,7 +801,7 @@ pub fn install_components_from_repo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(cfg, None);
|
let catalog_dir = takana_build::swm_bridge::catalog_dir_for(cfg, None);
|
||||||
std::fs::create_dir_all(&catalog_dir)?;
|
std::fs::create_dir_all(&catalog_dir)?;
|
||||||
|
|
||||||
let mut out = Vec::with_capacity(names.len());
|
let mut out = Vec::with_capacity(names.len());
|
||||||
@@ -817,7 +817,7 @@ pub fn install_components_from_repo(
|
|||||||
let mutation = source_patch_of(&dep_swm).ok_or_else(|| {
|
let mutation = source_patch_of(&dep_swm).ok_or_else(|| {
|
||||||
Error::Other(format!("la dep '{}' no es un paquete source_patch", dep.name))
|
Error::Other(format!("la dep '{}' no es un paquete source_patch", dep.name))
|
||||||
})?;
|
})?;
|
||||||
let dep_recipe = hammer_build::swm_bridge::recipe_from_source_patch(
|
let dep_recipe = takana_build::swm_bridge::recipe_from_source_patch(
|
||||||
mutation,
|
mutation,
|
||||||
Some(&dep.name),
|
Some(&dep.name),
|
||||||
&catalog_dir,
|
&catalog_dir,
|
||||||
@@ -829,7 +829,7 @@ pub fn install_components_from_repo(
|
|||||||
let mutation = source_patch_of(&swm).ok_or_else(|| {
|
let mutation = source_patch_of(&swm).ok_or_else(|| {
|
||||||
Error::Other(format!("'{name}' no es un paquete source_patch"))
|
Error::Other(format!("'{name}' no es un paquete source_patch"))
|
||||||
})?;
|
})?;
|
||||||
let hash = hammer_build::swm_bridge::build_source_patch(mutation, cfg, store, None, Some(name))?;
|
let hash = takana_build::swm_bridge::build_source_patch(mutation, cfg, store, None, Some(name))?;
|
||||||
tracing::info!(%name, %hash, "product: componente reproducido desde el repo firmado");
|
tracing::info!(%name, %hash, "product: componente reproducido desde el repo firmado");
|
||||||
out.push((name.to_string(), hash));
|
out.push((name.to_string(), hash));
|
||||||
}
|
}
|
||||||
@@ -837,18 +837,18 @@ pub fn install_components_from_repo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lee un `.swm` (YAML) de disco a `Swm`. Equivalente al helper homónimo de la CLI (privado allí).
|
/// Lee un `.swm` (YAML) de disco a `Swm`. Equivalente al helper homónimo de la CLI (privado allí).
|
||||||
fn load_swm_file(path: &Path) -> Result<hammer_core::Swm> {
|
fn load_swm_file(path: &Path) -> Result<takana_core::Swm> {
|
||||||
let yaml = std::fs::read_to_string(path)
|
let yaml = std::fs::read_to_string(path)
|
||||||
.map_err(|e| Error::Other(format!("no pude leer {}: {e}", path.display())))?;
|
.map_err(|e| Error::Other(format!("no pude leer {}: {e}", path.display())))?;
|
||||||
hammer_core::Swm::from_yaml(&yaml)
|
takana_core::Swm::from_yaml(&yaml)
|
||||||
.map_err(|e| Error::Other(format!("no pude parsear {}: {e}", path.display())))
|
.map_err(|e| Error::Other(format!("no pude parsear {}: {e}", path.display())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Primera mutación `SourcePatch` de un `.swm` (un paquete hammer es un único source_patch).
|
/// Primera mutación `SourcePatch` de un `.swm` (un paquete hammer es un único source_patch).
|
||||||
fn source_patch_of(swm: &hammer_core::Swm) -> Option<&hammer_core::Mutation> {
|
fn source_patch_of(swm: &takana_core::Swm) -> Option<&takana_core::Mutation> {
|
||||||
swm.mutations
|
swm.mutations
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| matches!(m, hammer_core::Mutation::SourcePatch { .. }))
|
.find(|m| matches!(m, takana_core::Mutation::SourcePatch { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Producto ATESTADO: la capa FIRMADA que el gate de arje verifica al boot (I4 / Etapa D) ───────
|
// ── Producto ATESTADO: la capa FIRMADA que el gate de arje verifica al boot (I4 / Etapa D) ───────
|
||||||
@@ -966,7 +966,7 @@ fn assemble_attested_product_rootfs(
|
|||||||
base_dir.display()
|
base_dir.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
hammer_build::run_hydrate(&base_dir, staging, hammer_core::LinkMode::Static, None)?;
|
takana_build::run_hydrate(&base_dir, staging, takana_core::LinkMode::Static, None)?;
|
||||||
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
|
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
|
||||||
|
|
||||||
// 2) PISAR /usr/bin/arje-zero con el init CON gate. Es un hardlink read-only al store ⇒ romper el
|
// 2) PISAR /usr/bin/arje-zero con el init CON gate. Es un hardlink read-only al store ⇒ romper el
|
||||||
@@ -1071,7 +1071,7 @@ fn assemble_attested_product_rootfs(
|
|||||||
/// base quedan intactos (Separación Mecanismo/Política): el atestado es un sello APARTE.
|
/// base quedan intactos (Separación Mecanismo/Política): el atestado es un sello APARTE.
|
||||||
pub fn product_attested(
|
pub fn product_attested(
|
||||||
base: &RootfsHash,
|
base: &RootfsHash,
|
||||||
base_cfg: &hammer_build::BuildConfig,
|
base_cfg: &takana_build::BuildConfig,
|
||||||
recipes_dir: &Path,
|
recipes_dir: &Path,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
cfg: &AttestConfig,
|
cfg: &AttestConfig,
|
||||||
@@ -1245,7 +1245,7 @@ pub struct AllReport {
|
|||||||
pub fn all(
|
pub fn all(
|
||||||
seed: &SeedSpec,
|
seed: &SeedSpec,
|
||||||
recipes_dir: &Path,
|
recipes_dir: &Path,
|
||||||
base_cfg: &hammer_build::BuildConfig,
|
base_cfg: &takana_build::BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> Result<AllReport> {
|
) -> Result<AllReport> {
|
||||||
let seed_hash = stage0(seed, store)?;
|
let seed_hash = stage0(seed, store)?;
|
||||||
@@ -1505,7 +1505,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
|
|||||||
stage1_dir.display()
|
stage1_dir.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
hammer_build::run_hydrate(&stage1_dir, staging, hammer_core::LinkMode::Static, None)?;
|
takana_build::run_hydrate(&stage1_dir, staging, takana_core::LinkMode::Static, None)?;
|
||||||
|
|
||||||
// 2) Esqueleto del builder: el sandbox de build (/toolchain), el store, el work y la config.
|
// 2) Esqueleto del builder: el sandbox de build (/toolchain), el store, el work y la config.
|
||||||
for d in ["toolchain", "store", "work", "usr/bin", "etc/hammer/recipes"] {
|
for d in ["toolchain", "store", "work", "usr/bin", "etc/hammer/recipes"] {
|
||||||
@@ -1953,7 +1953,7 @@ mod tests {
|
|||||||
fn seed_build_config_points_zig_dir_at_seed() {
|
fn seed_build_config_points_zig_dir_at_seed() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let (store, seed) = seal_seed_with_zig_at(tmp.path(), "zig-linux-x86_64-0.13.0/zig");
|
let (store, seed) = seal_seed_with_zig_at(tmp.path(), "zig-linux-x86_64-0.13.0/zig");
|
||||||
let base = hammer_build::BuildConfig::defaults_for_store(store.root());
|
let base = takana_build::BuildConfig::defaults_for_store(store.root());
|
||||||
let cfg = seed_build_config(&base, &seed, &store).unwrap();
|
let cfg = seed_build_config(&base, &seed, &store).unwrap();
|
||||||
assert_eq!(cfg.zig_dir, seed.toolchain_dir(&store).unwrap());
|
assert_eq!(cfg.zig_dir, seed.toolchain_dir(&store).unwrap());
|
||||||
// El resto de la config se hereda sin cambios.
|
// El resto de la config se hereda sin cambios.
|
||||||
@@ -2360,7 +2360,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
|
|||||||
let path = recipes_dir.join(format!("{name}.toml"));
|
let path = recipes_dir.join(format!("{name}.toml"));
|
||||||
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
||||||
assert_eq!(&r.name, name);
|
assert_eq!(&r.name, name);
|
||||||
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).expect("hash");
|
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).expect("hash");
|
||||||
assert!(h.as_str().starts_with("b3:"));
|
assert!(h.as_str().starts_with("b3:"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2375,7 +2375,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
|
|||||||
let path = recipes_dir.join(format!("{name}.toml"));
|
let path = recipes_dir.join(format!("{name}.toml"));
|
||||||
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
||||||
assert_eq!(&r.name, name);
|
assert_eq!(&r.name, name);
|
||||||
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).expect("hash");
|
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).expect("hash");
|
||||||
assert!(h.as_str().starts_with("b3:"), "hash con prefijo b3:");
|
assert!(h.as_str().starts_with("b3:"), "hash con prefijo b3:");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2696,7 +2696,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
|
|||||||
}
|
}
|
||||||
let r = Recipe::load_from_path(&path)
|
let r = Recipe::load_from_path(&path)
|
||||||
.unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));
|
.unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));
|
||||||
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests())
|
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests())
|
||||||
.unwrap_or_else(|e| panic!("hash {}: {e}", path.display()));
|
.unwrap_or_else(|e| panic!("hash {}: {e}", path.display()));
|
||||||
assert!(h.as_str().starts_with("b3:"), "{}: hash con prefijo b3:", path.display());
|
assert!(h.as_str().starts_with("b3:"), "{}: hash con prefijo b3:", path.display());
|
||||||
count += 1;
|
count += 1;
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use hammer_core::{ArtifactHash, Store};
|
use takana_core::{ArtifactHash, Store};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-build"
|
name = "takana-build"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,7 +8,7 @@ repository.workspace = true
|
|||||||
description = "El laboratorio de hammer: sandbox de build (bubblewrap + zig cc) e hidratación."
|
description = "El laboratorio de hammer: sandbox de build (bubblewrap + zig cc) e hidratación."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -12,7 +12,7 @@ use std::process::Command;
|
|||||||
|
|
||||||
/// Descarga `url` y devuelve su contenido en memoria. Falla con el stderr de curl si el
|
/// Descarga `url` y devuelve su contenido en memoria. Falla con el stderr de curl si el
|
||||||
/// código de salida no es 0 (incluye errores HTTP gracias a `-f`).
|
/// código de salida no es 0 (incluye errores HTTP gracias a `-f`).
|
||||||
pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
|
pub fn fetch_url_bytes(url: &str) -> takana_core::Result<Vec<u8>> {
|
||||||
let out = Command::new("curl")
|
let out = Command::new("curl")
|
||||||
.args([
|
.args([
|
||||||
"-fsSL",
|
"-fsSL",
|
||||||
@@ -25,9 +25,9 @@ pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
|
|||||||
url,
|
url,
|
||||||
])
|
])
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"curl {url} falló (exit {}): {}",
|
"curl {url} falló (exit {}): {}",
|
||||||
out.status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into()),
|
out.status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into()),
|
||||||
String::from_utf8_lossy(&out.stderr).trim()
|
String::from_utf8_lossy(&out.stderr).trim()
|
||||||
@@ -37,7 +37,7 @@ pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Descarga `url` y lo escribe en `dst` (creando los directorios padre).
|
/// Descarga `url` y lo escribe en `dst` (creando los directorios padre).
|
||||||
pub fn fetch_url_to_file(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
pub fn fetch_url_to_file(url: &str, dst: &Path) -> takana_core::Result<()> {
|
||||||
let bytes = fetch_url_bytes(url)?;
|
let bytes = fetch_url_bytes(url)?;
|
||||||
if let Some(parent) = dst.parent() {
|
if let Some(parent) = dst.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
@@ -49,14 +49,14 @@ pub fn fetch_url_to_file(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
/// Verifica que el sha256 de `path` coincide con `expected` (hex, case-insensitive). Lo usan
|
/// Verifica que el sha256 de `path` coincide con `expected` (hex, case-insensitive). Lo usan
|
||||||
/// los consumidores que ingieren una fuente fijada (p. ej. el toolchain semilla del bootstrap,
|
/// los consumidores que ingieren una fuente fijada (p. ej. el toolchain semilla del bootstrap,
|
||||||
/// [SDD 11](../../docs/11-bootstrap.md)) y necesitan comprobar integridad **antes** de sellar.
|
/// [SDD 11](../../docs/11-bootstrap.md)) y necesitan comprobar integridad **antes** de sellar.
|
||||||
pub fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
|
pub fn verify_sha256(path: &Path, expected: &str) -> takana_core::Result<()> {
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
let mut f = std::fs::File::open(path)?;
|
let mut f = std::fs::File::open(path)?;
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
std::io::copy(&mut f, &mut hasher)?;
|
std::io::copy(&mut f, &mut hasher)?;
|
||||||
let got = hex::encode(hasher.finalize());
|
let got = hex::encode(hasher.finalize());
|
||||||
if !got.eq_ignore_ascii_case(expected) {
|
if !got.eq_ignore_ascii_case(expected) {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
|
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
|
||||||
path.display()
|
path.display()
|
||||||
)));
|
)));
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Fase `fetch`: materializa el árbol fuente de la receta en un directorio trabajable.
|
//! Fase `fetch`: materializa el árbol fuente de la receta en un directorio trabajable.
|
||||||
//!
|
//!
|
||||||
//! Dos modos, decididos por la receta (ver `hammer_core::SourceKind`):
|
//! Dos modos, decididos por la receta (ver `takana_core::SourceKind`):
|
||||||
//!
|
//!
|
||||||
//! * **Git** — mirror local en `<work_root>/repos/<name>.git` + `git archive` del commit
|
//! * **Git** — mirror local en `<work_root>/repos/<name>.git` + `git archive` del commit
|
||||||
//! fijado al árbol trabajable. El mirror se reusa entre builds; el árbol es descartable.
|
//! fijado al árbol trabajable. El mirror se reusa entre builds; el árbol es descartable.
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use hammer_core::{Recipe, SourceKind};
|
use takana_core::{Recipe, SourceKind};
|
||||||
|
|
||||||
/// Materializa el árbol fuente en un directorio trabajable y devuelve su ruta.
|
/// Materializa el árbol fuente en un directorio trabajable y devuelve su ruta.
|
||||||
pub fn fetch(recipe: &Recipe, work_root: &Path) -> hammer_core::Result<PathBuf> {
|
pub fn fetch(recipe: &Recipe, work_root: &Path) -> takana_core::Result<PathBuf> {
|
||||||
match recipe.source.kind()? {
|
match recipe.source.kind()? {
|
||||||
SourceKind::Git { repo, commit } => fetch_git(recipe, repo, commit, work_root),
|
SourceKind::Git { repo, commit } => fetch_git(recipe, repo, commit, work_root),
|
||||||
SourceKind::Tarball { url, sha256 } => fetch_tarball(recipe, url, sha256, work_root),
|
SourceKind::Tarball { url, sha256 } => fetch_tarball(recipe, url, sha256, work_root),
|
||||||
@@ -34,16 +34,16 @@ pub fn fetch(recipe: &Recipe, work_root: &Path) -> hammer_core::Result<PathBuf>
|
|||||||
///
|
///
|
||||||
/// El nombre del workspace lleva el hash del CONTENIDO y no el de la ruta: editar el overlay cambia
|
/// El nombre del workspace lleva el hash del CONTENIDO y no el de la ruta: editar el overlay cambia
|
||||||
/// de directorio, así que un árbol viejo nunca se reusa por accidente.
|
/// de directorio, así que un árbol viejo nunca se reusa por accidente.
|
||||||
fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> hammer_core::Result<PathBuf> {
|
fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> takana_core::Result<PathBuf> {
|
||||||
let origen = recipe.base_dir.join(dir);
|
let origen = recipe.base_dir.join(dir);
|
||||||
if !origen.is_dir() {
|
if !origen.is_dir() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"source.dir apunta a {} y no es un directorio",
|
"source.dir apunta a {} y no es un directorio",
|
||||||
origen.display()
|
origen.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let h = hammer_core::ArtifactHash::of_tree(&origen)
|
let h = takana_core::ArtifactHash::of_tree(&origen)
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("hash del árbol {}: {e}", origen.display())))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("hash del árbol {}: {e}", origen.display())))?;
|
||||||
let bare = h.as_str().strip_prefix("b3:").unwrap_or(h.as_str());
|
let bare = h.as_str().strip_prefix("b3:").unwrap_or(h.as_str());
|
||||||
let sources_dir = work_root.join("sources");
|
let sources_dir = work_root.join("sources");
|
||||||
std::fs::create_dir_all(&sources_dir)?;
|
std::fs::create_dir_all(&sources_dir)?;
|
||||||
@@ -57,9 +57,9 @@ fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> hammer_core::Resul
|
|||||||
.arg(format!("{}/.", origen.display()))
|
.arg(format!("{}/.", origen.display()))
|
||||||
.arg(&work_tree)
|
.arg(&work_tree)
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cp -a del source.dir: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn cp -a del source.dir: {e}")))?;
|
||||||
if !st.success() {
|
if !st.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"cp -a de {} falló",
|
"cp -a de {} falló",
|
||||||
origen.display()
|
origen.display()
|
||||||
)));
|
)));
|
||||||
@@ -72,7 +72,7 @@ fn fetch_git(
|
|||||||
repo: &str,
|
repo: &str,
|
||||||
commit: &str,
|
commit: &str,
|
||||||
work_root: &Path,
|
work_root: &Path,
|
||||||
) -> hammer_core::Result<PathBuf> {
|
) -> takana_core::Result<PathBuf> {
|
||||||
let repos_dir = work_root.join("repos");
|
let repos_dir = work_root.join("repos");
|
||||||
let sources_dir = work_root.join("sources");
|
let sources_dir = work_root.join("sources");
|
||||||
std::fs::create_dir_all(&repos_dir)?;
|
std::fs::create_dir_all(&repos_dir)?;
|
||||||
@@ -105,7 +105,7 @@ fn fetch_git(
|
|||||||
if run_git(&["cat-file", "-e", commit], Some(&mirror)).is_err() {
|
if run_git(&["cat-file", "-e", commit], Some(&mirror)).is_err() {
|
||||||
run_git(&["fetch", "--all", "--tags"], Some(&mirror))?;
|
run_git(&["fetch", "--all", "--tags"], Some(&mirror))?;
|
||||||
run_git(&["cat-file", "-e", commit], Some(&mirror)).map_err(|_| {
|
run_git(&["cat-file", "-e", commit], Some(&mirror)).map_err(|_| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"commit {commit} no existe en {repo} tras fetch"
|
"commit {commit} no existe en {repo} tras fetch"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -131,7 +131,7 @@ fn fetch_git(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `git archive <commit> | tar -x -C <destino>`: materializa UN árbol sin pagar un worktree.
|
/// `git archive <commit> | tar -x -C <destino>`: materializa UN árbol sin pagar un worktree.
|
||||||
fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Result<()> {
|
fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> takana_core::Result<()> {
|
||||||
let archive = Command::new("git")
|
let archive = Command::new("git")
|
||||||
.arg("-C")
|
.arg("-C")
|
||||||
.arg(mirror)
|
.arg(mirror)
|
||||||
@@ -139,7 +139,7 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
|
|||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git archive: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git archive: {e}")))?;
|
||||||
|
|
||||||
let tar_status = Command::new("tar")
|
let tar_status = Command::new("tar")
|
||||||
.args(["-x", "-C"])
|
.args(["-x", "-C"])
|
||||||
@@ -148,10 +148,10 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
|
|||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn tar -x: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn tar -x: {e}")))?;
|
||||||
|
|
||||||
if !tar_status.success() {
|
if !tar_status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"git archive | tar -x falló (tar exit {:?})",
|
"git archive | tar -x falló (tar exit {:?})",
|
||||||
tar_status.code()
|
tar_status.code()
|
||||||
)));
|
)));
|
||||||
@@ -184,13 +184,13 @@ fn materializar_submodulos(
|
|||||||
repos_dir: &Path,
|
repos_dir: &Path,
|
||||||
url_padre: &str,
|
url_padre: &str,
|
||||||
profundidad: usize,
|
profundidad: usize,
|
||||||
) -> hammer_core::Result<()> {
|
) -> takana_core::Result<()> {
|
||||||
let gitlinks = gitlinks_de(mirror, commit)?;
|
let gitlinks = gitlinks_de(mirror, commit)?;
|
||||||
if gitlinks.is_empty() {
|
if gitlinks.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if profundidad >= PROFUNDIDAD_SUBMODULOS {
|
if profundidad >= PROFUNDIDAD_SUBMODULOS {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"submódulos anidados más de {PROFUNDIDAD_SUBMODULOS} niveles en {url_padre} \
|
"submódulos anidados más de {PROFUNDIDAD_SUBMODULOS} niveles en {url_padre} \
|
||||||
({commit}): parece un ciclo"
|
({commit}): parece un ciclo"
|
||||||
)));
|
)));
|
||||||
@@ -200,7 +200,7 @@ fn materializar_submodulos(
|
|||||||
|
|
||||||
for (sha, ruta) in gitlinks {
|
for (sha, ruta) in gitlinks {
|
||||||
let declarada = urls.get(&ruta).ok_or_else(|| {
|
let declarada = urls.get(&ruta).ok_or_else(|| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"el commit {commit} declara el submódulo `{ruta}` pero .gitmodules no le da url"
|
"el commit {commit} declara el submódulo `{ruta}` pero .gitmodules no le da url"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -222,7 +222,7 @@ fn materializar_submodulos(
|
|||||||
if run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).is_err() {
|
if run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).is_err() {
|
||||||
run_git(&["fetch", "--all", "--tags"], Some(&sub_mirror))?;
|
run_git(&["fetch", "--all", "--tags"], Some(&sub_mirror))?;
|
||||||
run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).map_err(|_| {
|
run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).map_err(|_| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"el submódulo `{ruta}` pide {sha}, que no existe en {url} tras fetch"
|
"el submódulo `{ruta}` pide {sha}, que no existe en {url} tras fetch"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -246,7 +246,7 @@ fn materializar_submodulos(
|
|||||||
/// Las entradas de tipo `commit` (modo 160000) del commit: `(sha, ruta)`.
|
/// Las entradas de tipo `commit` (modo 160000) del commit: `(sha, ruta)`.
|
||||||
///
|
///
|
||||||
/// `-z` porque una ruta puede llevar espacios y el formato sin `-z` los escapa con comillas.
|
/// `-z` porque una ruta puede llevar espacios y el formato sin `-z` los escapa con comillas.
|
||||||
fn gitlinks_de(mirror: &Path, commit: &str) -> hammer_core::Result<Vec<(String, String)>> {
|
fn gitlinks_de(mirror: &Path, commit: &str) -> takana_core::Result<Vec<(String, String)>> {
|
||||||
let salida = salida_git(&["ls-tree", "-r", "-z", commit], mirror)?;
|
let salida = salida_git(&["ls-tree", "-r", "-z", commit], mirror)?;
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for reg in salida.split('\0').filter(|r| !r.is_empty()) {
|
for reg in salida.split('\0').filter(|r| !r.is_empty()) {
|
||||||
@@ -269,7 +269,7 @@ fn gitlinks_de(mirror: &Path, commit: &str) -> hammer_core::Result<Vec<(String,
|
|||||||
fn urls_de_gitmodules(
|
fn urls_de_gitmodules(
|
||||||
mirror: &Path,
|
mirror: &Path,
|
||||||
commit: &str,
|
commit: &str,
|
||||||
) -> hammer_core::Result<std::collections::BTreeMap<String, String>> {
|
) -> takana_core::Result<std::collections::BTreeMap<String, String>> {
|
||||||
let blob = format!("{commit}:.gitmodules");
|
let blob = format!("{commit}:.gitmodules");
|
||||||
let salida = match salida_git(
|
let salida = match salida_git(
|
||||||
&[
|
&[
|
||||||
@@ -365,7 +365,7 @@ fn sanear_para_ruta(url: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `git <args>` capturando stdout. Error si el comando falla.
|
/// `git <args>` capturando stdout. Error si el comando falla.
|
||||||
fn salida_git(args: &[&str], cwd: &Path) -> hammer_core::Result<String> {
|
fn salida_git(args: &[&str], cwd: &Path) -> takana_core::Result<String> {
|
||||||
let out = Command::new("git")
|
let out = Command::new("git")
|
||||||
.arg("-C")
|
.arg("-C")
|
||||||
.arg(cwd)
|
.arg(cwd)
|
||||||
@@ -373,9 +373,9 @@ fn salida_git(args: &[&str], cwd: &Path) -> hammer_core::Result<String> {
|
|||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"git {args:?} falló (exit {:?})",
|
"git {args:?} falló (exit {:?})",
|
||||||
out.status.code()
|
out.status.code()
|
||||||
)));
|
)));
|
||||||
@@ -544,7 +544,7 @@ fn fetch_tarball(
|
|||||||
url: &str,
|
url: &str,
|
||||||
sha256: &str,
|
sha256: &str,
|
||||||
work_root: &Path,
|
work_root: &Path,
|
||||||
) -> hammer_core::Result<PathBuf> {
|
) -> takana_core::Result<PathBuf> {
|
||||||
let tarballs_dir = work_root.join("tarballs");
|
let tarballs_dir = work_root.join("tarballs");
|
||||||
let sources_dir = work_root.join("sources");
|
let sources_dir = work_root.join("sources");
|
||||||
std::fs::create_dir_all(&tarballs_dir)?;
|
std::fs::create_dir_all(&tarballs_dir)?;
|
||||||
@@ -595,9 +595,9 @@ fn fetch_tarball(
|
|||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn tar -x tarball: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn tar -x tarball: {e}")))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"tar -x falló (exit {:?})",
|
"tar -x falló (exit {:?})",
|
||||||
status.code()
|
status.code()
|
||||||
)));
|
)));
|
||||||
@@ -628,7 +628,7 @@ fn fetch_tarball(
|
|||||||
/// `{base}/{sha256}.tar` en cada uno, en el orden que dicta [`bases_de_mirror`].
|
/// `{base}/{sha256}.tar` en cada uno, en el orden que dicta [`bases_de_mirror`].
|
||||||
/// `HAMMER_MIRROR_KEY`, si está, se pasa como `--key` (Storage Box por sftp). Sin `HAMMER_MIRROR`
|
/// `HAMMER_MIRROR_KEY`, si está, se pasa como `--key` (Storage Box por sftp). Sin `HAMMER_MIRROR`
|
||||||
/// el comportamiento es el de siempre: derecho a upstream.
|
/// el comportamiento es el de siempre: derecho a upstream.
|
||||||
fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<()> {
|
fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> takana_core::Result<()> {
|
||||||
for base in bases_de_mirror("HAMMER_MIRROR", sha256) {
|
for base in bases_de_mirror("HAMMER_MIRROR", sha256) {
|
||||||
let espejo = format!("{base}/{sha256}.tar");
|
let espejo = format!("{base}/{sha256}.tar");
|
||||||
// Un fallo acá NO es un error: es el caso normal la primera vez que se ve una fuente, y
|
// Un fallo acá NO es un error: es el caso normal la primera vez que se ve una fuente, y
|
||||||
@@ -645,7 +645,7 @@ fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<
|
|||||||
/// para que la próxima corrida lo vuelva a intentar (no envenenamos la caché).
|
/// para que la próxima corrida lo vuelva a intentar (no envenenamos la caché).
|
||||||
/// Baja `url` a `dst` con curl. Sin verificar nada: quien llama decide cómo se comprueba —
|
/// Baja `url` a `dst` con curl. Sin verificar nada: quien llama decide cómo se comprueba —
|
||||||
/// `download_verify` por sha256 (tarballs), `hidratar_desde_bundle` delegando en git (bundles).
|
/// `download_verify` por sha256 (tarballs), `hidratar_desde_bundle` delegando en git (bundles).
|
||||||
fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
fn descargar(url: &str, dst: &Path) -> takana_core::Result<()> {
|
||||||
let mut cmd = Command::new("curl");
|
let mut cmd = Command::new("curl");
|
||||||
cmd.args(["-fL", "--retry", "3", "--connect-timeout", "30"]);
|
cmd.args(["-fL", "--retry", "3", "--connect-timeout", "30"]);
|
||||||
// Clave para el mirror por sftp (Storage Box). Inocua para http/file.
|
// Clave para el mirror por sftp (Storage Box). Inocua para http/file.
|
||||||
@@ -661,9 +661,9 @@ fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"curl {url} falló (exit {:?})",
|
"curl {url} falló (exit {:?})",
|
||||||
status.code()
|
status.code()
|
||||||
)));
|
)));
|
||||||
@@ -671,7 +671,7 @@ fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_verify(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<()> {
|
fn download_verify(url: &str, sha256: &str, dst: &Path) -> takana_core::Result<()> {
|
||||||
let tmp = dst.with_extension("tar.partial");
|
let tmp = dst.with_extension("tar.partial");
|
||||||
if let Err(e) = descargar(url, &tmp) {
|
if let Err(e) = descargar(url, &tmp) {
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
@@ -685,14 +685,14 @@ fn download_verify(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
|
fn verify_sha256(path: &Path, expected: &str) -> takana_core::Result<()> {
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
let mut f = std::fs::File::open(path)?;
|
let mut f = std::fs::File::open(path)?;
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
std::io::copy(&mut f, &mut hasher)?;
|
std::io::copy(&mut f, &mut hasher)?;
|
||||||
let got = hex::encode(hasher.finalize());
|
let got = hex::encode(hasher.finalize());
|
||||||
if !got.eq_ignore_ascii_case(expected) {
|
if !got.eq_ignore_ascii_case(expected) {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
|
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
|
||||||
path.display()
|
path.display()
|
||||||
)));
|
)));
|
||||||
@@ -714,7 +714,7 @@ fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
|
|||||||
/// (plan C.2 #5). Ver `docs/runbooks/stage1-vm-boot.md` §8.
|
/// (plan C.2 #5). Ver `docs/runbooks/stage1-vm-boot.md` §8.
|
||||||
///
|
///
|
||||||
/// Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
|
/// Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
|
||||||
pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()> {
|
pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> takana_core::Result<()> {
|
||||||
let locked = src.join("Cargo.lock").is_file();
|
let locked = src.join("Cargo.lock").is_file();
|
||||||
let mut args: Vec<&str> = vec!["vendor"];
|
let mut args: Vec<&str> = vec!["vendor"];
|
||||||
if locked {
|
if locked {
|
||||||
@@ -735,9 +735,9 @@ pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()
|
|||||||
.args(&args)
|
.args(&args)
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"cargo vendor falló (exit {:?})",
|
"cargo vendor falló (exit {:?})",
|
||||||
output.status.code()
|
output.status.code()
|
||||||
)));
|
)));
|
||||||
@@ -762,7 +762,7 @@ pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()
|
|||||||
/// `GOFLAGS=-mod=mod` permite poblar el `vendor/` aunque el árbol no lo traiga.
|
/// `GOFLAGS=-mod=mod` permite poblar el `vendor/` aunque el árbol no lo traiga.
|
||||||
///
|
///
|
||||||
/// Un proyecto sin deps externas produce un `vendor/` vacío (o ninguno): inocuo.
|
/// Un proyecto sin deps externas produce un `vendor/` vacío (o ninguno): inocuo.
|
||||||
pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
|
pub fn vendor_go_deps(src: &Path) -> takana_core::Result<()> {
|
||||||
// Proyecto WORKSPACE (multimódulo): si el árbol trae `go.work`, `go mod vendor` con GOWORK=off
|
// Proyecto WORKSPACE (multimódulo): si el árbol trae `go.work`, `go mod vendor` con GOWORK=off
|
||||||
// vendorea SÓLO el módulo raíz → el build (que ve todo el workspace) se queja de "inconsistent
|
// vendorea SÓLO el módulo raíz → el build (que ve todo el workspace) se queja de "inconsistent
|
||||||
// vendoring". `go work vendor` (go 1.22+) produce un `vendor/` consistente para TODO el workspace.
|
// vendoring". `go work vendor` (go 1.22+) produce un `vendor/` consistente para TODO el workspace.
|
||||||
@@ -787,13 +787,13 @@ pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
|
|||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"spawn {label}: {e} (¿está `go` en el PATH del host? lo provee el lab, \
|
"spawn {label}: {e} (¿está `go` en el PATH del host? lo provee el lab, \
|
||||||
cf. scripts/bootstrap-devfs.sh / vps-setup.sh)"
|
cf. scripts/bootstrap-devfs.sh / vps-setup.sh)"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"{label} falló (exit {:?})",
|
"{label} falló (exit {:?})",
|
||||||
output.status.code()
|
output.status.code()
|
||||||
)));
|
)));
|
||||||
@@ -802,11 +802,11 @@ pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Aplica los patches de la receta (relativos a `recipe.base_dir`) sobre `tree`.
|
/// Aplica los patches de la receta (relativos a `recipe.base_dir`) sobre `tree`.
|
||||||
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
|
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> takana_core::Result<()> {
|
||||||
for p in &recipe.source.patches {
|
for p in &recipe.source.patches {
|
||||||
let patch_path = recipe.base_dir.join(p);
|
let patch_path = recipe.base_dir.join(p);
|
||||||
let bytes = std::fs::read(&patch_path).map_err(|e| {
|
let bytes = std::fs::read(&patch_path).map_err(|e| {
|
||||||
hammer_core::Error::Recipe(format!(
|
takana_core::Error::Recipe(format!(
|
||||||
"no pude leer patch {}: {e}",
|
"no pude leer patch {}: {e}",
|
||||||
patch_path.display()
|
patch_path.display()
|
||||||
))
|
))
|
||||||
@@ -818,18 +818,18 @@ pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
|
|||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn patch: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn patch: {e}")))?;
|
||||||
if let Some(mut stdin) = child.stdin.take() {
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
stdin
|
stdin
|
||||||
.write_all(&bytes)
|
.write_all(&bytes)
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("write patch: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("write patch: {e}")))?;
|
||||||
}
|
}
|
||||||
let status = child
|
let status = child
|
||||||
.wait()
|
.wait()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait patch: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait patch: {e}")))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"patch {} falló (exit {:?})",
|
"patch {} falló (exit {:?})",
|
||||||
patch_path.display(),
|
patch_path.display(),
|
||||||
status.code()
|
status.code()
|
||||||
@@ -1047,7 +1047,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
|
fn run_git(args: &[&str], cwd: Option<&Path>) -> takana_core::Result<()> {
|
||||||
let mut cmd = Command::new("git");
|
let mut cmd = Command::new("git");
|
||||||
if let Some(c) = cwd {
|
if let Some(c) = cwd {
|
||||||
cmd.arg("-C").arg(c);
|
cmd.arg("-C").arg(c);
|
||||||
@@ -1058,9 +1058,9 @@ fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
|
|||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.status()
|
.status()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"git {args:?} falló (exit {:?})",
|
"git {args:?} falló (exit {:?})",
|
||||||
status.code()
|
status.code()
|
||||||
)));
|
)));
|
||||||
@@ -70,19 +70,19 @@ impl Harkaq {
|
|||||||
deps: &[PathBuf],
|
deps: &[PathBuf],
|
||||||
contratos: &[String],
|
contratos: &[String],
|
||||||
dst: &Path,
|
dst: &Path,
|
||||||
) -> hammer_core::Result<()> {
|
) -> takana_core::Result<()> {
|
||||||
let mut ro: Vec<String> = Vec::new();
|
let mut ro: Vec<String> = Vec::new();
|
||||||
let mut dirs: std::collections::BTreeSet<String> = Default::default();
|
let mut dirs: std::collections::BTreeSet<String> = Default::default();
|
||||||
|
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
let real = std::fs::canonicalize(dep).map_err(|e| {
|
let real = std::fs::canonicalize(dep).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("canonicalize {}: {e}", dep.display()))
|
takana_core::Error::Other(anyhow::anyhow!("canonicalize {}: {e}", dep.display()))
|
||||||
})?;
|
})?;
|
||||||
recorrer(&real, &real, &mut ro, &mut dirs)?;
|
recorrer(&real, &real, &mut ro, &mut dirs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut f = std::fs::File::create(dst).map_err(|e| {
|
let mut f = std::fs::File::create(dst).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("crear política {}: {e}", dst.display()))
|
takana_core::Error::Other(anyhow::anyhow!("crear política {}: {e}", dst.display()))
|
||||||
})?;
|
})?;
|
||||||
writeln!(f, "# harkaq: clausura derivada de {} dep(s)", deps.len()).ok();
|
writeln!(f, "# harkaq: clausura derivada de {} dep(s)", deps.len()).ok();
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ impl Harkaq {
|
|||||||
|
|
||||||
if let Some(base) = &self.base {
|
if let Some(base) = &self.base {
|
||||||
let txt = std::fs::read_to_string(base).map_err(|e| {
|
let txt = std::fs::read_to_string(base).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("leer HARKAQ_BASE {}: {e}", base.display()))
|
takana_core::Error::Other(anyhow::anyhow!("leer HARKAQ_BASE {}: {e}", base.display()))
|
||||||
})?;
|
})?;
|
||||||
writeln!(f, "# runtime base ({})", base.display()).ok();
|
writeln!(f, "# runtime base ({})", base.display()).ok();
|
||||||
f.write_all(txt.as_bytes()).ok();
|
f.write_all(txt.as_bytes()).ok();
|
||||||
@@ -130,7 +130,7 @@ impl Harkaq {
|
|||||||
deps: &[PathBuf],
|
deps: &[PathBuf],
|
||||||
contratos: &[String],
|
contratos: &[String],
|
||||||
id: &str,
|
id: &str,
|
||||||
) -> hammer_core::Result<Corrida> {
|
) -> takana_core::Result<Corrida> {
|
||||||
let tmp = std::env::temp_dir().join(format!("harkaq-{id}-{}", std::process::id()));
|
let tmp = std::env::temp_dir().join(format!("harkaq-{id}-{}", std::process::id()));
|
||||||
std::fs::create_dir_all(&tmp).ok();
|
std::fs::create_dir_all(&tmp).ok();
|
||||||
let policy = tmp.join("policy");
|
let policy = tmp.join("policy");
|
||||||
@@ -146,7 +146,7 @@ impl Harkaq {
|
|||||||
|
|
||||||
let verdict = tmp.join("verdict.json");
|
let verdict = tmp.join("verdict.json");
|
||||||
let vf = std::fs::File::create(&verdict).map_err(|e| {
|
let vf = std::fs::File::create(&verdict).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("crear verdict: {e}"))
|
takana_core::Error::Other(anyhow::anyhow!("crear verdict: {e}"))
|
||||||
})?;
|
})?;
|
||||||
let reader = Command::new(self.bin.join("harkaq-audit"))
|
let reader = Command::new(self.bin.join("harkaq-audit"))
|
||||||
.arg("--canary")
|
.arg("--canary")
|
||||||
@@ -157,7 +157,7 @@ impl Harkaq {
|
|||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"spawn harkaq-audit ({}): {e} — ¿compilado y con `setcap cap_audit_read`?",
|
"spawn harkaq-audit ({}): {e} — ¿compilado y con `setcap cap_audit_read`?",
|
||||||
self.bin.display()
|
self.bin.display()
|
||||||
))
|
))
|
||||||
@@ -199,7 +199,7 @@ fn recorrer(
|
|||||||
dir: &Path,
|
dir: &Path,
|
||||||
ro: &mut Vec<String>,
|
ro: &mut Vec<String>,
|
||||||
dirs: &mut std::collections::BTreeSet<String>,
|
dirs: &mut std::collections::BTreeSet<String>,
|
||||||
) -> hammer_core::Result<()> {
|
) -> takana_core::Result<()> {
|
||||||
let rd = match std::fs::read_dir(dir) {
|
let rd = match std::fs::read_dir(dir) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return Ok(()),
|
Err(_) => return Ok(()),
|
||||||
@@ -15,7 +15,7 @@ use std::os::unix::fs::FileTypeExt;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_core::LinkMode;
|
use takana_core::LinkMode;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HydratedFile {
|
pub struct HydratedFile {
|
||||||
@@ -61,7 +61,7 @@ pub fn hydrate(
|
|||||||
target_fhs: &Path,
|
target_fhs: &Path,
|
||||||
mode: LinkMode,
|
mode: LinkMode,
|
||||||
dynamic: Option<&DynamicSpec>,
|
dynamic: Option<&DynamicSpec>,
|
||||||
) -> hammer_core::Result<HydrateReport> {
|
) -> takana_core::Result<HydrateReport> {
|
||||||
// `is_dir()` a secas acepta un directorio VACÍO: hydrate proyectaría 0 ficheros y devolvería
|
// `is_dir()` a secas acepta un directorio VACÍO: hydrate proyectaría 0 ficheros y devolvería
|
||||||
// un HydrateReport de éxito. Es el mismo agujero que ya se tapó en `Store::has` (2026-08-10) y
|
// un HydrateReport de éxito. Es el mismo agujero que ya se tapó en `Store::has` (2026-08-10) y
|
||||||
// en `Store::seal` (2026-08-29): un vacío no es un artefacto, es un nombre. Acá duele especial
|
// en `Store::seal` (2026-08-29): un vacío no es un artefacto, es un nombre. Acá duele especial
|
||||||
@@ -71,7 +71,7 @@ pub fn hydrate(
|
|||||||
.map(|mut e| e.next().is_none())
|
.map(|mut e| e.next().is_none())
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
if !artifact_dir.is_dir() || vacio {
|
if !artifact_dir.is_dir() || vacio {
|
||||||
return Err(hammer_core::Error::Store(format!(
|
return Err(takana_core::Error::Store(format!(
|
||||||
"artefacto inexistente o VACÍO: {}",
|
"artefacto inexistente o VACÍO: {}",
|
||||||
artifact_dir.display()
|
artifact_dir.display()
|
||||||
)));
|
)));
|
||||||
@@ -93,7 +93,7 @@ pub fn hydrate(
|
|||||||
// La raíz REAL contra la que se mide la contención (ver `no_escapa`). Se resuelve una sola vez:
|
// La raíz REAL contra la que se mide la contención (ver `no_escapa`). Se resuelve una sola vez:
|
||||||
// si el propio `target_fhs` es un symlink, eso es elección de quien hidrata y vale como raíz.
|
// si el propio `target_fhs` es un symlink, eso es elección de quien hidrata y vale como raíz.
|
||||||
let raiz = target_fhs.canonicalize().map_err(|e| {
|
let raiz = target_fhs.canonicalize().map_err(|e| {
|
||||||
hammer_core::Error::Store(format!("canonicalize {}: {e}", target_fhs.display()))
|
takana_core::Error::Store(format!("canonicalize {}: {e}", target_fhs.display()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut report = HydrateReport::default();
|
let mut report = HydrateReport::default();
|
||||||
@@ -121,12 +121,12 @@ pub fn hydrate(
|
|||||||
/// entre las dos. Para el fallo real —un symlink persistente puesto por otro artefacto— alcanza y
|
/// entre las dos. Para el fallo real —un symlink persistente puesto por otro artefacto— alcanza y
|
||||||
/// es determinista; la versión sin carrera es mover las escrituras a `openat2` + `linkat`/`renameat`
|
/// es determinista; la versión sin carrera es mover las escrituras a `openat2` + `linkat`/`renameat`
|
||||||
/// (SDD 25 §7-H3), y eso arrastra el camino de `patchelf`.
|
/// (SDD 25 §7-H3), y eso arrastra el camino de `patchelf`.
|
||||||
fn no_escapa(raiz: &Path, dst: &Path) -> hammer_core::Result<()> {
|
fn no_escapa(raiz: &Path, dst: &Path) -> takana_core::Result<()> {
|
||||||
let real = dst.canonicalize().map_err(|e| {
|
let real = dst.canonicalize().map_err(|e| {
|
||||||
hammer_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
|
takana_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
|
||||||
})?;
|
})?;
|
||||||
if !real.starts_with(raiz) {
|
if !real.starts_with(raiz) {
|
||||||
return Err(hammer_core::Error::Store(format!(
|
return Err(takana_core::Error::Store(format!(
|
||||||
"la hidratación se saldría del root: {} lleva a {}, fuera de {}.\n\
|
"la hidratación se saldría del root: {} lleva a {}, fuera de {}.\n\
|
||||||
Es un symlink de directorio ya presente en el FHS (probablemente puesto por otro \
|
Es un symlink de directorio ya presente en el FHS (probablemente puesto por otro \
|
||||||
artefacto). No se escribe a través de él: borralo o hidratá sobre un root limpio.",
|
artefacto). No se escribe a través de él: borralo o hidratá sobre un root limpio.",
|
||||||
@@ -145,7 +145,7 @@ fn walk_and_link(
|
|||||||
raiz: &Path,
|
raiz: &Path,
|
||||||
spec: Option<&DynamicSpec>,
|
spec: Option<&DynamicSpec>,
|
||||||
report: &mut HydrateReport,
|
report: &mut HydrateReport,
|
||||||
) -> hammer_core::Result<()> {
|
) -> takana_core::Result<()> {
|
||||||
for entry in std::fs::read_dir(cur)? {
|
for entry in std::fs::read_dir(cur)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let src = entry.path();
|
let src = entry.path();
|
||||||
@@ -183,7 +183,7 @@ fn walk_and_link(
|
|||||||
report.files.push(HydratedFile { src, dst });
|
report.files.push(HydratedFile { src, dst });
|
||||||
} else if ft.is_block_device() || ft.is_char_device() || ft.is_fifo() || ft.is_socket() {
|
} else if ft.is_block_device() || ft.is_char_device() || ft.is_fifo() || ft.is_socket() {
|
||||||
// Los artefactos del lab no deberían contener nodos especiales; lo señalamos.
|
// Los artefactos del lab no deberían contener nodos especiales; lo señalamos.
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"tipo no soportado en {}: {ft:?}",
|
"tipo no soportado en {}: {ft:?}",
|
||||||
src.display()
|
src.display()
|
||||||
)));
|
)));
|
||||||
@@ -195,7 +195,7 @@ fn walk_and_link(
|
|||||||
/// Crea un hardlink `dst → src` reemplazando atómicamente cualquier archivo previo en `dst`.
|
/// Crea un hardlink `dst → src` reemplazando atómicamente cualquier archivo previo en `dst`.
|
||||||
/// Implementación: `link(src, dst.tmp)` + `rename(dst.tmp, dst)`. La técnica garantiza que
|
/// Implementación: `link(src, dst.tmp)` + `rename(dst.tmp, dst)`. La técnica garantiza que
|
||||||
/// el archivo en `dst` nunca queda en estado intermedio y que el inode del store no se toca.
|
/// el archivo en `dst` nunca queda en estado intermedio y que el inode del store no se toca.
|
||||||
fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
|
fn atomic_hardlink(src: &Path, dst: &Path) -> takana_core::Result<()> {
|
||||||
if let Some(parent) = dst.parent() {
|
if let Some(parent) = dst.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
// Limpia un tmp olvidado de una corrida anterior.
|
// Limpia un tmp olvidado de una corrida anterior.
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
std::fs::hard_link(src, &tmp).map_err(|e| {
|
std::fs::hard_link(src, &tmp).map_err(|e| {
|
||||||
hammer_core::Error::Store(format!(
|
takana_core::Error::Store(format!(
|
||||||
"hardlink {} → {}: {e}",
|
"hardlink {} → {}: {e}",
|
||||||
src.display(),
|
src.display(),
|
||||||
tmp.display()
|
tmp.display()
|
||||||
@@ -212,7 +212,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
// rename sobreescribe atómicamente si dst es un archivo (no si es directorio).
|
// rename sobreescribe atómicamente si dst es un archivo (no si es directorio).
|
||||||
if let Err(e) = std::fs::rename(&tmp, dst) {
|
if let Err(e) = std::fs::rename(&tmp, dst) {
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
return Err(hammer_core::Error::Store(format!(
|
return Err(takana_core::Error::Store(format!(
|
||||||
"rename {} → {}: {e}",
|
"rename {} → {}: {e}",
|
||||||
tmp.display(),
|
tmp.display(),
|
||||||
dst.display()
|
dst.display()
|
||||||
@@ -223,7 +223,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
|
|||||||
|
|
||||||
/// `true` si el archivo empieza por el magic ELF (`\x7fELF`). Errores de I/O se propagan;
|
/// `true` si el archivo empieza por el magic ELF (`\x7fELF`). Errores de I/O se propagan;
|
||||||
/// un archivo más corto que 4 bytes simplemente no es ELF.
|
/// un archivo más corto que 4 bytes simplemente no es ELF.
|
||||||
fn is_elf(path: &Path) -> hammer_core::Result<bool> {
|
fn is_elf(path: &Path) -> takana_core::Result<bool> {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
let mut f = std::fs::File::open(path)?;
|
let mut f = std::fs::File::open(path)?;
|
||||||
let mut magic = [0u8; 4];
|
let mut magic = [0u8; 4];
|
||||||
@@ -235,14 +235,14 @@ fn is_elf(path: &Path) -> hammer_core::Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verifica que `patchelf` esté disponible antes de empezar a proyectar.
|
/// Verifica que `patchelf` esté disponible antes de empezar a proyectar.
|
||||||
fn ensure_patchelf() -> hammer_core::Result<()> {
|
fn ensure_patchelf() -> takana_core::Result<()> {
|
||||||
match Command::new("patchelf").arg("--version").output() {
|
match Command::new("patchelf").arg("--version").output() {
|
||||||
Ok(o) if o.status.success() => Ok(()),
|
Ok(o) if o.status.success() => Ok(()),
|
||||||
Ok(o) => Err(hammer_core::Error::Other(anyhow::anyhow!(
|
Ok(o) => Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"patchelf falló al invocarse: {}",
|
"patchelf falló al invocarse: {}",
|
||||||
String::from_utf8_lossy(&o.stderr).trim()
|
String::from_utf8_lossy(&o.stderr).trim()
|
||||||
))),
|
))),
|
||||||
Err(e) => Err(hammer_core::Error::Other(anyhow::anyhow!(
|
Err(e) => Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"hidratación dinámica requiere `patchelf` en el PATH y no se pudo ejecutar: {e}"
|
"hidratación dinámica requiere `patchelf` en el PATH y no se pudo ejecutar: {e}"
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
@@ -251,14 +251,14 @@ fn ensure_patchelf() -> hammer_core::Result<()> {
|
|||||||
/// Copia `src → dst` atómicamente (copy a tmp → patchelf el tmp → rename), preservando los
|
/// Copia `src → dst` atómicamente (copy a tmp → patchelf el tmp → rename), preservando los
|
||||||
/// permisos (`std::fs::copy` los replica, incluido el bit de ejecución). Patcheamos el tmp
|
/// permisos (`std::fs::copy` los replica, incluido el bit de ejecución). Patcheamos el tmp
|
||||||
/// **antes** del rename para que `dst` nunca quede en un estado a medio parchear.
|
/// **antes** del rename para que `dst` nunca quede en un estado a medio parchear.
|
||||||
fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> takana_core::Result<()> {
|
||||||
if let Some(parent) = dst.parent() {
|
if let Some(parent) = dst.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
let tmp = with_suffix(dst, ".hammer-tmp");
|
let tmp = with_suffix(dst, ".hammer-tmp");
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
std::fs::copy(src, &tmp).map_err(|e| {
|
std::fs::copy(src, &tmp).map_err(|e| {
|
||||||
hammer_core::Error::Store(format!("copy {} → {}: {e}", src.display(), tmp.display()))
|
takana_core::Error::Store(format!("copy {} → {}: {e}", src.display(), tmp.display()))
|
||||||
})?;
|
})?;
|
||||||
if let Err(e) = run_patchelf(&tmp, spec) {
|
if let Err(e) = run_patchelf(&tmp, spec) {
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
@@ -266,7 +266,7 @@ fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_c
|
|||||||
}
|
}
|
||||||
if let Err(e) = std::fs::rename(&tmp, dst) {
|
if let Err(e) = std::fs::rename(&tmp, dst) {
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
return Err(hammer_core::Error::Store(format!(
|
return Err(takana_core::Error::Store(format!(
|
||||||
"rename {} → {}: {e}",
|
"rename {} → {}: {e}",
|
||||||
tmp.display(),
|
tmp.display(),
|
||||||
dst.display()
|
dst.display()
|
||||||
@@ -277,7 +277,7 @@ fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_c
|
|||||||
|
|
||||||
/// Ejecuta `patchelf [--set-interpreter I] [--set-rpath R] <file>`. Asume que el caller ya
|
/// Ejecuta `patchelf [--set-interpreter I] [--set-rpath R] <file>`. Asume que el caller ya
|
||||||
/// comprobó (`has_work`) que hay al menos una de las dos.
|
/// comprobó (`has_work`) que hay al menos una de las dos.
|
||||||
fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
fn run_patchelf(file: &Path, spec: &DynamicSpec) -> takana_core::Result<()> {
|
||||||
let mut cmd = Command::new("patchelf");
|
let mut cmd = Command::new("patchelf");
|
||||||
if let Some(interp) = &spec.interpreter {
|
if let Some(interp) = &spec.interpreter {
|
||||||
cmd.arg("--set-interpreter").arg(interp);
|
cmd.arg("--set-interpreter").arg(interp);
|
||||||
@@ -287,13 +287,13 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
|||||||
}
|
}
|
||||||
cmd.arg(file);
|
cmd.arg(file);
|
||||||
let out = cmd.output().map_err(|e| {
|
let out = cmd.output().map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!(
|
takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"no pude ejecutar patchelf sobre {}: {e}",
|
"no pude ejecutar patchelf sobre {}: {e}",
|
||||||
file.display()
|
file.display()
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"patchelf falló sobre {}: {}",
|
"patchelf falló sobre {}: {}",
|
||||||
file.display(),
|
file.display(),
|
||||||
String::from_utf8_lossy(&out.stderr).trim()
|
String::from_utf8_lossy(&out.stderr).trim()
|
||||||
@@ -302,7 +302,7 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> hammer_core::Result<()> {
|
fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> takana_core::Result<()> {
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
if let Some(parent) = dst.parent() {
|
if let Some(parent) = dst.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
@@ -310,11 +310,11 @@ fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> hammer_core::Result
|
|||||||
let tmp = with_suffix(dst, ".hammer-tmp");
|
let tmp = with_suffix(dst, ".hammer-tmp");
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
symlink(link_target, &tmp).map_err(|e| {
|
symlink(link_target, &tmp).map_err(|e| {
|
||||||
hammer_core::Error::Store(format!("symlink {} → {}: {e}", link_target.display(), tmp.display()))
|
takana_core::Error::Store(format!("symlink {} → {}: {e}", link_target.display(), tmp.display()))
|
||||||
})?;
|
})?;
|
||||||
if let Err(e) = std::fs::rename(&tmp, dst) {
|
if let Err(e) = std::fs::rename(&tmp, dst) {
|
||||||
let _ = std::fs::remove_file(&tmp);
|
let _ = std::fs::remove_file(&tmp);
|
||||||
return Err(hammer_core::Error::Store(format!(
|
return Err(takana_core::Error::Store(format!(
|
||||||
"rename {} → {}: {e}",
|
"rename {} → {}: {e}",
|
||||||
tmp.display(),
|
tmp.display(),
|
||||||
dst.display()
|
dst.display()
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use hammer_core::{ArtifactHash, Compiler, LinkMode, Phases, Recipe, Store};
|
use takana_core::{ArtifactHash, Compiler, LinkMode, Phases, Recipe, Store};
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod download;
|
pub mod download;
|
||||||
@@ -33,8 +33,8 @@ pub use swm_bridge::build_source_patch;
|
|||||||
pub fn artifact_hash(
|
pub fn artifact_hash(
|
||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
_store: &Store,
|
_store: &Store,
|
||||||
lab: &hammer_core::lab::LabFingerprint,
|
lab: &takana_core::lab::LabFingerprint,
|
||||||
) -> hammer_core::Result<ArtifactHash> {
|
) -> takana_core::Result<ArtifactHash> {
|
||||||
let mut stack: Vec<String> = Vec::new();
|
let mut stack: Vec<String> = Vec::new();
|
||||||
// Memo por path canónico de receta: el grafo de build es un DAG-diamante (p.ej. TODO KF6/Qt
|
// Memo por path canónico de receta: el grafo de build es un DAG-diamante (p.ej. TODO KF6/Qt
|
||||||
// converge en qtbase/kcoreaddons, alcanzables por decenas de rutas). Sin memoizar, el walk
|
// converge en qtbase/kcoreaddons, alcanzables por decenas de rutas). Sin memoizar, el walk
|
||||||
@@ -50,11 +50,11 @@ fn artifact_hash_rec(
|
|||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
stack: &mut Vec<String>,
|
stack: &mut Vec<String>,
|
||||||
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
||||||
lab: &hammer_core::lab::LabFingerprint,
|
lab: &takana_core::lab::LabFingerprint,
|
||||||
) -> hammer_core::Result<ArtifactHash> {
|
) -> takana_core::Result<ArtifactHash> {
|
||||||
if stack.iter().any(|n| n == &recipe.name) {
|
if stack.iter().any(|n| n == &recipe.name) {
|
||||||
stack.push(recipe.name.clone());
|
stack.push(recipe.name.clone());
|
||||||
return Err(hammer_core::Error::Recipe(format!(
|
return Err(takana_core::Error::Recipe(format!(
|
||||||
"ciclo de dependencias de build: {}",
|
"ciclo de dependencias de build: {}",
|
||||||
stack.join(" -> ")
|
stack.join(" -> ")
|
||||||
)));
|
)));
|
||||||
@@ -92,13 +92,13 @@ fn resolve_build_dep_hashes(
|
|||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
stack: &mut Vec<String>,
|
stack: &mut Vec<String>,
|
||||||
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
||||||
lab: &hammer_core::lab::LabFingerprint,
|
lab: &takana_core::lab::LabFingerprint,
|
||||||
) -> hammer_core::Result<Vec<ArtifactHash>> {
|
) -> takana_core::Result<Vec<ArtifactHash>> {
|
||||||
if recipe.deps.build.is_empty() {
|
if recipe.deps.build.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
if recipe.base_dir.as_os_str().is_empty() {
|
if recipe.base_dir.as_os_str().is_empty() {
|
||||||
return Err(hammer_core::Error::Recipe(format!(
|
return Err(takana_core::Error::Recipe(format!(
|
||||||
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
|
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
|
||||||
cárgala con Recipe::load_from_path para resolver el catálogo",
|
cárgala con Recipe::load_from_path para resolver el catálogo",
|
||||||
recipe.name, recipe.deps.build
|
recipe.name, recipe.deps.build
|
||||||
@@ -116,7 +116,7 @@ fn resolve_build_dep_hashes(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
|
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
|
||||||
hammer_core::Error::Recipe(format!(
|
takana_core::Error::Recipe(format!(
|
||||||
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
|
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
|
||||||
recipe.name,
|
recipe.name,
|
||||||
dep_path.display()
|
dep_path.display()
|
||||||
@@ -138,12 +138,12 @@ fn materialize_build_deps(
|
|||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
cfg: &BuildConfig,
|
cfg: &BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> hammer_core::Result<Vec<PathBuf>> {
|
) -> takana_core::Result<Vec<PathBuf>> {
|
||||||
if recipe.deps.build.is_empty() {
|
if recipe.deps.build.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
if recipe.base_dir.as_os_str().is_empty() {
|
if recipe.base_dir.as_os_str().is_empty() {
|
||||||
return Err(hammer_core::Error::Recipe(format!(
|
return Err(takana_core::Error::Recipe(format!(
|
||||||
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
|
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
|
||||||
cárgala con Recipe::load_from_path para resolver el catálogo",
|
cárgala con Recipe::load_from_path para resolver el catálogo",
|
||||||
recipe.name, recipe.deps.build
|
recipe.name, recipe.deps.build
|
||||||
@@ -153,7 +153,7 @@ fn materialize_build_deps(
|
|||||||
for dep in &recipe.deps.build {
|
for dep in &recipe.deps.build {
|
||||||
let dep_path = resolve_dep_path(&recipe.base_dir, dep);
|
let dep_path = resolve_dep_path(&recipe.base_dir, dep);
|
||||||
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
|
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
|
||||||
hammer_core::Error::Recipe(format!(
|
takana_core::Error::Recipe(format!(
|
||||||
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
|
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
|
||||||
recipe.name,
|
recipe.name,
|
||||||
dep_path.display()
|
dep_path.display()
|
||||||
@@ -169,14 +169,14 @@ fn materialize_build_deps(
|
|||||||
/// la receta fije `build.zig_version`, en cuyo caso se usa el zig de esa versión, ubicado como
|
/// la receta fije `build.zig_version`, en cuyo caso se usa el zig de esa versión, ubicado como
|
||||||
/// **hermano** del por defecto (`<…>/zig-x86_64-linux-<version>`, el layout de `.dev-fs/tools/`).
|
/// **hermano** del por defecto (`<…>/zig-x86_64-linux-<version>`, el layout de `.dev-fs/tools/`).
|
||||||
/// Escotilla por-receta para esquivar la regresión de zig 0.14 sin tocar el toolchain global.
|
/// Escotilla por-receta para esquivar la regresión de zig 0.14 sin tocar el toolchain global.
|
||||||
fn effective_zig_dir(cfg: &BuildConfig, recipe: &Recipe) -> hammer_core::Result<PathBuf> {
|
fn effective_zig_dir(cfg: &BuildConfig, recipe: &Recipe) -> takana_core::Result<PathBuf> {
|
||||||
let Some(version) = &recipe.build.zig_version else {
|
let Some(version) = &recipe.build.zig_version else {
|
||||||
return Ok(cfg.zig_dir.clone());
|
return Ok(cfg.zig_dir.clone());
|
||||||
};
|
};
|
||||||
let parent = cfg.zig_dir.parent().unwrap_or_else(|| Path::new("."));
|
let parent = cfg.zig_dir.parent().unwrap_or_else(|| Path::new("."));
|
||||||
let dir = parent.join(format!("zig-x86_64-linux-{version}"));
|
let dir = parent.join(format!("zig-x86_64-linux-{version}"));
|
||||||
if !dir.join("zig").is_file() {
|
if !dir.join("zig").is_file() {
|
||||||
return Err(hammer_core::Error::Recipe(format!(
|
return Err(takana_core::Error::Recipe(format!(
|
||||||
"zig_version=\"{version}\": no encuentro el ejecutable zig en {} \
|
"zig_version=\"{version}\": no encuentro el ejecutable zig en {} \
|
||||||
(bajá ese zig a {}/)",
|
(bajá ese zig a {}/)",
|
||||||
dir.display(),
|
dir.display(),
|
||||||
@@ -192,13 +192,13 @@ pub fn build(
|
|||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
cfg: &BuildConfig,
|
cfg: &BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> hammer_core::Result<ArtifactHash> {
|
) -> takana_core::Result<ArtifactHash> {
|
||||||
// La huella sale del rootfs de ESTE cfg, que es el lab que de verdad va a compilar — no de
|
// La huella sale del rootfs de ESTE cfg, que es el lab que de verdad va a compilar — no de
|
||||||
// `docs/state/lab-toolchain.lock` ni de una ruta derivada del store. Derivarla del padre del
|
// `docs/state/lab-toolchain.lock` ni de una ruta derivada del store. Derivarla del padre del
|
||||||
// store ya mordió una vez (ver `BuildConfig::defaults_for_store_with_lab`): al anclar el store
|
// store ya mordió una vez (ver `BuildConfig::defaults_for_store_with_lab`): al anclar el store
|
||||||
// de un worker a un volumen, el lab se buscaba donde no estaba. `cfg.rootfs` respeta
|
// de un worker a un volumen, el lab se buscaba donde no estaba. `cfg.rootfs` respeta
|
||||||
// `HAMMER_LAB`/`HAMMER_ROOTFS` y es la única fuente que no puede desincronizarse del build.
|
// `HAMMER_LAB`/`HAMMER_ROOTFS` y es la única fuente que no puede desincronizarse del build.
|
||||||
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
||||||
let h = artifact_hash(recipe, store, &lab)?;
|
let h = artifact_hash(recipe, store, &lab)?;
|
||||||
if store.has(&h, &recipe.name) {
|
if store.has(&h, &recipe.name) {
|
||||||
tracing::info!(hash = %h, name = %recipe.name, "caché: artefacto ya en el store");
|
tracing::info!(hash = %h, name = %recipe.name, "caché: artefacto ya en el store");
|
||||||
@@ -342,7 +342,7 @@ pub fn build(
|
|||||||
tracing::info!(cmd, "phase: compile");
|
tracing::info!(cmd, "phase: compile");
|
||||||
sb.run(cmd)?;
|
sb.run(cmd)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(hammer_core::Error::Recipe(
|
return Err(takana_core::Error::Recipe(
|
||||||
"receta sin fase 'compile' y heurística no encontró ninguna".into(),
|
"receta sin fase 'compile' y heurística no encontró ninguna".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -350,7 +350,7 @@ pub fn build(
|
|||||||
tracing::info!(cmd, "phase: install");
|
tracing::info!(cmd, "phase: install");
|
||||||
sb.run(cmd)?;
|
sb.run(cmd)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(hammer_core::Error::Recipe(
|
return Err(takana_core::Error::Recipe(
|
||||||
"receta sin fase 'install' y heurística no encontró ninguna".into(),
|
"receta sin fase 'install' y heurística no encontró ninguna".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -404,7 +404,7 @@ pub fn build(
|
|||||||
// seal, para que quede congelada en el árbol read-only del store. `hammer export` la
|
// seal, para que quede congelada en el árbol read-only del store. `hammer export` la
|
||||||
// lee de vuelta para emitir `source_patch` con la receta original en vez de un
|
// lee de vuelta para emitir `source_patch` con la receta original en vez de un
|
||||||
// `file_drop` opaco. Ver `docs/04-overlay.md` §provenance (Fase 4).
|
// `file_drop` opaco. Ver `docs/04-overlay.md` §provenance (Fase 4).
|
||||||
let sidecar_path = out_dir.join(hammer_core::store::RECIPE_SIDECAR_REL);
|
let sidecar_path = out_dir.join(takana_core::store::RECIPE_SIDECAR_REL);
|
||||||
if let Some(parent) = sidecar_path.parent() {
|
if let Some(parent) = sidecar_path.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
@@ -419,9 +419,9 @@ pub fn build(
|
|||||||
/// El resultado de correr un check de evidencia, con su veredicto (H1b).
|
/// El resultado de correr un check de evidencia, con su veredicto (H1b).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EvidenceOutcome {
|
pub struct EvidenceOutcome {
|
||||||
pub kind: hammer_core::EvidenceKind,
|
pub kind: takana_core::EvidenceKind,
|
||||||
pub cmd: String,
|
pub cmd: String,
|
||||||
pub outcome: hammer_core::CheckOutcome,
|
pub outcome: takana_core::CheckOutcome,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reporte de la ejecución de TODA la evidencia de una receta (H1b). `all_passed` es el gate:
|
/// Reporte de la ejecución de TODA la evidencia de una receta (H1b). `all_passed` es el gate:
|
||||||
@@ -437,7 +437,7 @@ impl EvidenceReport {
|
|||||||
}
|
}
|
||||||
/// El estrato de confianza MÁS ALTO que pasó (para reportar "verificado hasta <nivel>"). `None`
|
/// El estrato de confianza MÁS ALTO que pasó (para reportar "verificado hasta <nivel>"). `None`
|
||||||
/// si no hay checks. No promete que ese nivel *baste* — sólo que lo declarado a ese nivel pasó.
|
/// si no hay checks. No promete que ese nivel *baste* — sólo que lo declarado a ese nivel pasó.
|
||||||
pub fn max_level_passed(&self) -> Option<hammer_core::EvidenceKind> {
|
pub fn max_level_passed(&self) -> Option<takana_core::EvidenceKind> {
|
||||||
self.results
|
self.results
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.outcome.passed)
|
.filter(|r| r.outcome.passed)
|
||||||
@@ -460,7 +460,7 @@ pub fn run_evidence(
|
|||||||
recipe: &Recipe,
|
recipe: &Recipe,
|
||||||
cfg: &BuildConfig,
|
cfg: &BuildConfig,
|
||||||
store: &Store,
|
store: &Store,
|
||||||
) -> hammer_core::Result<EvidenceReport> {
|
) -> takana_core::Result<EvidenceReport> {
|
||||||
recipe.evidence.validate()?;
|
recipe.evidence.validate()?;
|
||||||
if recipe.evidence.checks.is_empty() {
|
if recipe.evidence.checks.is_empty() {
|
||||||
return Ok(EvidenceReport { results: Vec::new() });
|
return Ok(EvidenceReport { results: Vec::new() });
|
||||||
@@ -615,7 +615,7 @@ fn inject_cargo_package_selector(recipe: &mut Recipe, src: &Path) {
|
|||||||
/// NO la considere parte del workspace del repo hammer (que la contiene en `work/sources/`). Append
|
/// NO la considere parte del workspace del repo hammer (que la contiene en `work/sources/`). Append
|
||||||
/// idempotente: si ya hay `[workspace]` (crate suelto que es su propio workspace, o una receta que
|
/// idempotente: si ya hay `[workspace]` (crate suelto que es su propio workspace, o una receta que
|
||||||
/// lo parchea), no toca nada. Caso virtual-manifest (root ya es `[workspace]`) tampoco se toca.
|
/// lo parchea), no toca nada. Caso virtual-manifest (root ya es `[workspace]`) tampoco se toca.
|
||||||
fn ensure_cargo_workspace_isolation(src: &Path) -> hammer_core::Result<()> {
|
fn ensure_cargo_workspace_isolation(src: &Path) -> takana_core::Result<()> {
|
||||||
let manifest = src.join("Cargo.toml");
|
let manifest = src.join("Cargo.toml");
|
||||||
let text = std::fs::read_to_string(&manifest)?;
|
let text = std::fs::read_to_string(&manifest)?;
|
||||||
// Heurística simple: ¿hay una tabla `[workspace]` (al inicio de línea)? Cubre `[workspace]` y
|
// Heurística simple: ¿hay una tabla `[workspace]` (al inicio de línea)? Cubre `[workspace]` y
|
||||||
@@ -804,7 +804,7 @@ fn cargo_linker_env_var(triple: &str) -> String {
|
|||||||
|
|
||||||
/// Devuelve los comandos a ejecutar para cada fase: overrides explícitos en la receta
|
/// Devuelve los comandos a ejecutar para cada fase: overrides explícitos en la receta
|
||||||
/// ganan; el resto lo deriva la heurística según el sistema de build detectado.
|
/// ganan; el resto lo deriva la heurística según el sistema de build detectado.
|
||||||
fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result<Phases> {
|
fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
|
||||||
let mut out = recipe.build.phases.clone();
|
let mut out = recipe.build.phases.clone();
|
||||||
// Raíz efectiva del módulo Go: `<src>/<subdir>` si la receta lo fija (dolt→go, csvtk→subdir),
|
// Raíz efectiva del módulo Go: `<src>/<subdir>` si la receta lo fija (dolt→go, csvtk→subdir),
|
||||||
// si no `src`. detect/detect_go_main operan ahí; el compile hace `cd <subdir>` primero.
|
// si no `src`. detect/detect_go_main operan ahí; el compile hace `cd <subdir>` primero.
|
||||||
@@ -1303,7 +1303,7 @@ commit = "deadbeef"
|
|||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
touch(d.path(), "Cargo.toml");
|
touch(d.path(), "Cargo.toml");
|
||||||
let mut r = recipe(&[]);
|
let mut r = recipe(&[]);
|
||||||
r.build.link = hammer_core::LinkMode::Dynamic;
|
r.build.link = takana_core::LinkMode::Dynamic;
|
||||||
let c = resolve_phases(&r, d.path()).unwrap().compile.unwrap();
|
let c = resolve_phases(&r, d.path()).unwrap().compile.unwrap();
|
||||||
assert!(c.contains("target-feature=-crt-static"), "{c}");
|
assert!(c.contains("target-feature=-crt-static"), "{c}");
|
||||||
}
|
}
|
||||||
@@ -1359,7 +1359,7 @@ fn unique_out_dir(
|
|||||||
work_root: &Path,
|
work_root: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
h: &ArtifactHash,
|
h: &ArtifactHash,
|
||||||
) -> hammer_core::Result<PathBuf> {
|
) -> takana_core::Result<PathBuf> {
|
||||||
let dir = work_root
|
let dir = work_root
|
||||||
.join("out")
|
.join("out")
|
||||||
.join(h.store_dir_name(name));
|
.join(h.store_dir_name(name));
|
||||||
@@ -1378,7 +1378,7 @@ pub fn hydrate(
|
|||||||
target_fhs: &Path,
|
target_fhs: &Path,
|
||||||
mode: LinkMode,
|
mode: LinkMode,
|
||||||
dynamic: Option<&hydrate::DynamicSpec>,
|
dynamic: Option<&hydrate::DynamicSpec>,
|
||||||
) -> hammer_core::Result<HydrateReport> {
|
) -> takana_core::Result<HydrateReport> {
|
||||||
let artifact_dir = store.find_by_hash(h.as_str())?;
|
let artifact_dir = store.find_by_hash(h.as_str())?;
|
||||||
hydrate::hydrate(&artifact_dir, target_fhs, mode, dynamic)
|
hydrate::hydrate(&artifact_dir, target_fhs, mode, dynamic)
|
||||||
}
|
}
|
||||||
@@ -1417,8 +1417,8 @@ mod dep_hash_tests {
|
|||||||
write_recipe(d.path(), "app", "a0", &["base"]);
|
write_recipe(d.path(), "app", "a0", &["base"]);
|
||||||
write_recipe(d.path(), "app_nodep", "a0", &[]); // misma fuente, sin la dep
|
write_recipe(d.path(), "app_nodep", "a0", &[]); // misma fuente, sin la dep
|
||||||
|
|
||||||
let with_dep = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let with_dep = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
let no_dep = artifact_hash(&load(d.path(), "app_nodep"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let no_dep = artifact_hash(&load(d.path(), "app_nodep"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
// `app_nodep` se llama distinto, pero el nombre no entra al hash: la única diferencia
|
// `app_nodep` se llama distinto, pero el nombre no entra al hash: la única diferencia
|
||||||
// de entrada es la presencia de la dep. (Renombramos sólo para tener dos .toml.)
|
// de entrada es la presencia de la dep. (Renombramos sólo para tener dos .toml.)
|
||||||
assert_ne!(with_dep, no_dep, "la dep de build debe entrar al hash");
|
assert_ne!(with_dep, no_dep, "la dep de build debe entrar al hash");
|
||||||
@@ -1431,11 +1431,11 @@ mod dep_hash_tests {
|
|||||||
write_recipe(d.path(), "base", "b0", &[]);
|
write_recipe(d.path(), "base", "b0", &[]);
|
||||||
write_recipe(d.path(), "lib", "l0", &["base"]);
|
write_recipe(d.path(), "lib", "l0", &["base"]);
|
||||||
write_recipe(d.path(), "app", "a0", &["lib"]);
|
write_recipe(d.path(), "app", "a0", &["lib"]);
|
||||||
let before = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let before = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
|
|
||||||
// Cambiar la fuente de la dep transitiva `base` debe re-hashear `app`.
|
// Cambiar la fuente de la dep transitiva `base` debe re-hashear `app`.
|
||||||
write_recipe(d.path(), "base", "b1", &[]);
|
write_recipe(d.path(), "base", "b1", &[]);
|
||||||
let after = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let after = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
assert_ne!(before, after, "un cambio transitivo debe propagarse");
|
assert_ne!(before, after, "un cambio transitivo debe propagarse");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1444,7 +1444,7 @@ mod dep_hash_tests {
|
|||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
let store = store_in(d.path());
|
let store = store_in(d.path());
|
||||||
write_recipe(d.path(), "app", "a0", &["pcre2"]); // sin pcre2.toml
|
write_recipe(d.path(), "app", "a0", &["pcre2"]); // sin pcre2.toml
|
||||||
let err = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
let err = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
||||||
assert!(err.contains("pcre2"), "el error debe nombrar la dep: {err}");
|
assert!(err.contains("pcre2"), "el error debe nombrar la dep: {err}");
|
||||||
assert!(err.contains("app"), "y la receta que la pide: {err}");
|
assert!(err.contains("app"), "y la receta que la pide: {err}");
|
||||||
}
|
}
|
||||||
@@ -1455,7 +1455,7 @@ mod dep_hash_tests {
|
|||||||
let store = store_in(d.path());
|
let store = store_in(d.path());
|
||||||
write_recipe(d.path(), "a", "a0", &["b"]);
|
write_recipe(d.path(), "a", "a0", &["b"]);
|
||||||
write_recipe(d.path(), "b", "b0", &["a"]);
|
write_recipe(d.path(), "b", "b0", &["a"]);
|
||||||
let err = artifact_hash(&load(d.path(), "a"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
let err = artifact_hash(&load(d.path(), "a"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
||||||
assert!(err.contains("ciclo"), "esperaba ciclo: {err}");
|
assert!(err.contains("ciclo"), "esperaba ciclo: {err}");
|
||||||
assert!(err.contains("a -> b -> a"), "debe mostrar la cadena: {err}");
|
assert!(err.contains("a -> b -> a"), "debe mostrar la cadena: {err}");
|
||||||
}
|
}
|
||||||
@@ -1468,7 +1468,7 @@ mod dep_hash_tests {
|
|||||||
"name=\"x\"\nversion=\"1\"\n[source]\nrepo=\"git://x\"\ncommit=\"c0\"\n[build]\n[deps]\nbuild=[\"y\"]\n",
|
"name=\"x\"\nversion=\"1\"\n[source]\nrepo=\"git://x\"\ncommit=\"c0\"\n[build]\n[deps]\nbuild=[\"y\"]\n",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let err = artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
let err = artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
|
||||||
assert!(err.contains("base_dir"), "debe explicar la falta de base_dir: {err}");
|
assert!(err.contains("base_dir"), "debe explicar la falta de base_dir: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1477,8 +1477,8 @@ mod dep_hash_tests {
|
|||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
let store = store_in(d.path());
|
let store = store_in(d.path());
|
||||||
write_recipe(d.path(), "solo", "s0", &[]);
|
write_recipe(d.path(), "solo", "s0", &[]);
|
||||||
let a = artifact_hash(&load(d.path(), "solo"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let a = artifact_hash(&load(d.path(), "solo"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
let b = artifact_hash(&load(d.path(), "solo"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
let b = artifact_hash(&load(d.path(), "solo"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||||
assert_eq!(a, b, "sin deps el hash es estable");
|
assert_eq!(a, b, "sin deps el hash es estable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
const TAIL_LINES: usize = 50;
|
const TAIL_LINES: usize = 50;
|
||||||
|
|
||||||
/// Fallo de una fase de build, con la cola del log del lab si la pudimos capturar. Viaja
|
/// Fallo de una fase de build, con la cola del log del lab si la pudimos capturar. Viaja
|
||||||
/// dentro de `hammer_core::Error::Other(anyhow)`; el bus la recupera por `downcast_ref` para
|
/// dentro de `takana_core::Error::Other(anyhow)`; el bus la recupera por `downcast_ref` para
|
||||||
/// poblar `Event::BuildFailed.log_tail`. Ver `docs/07-agent-bus.md`.
|
/// poblar `Event::BuildFailed.log_tail`. Ver `docs/07-agent-bus.md`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BuildFailure {
|
pub struct BuildFailure {
|
||||||
@@ -41,9 +41,9 @@ impl std::error::Error for BuildFailure {}
|
|||||||
impl BuildFailure {
|
impl BuildFailure {
|
||||||
/// Recupera un `BuildFailure` de un error de hammer-core, si lo lleva dentro. El bus lo
|
/// Recupera un `BuildFailure` de un error de hammer-core, si lo lleva dentro. El bus lo
|
||||||
/// usa para separar `reason` de `log_tail`.
|
/// usa para separar `reason` de `log_tail`.
|
||||||
pub fn from_error(e: &hammer_core::Error) -> Option<&BuildFailure> {
|
pub fn from_error(e: &takana_core::Error) -> Option<&BuildFailure> {
|
||||||
match e {
|
match e {
|
||||||
hammer_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
|
takana_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,8 +87,8 @@ impl Sandbox {
|
|||||||
/// Ejecuta `cmd` dentro del sandbox bajo `sh -c`. Hace *tee* de stdout/stderr al padre
|
/// Ejecuta `cmd` dentro del sandbox bajo `sh -c`. Hace *tee* de stdout/stderr al padre
|
||||||
/// (para no perder el streaming en vivo) y a la vez retiene las últimas [`TAIL_LINES`]
|
/// (para no perder el streaming en vivo) y a la vez retiene las últimas [`TAIL_LINES`]
|
||||||
/// líneas; si el exit code es != 0, devuelve un [`BuildFailure`] con esa cola en
|
/// líneas; si el exit code es != 0, devuelve un [`BuildFailure`] con esa cola en
|
||||||
/// `log_tail`, envuelto en `hammer_core::Error::Other`.
|
/// `log_tail`, envuelto en `takana_core::Error::Other`.
|
||||||
pub fn run(&self, cmd: &str) -> hammer_core::Result<()> {
|
pub fn run(&self, cmd: &str) -> takana_core::Result<()> {
|
||||||
// harkaq (SDD 16): sin HARKAQ=1 esto es `None` y todo lo de abajo es el camino de
|
// harkaq (SDD 16): sin HARKAQ=1 esto es `None` y todo lo de abajo es el camino de
|
||||||
// siempre — mismos args de bwrap, mismo entorno, ningún proceso extra. Inerte por
|
// siempre — mismos args de bwrap, mismo entorno, ningún proceso extra. Inerte por
|
||||||
// requisito: hay 700+ artefactos sellados cuyo hash no puede moverse por encender un
|
// requisito: hay 700+ artefactos sellados cuyo hash no puede moverse por encender un
|
||||||
@@ -109,7 +109,7 @@ impl Sandbox {
|
|||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
|
||||||
|
|
||||||
// Ring buffer compartido por ambos pumps: la cola combinada de stdout+stderr.
|
// Ring buffer compartido por ambos pumps: la cola combinada de stdout+stderr.
|
||||||
let tail = Arc::new(Mutex::new(VecDeque::<String>::with_capacity(TAIL_LINES)));
|
let tail = Arc::new(Mutex::new(VecDeque::<String>::with_capacity(TAIL_LINES)));
|
||||||
@@ -120,7 +120,7 @@ impl Sandbox {
|
|||||||
|
|
||||||
let status = child
|
let status = child
|
||||||
.wait()
|
.wait()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
|
||||||
// Aseguramos drenar ambos streams antes de leer la cola.
|
// Aseguramos drenar ambos streams antes de leer la cola.
|
||||||
let _ = h_out.join();
|
let _ = h_out.join();
|
||||||
let _ = h_err.join();
|
let _ = h_err.join();
|
||||||
@@ -148,7 +148,7 @@ impl Sandbox {
|
|||||||
"build phase falló (exit {}): {cmd}",
|
"build phase falló (exit {}): {cmd}",
|
||||||
status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into())
|
status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into())
|
||||||
);
|
);
|
||||||
return Err(hammer_core::Error::Other(anyhow::Error::new(BuildFailure {
|
return Err(takana_core::Error::Other(anyhow::Error::new(BuildFailure {
|
||||||
reason,
|
reason,
|
||||||
log_tail: tail_str,
|
log_tail: tail_str,
|
||||||
})));
|
})));
|
||||||
@@ -160,7 +160,7 @@ impl Sandbox {
|
|||||||
/// exit code en vez de fallar en != 0 — el caller decide contra lo esperado. El stderr se teea al
|
/// exit code en vez de fallar en != 0 — el caller decide contra lo esperado. El stderr se teea al
|
||||||
/// padre en vivo (logging), como en `run`. Es el motor de ejecución del checker de evidencia (H1b):
|
/// padre en vivo (logging), como en `run`. Es el motor de ejecución del checker de evidencia (H1b):
|
||||||
/// correr un `cmd` en el sandbox reproducible y comparar (exit, blake3(stdout)) contra lo declarado.
|
/// correr un `cmd` en el sandbox reproducible y comparar (exit, blake3(stdout)) contra lo declarado.
|
||||||
pub fn run_capture(&self, cmd: &str) -> hammer_core::Result<CmdOutcome> {
|
pub fn run_capture(&self, cmd: &str) -> takana_core::Result<CmdOutcome> {
|
||||||
let args = self.bwrap_args(cmd);
|
let args = self.bwrap_args(cmd);
|
||||||
tracing::debug!(?args, "bwrap (capture)");
|
tracing::debug!(?args, "bwrap (capture)");
|
||||||
let mut child = Command::new("bwrap")
|
let mut child = Command::new("bwrap")
|
||||||
@@ -169,7 +169,7 @@ impl Sandbox {
|
|||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
|
||||||
|
|
||||||
let mut out = child.stdout.take().expect("stdout piped");
|
let mut out = child.stdout.take().expect("stdout piped");
|
||||||
let err = child.stderr.take().expect("stderr piped");
|
let err = child.stderr.take().expect("stderr piped");
|
||||||
@@ -184,7 +184,7 @@ impl Sandbox {
|
|||||||
|
|
||||||
let status = child
|
let status = child
|
||||||
.wait()
|
.wait()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
|
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
|
||||||
let stdout = h_out.join().unwrap_or_default();
|
let stdout = h_out.join().unwrap_or_default();
|
||||||
let _ = h_err.join();
|
let _ = h_err.join();
|
||||||
|
|
||||||
@@ -526,17 +526,17 @@ fn spawn_pump<R: std::io::Read + Send + 'static>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errores tempranos: comprueba que las rutas base existen antes de invocar el build.
|
/// Errores tempranos: comprueba que las rutas base existen antes de invocar el build.
|
||||||
pub fn ensure_layout(rootfs: &Path, zig_dir: &Path) -> hammer_core::Result<()> {
|
pub fn ensure_layout(rootfs: &Path, zig_dir: &Path) -> takana_core::Result<()> {
|
||||||
// bin/sh en Alpine es un symlink absoluto a /bin/busybox, que desde el host no resuelve.
|
// bin/sh en Alpine es un symlink absoluto a /bin/busybox, que desde el host no resuelve.
|
||||||
// Comprobamos el archivo regular real para no engañarnos.
|
// Comprobamos el archivo regular real para no engañarnos.
|
||||||
if !rootfs.join("bin/busybox").is_file() {
|
if !rootfs.join("bin/busybox").is_file() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"rootfs inválido: no encuentro {}/bin/busybox — ¿corriste el bootstrap de Alpine?",
|
"rootfs inválido: no encuentro {}/bin/busybox — ¿corriste el bootstrap de Alpine?",
|
||||||
rootfs.display()
|
rootfs.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if !zig_dir.join("zig").exists() {
|
if !zig_dir.join("zig").exists() {
|
||||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||||
"zig no encontrado en {}/zig — ¿instalaste zig en .dev-fs/tools/zig?",
|
"zig no encontrado en {}/zig — ¿instalaste zig en .dev-fs/tools/zig?",
|
||||||
zig_dir.display()
|
zig_dir.display()
|
||||||
)));
|
)));
|
||||||
@@ -597,11 +597,11 @@ pub const RANLIB_WRAPPER: &str = "ranlib";
|
|||||||
/// (ejecutable estático / `.a`) el wrapper es TRANSPARENTE ⇒ byte-idéntico a `zig cc`, y el resto del
|
/// (ejecutable estático / `.a`) el wrapper es TRANSPARENTE ⇒ byte-idéntico a `zig cc`, y el resto del
|
||||||
/// árbol sigue estático. El ArtifactHash se deriva de inputs (receta+deps), no de los bytes ⇒ este
|
/// árbol sigue estático. El ArtifactHash se deriva de inputs (receta+deps), no de los bytes ⇒ este
|
||||||
/// cambio NO re-hashea nada sellado; sólo destraba builds nuevos con enlace dinámico irreducible.
|
/// cambio NO re-hashea nada sellado; sólo destraba builds nuevos con enlace dinámico irreducible.
|
||||||
fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
|
fn write_cc_wrappers(rootfs: &Path) -> takana_core::Result<()> {
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
let bindir = rootfs.join("usr/local/bin");
|
let bindir = rootfs.join("usr/local/bin");
|
||||||
std::fs::create_dir_all(&bindir).map_err(|e| {
|
std::fs::create_dir_all(&bindir).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("no pude crear {}: {e}", bindir.display()))
|
takana_core::Error::Other(anyhow::anyhow!("no pude crear {}: {e}", bindir.display()))
|
||||||
})?;
|
})?;
|
||||||
// `case " $* "` detecta como palabra los marcadores de "este link es dinámico": -shared,
|
// `case " $* "` detecta como palabra los marcadores de "este link es dinámico": -shared,
|
||||||
// -rdynamic, --export-dynamic, o un shared object en la línea (`….so ` final o `….so.N` versionado).
|
// -rdynamic, --export-dynamic, o un shared object en la línea (`….so ` final o `….so.N` versionado).
|
||||||
@@ -636,10 +636,10 @@ fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
|
|||||||
for (name, driver) in [(CC_WRAPPER, "zig cc"), (CXX_WRAPPER, "zig c++")] {
|
for (name, driver) in [(CC_WRAPPER, "zig cc"), (CXX_WRAPPER, "zig c++")] {
|
||||||
let path = bindir.join(name);
|
let path = bindir.join(name);
|
||||||
std::fs::write(&path, body(driver)).map_err(|e| {
|
std::fs::write(&path, body(driver)).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
|
takana_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
|
||||||
})?;
|
})?;
|
||||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
|
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
|
takana_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,10 +658,10 @@ fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
|
|||||||
"#!/bin/sh\n # Interpuesto por hammer: fuerza archivos deterministas (sin hora de pared en la\n # cabecera `ar`). Sin esto dos builds idénticos sellan bytes distintos. Ver\n # sandbox.rs::AR_WRAPPER.\n [ -x /usr/bin/{tool} ] || exit 127\n exec /usr/bin/{tool} -D \"$@\"\n"
|
"#!/bin/sh\n # Interpuesto por hammer: fuerza archivos deterministas (sin hora de pared en la\n # cabecera `ar`). Sin esto dos builds idénticos sellan bytes distintos. Ver\n # sandbox.rs::AR_WRAPPER.\n [ -x /usr/bin/{tool} ] || exit 127\n exec /usr/bin/{tool} -D \"$@\"\n"
|
||||||
);
|
);
|
||||||
std::fs::write(&path, body).map_err(|e| {
|
std::fs::write(&path, body).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
|
takana_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
|
||||||
})?;
|
})?;
|
||||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
|
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
|
||||||
hammer_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
|
takana_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -688,7 +688,7 @@ mod tests {
|
|||||||
reason: "exit 2".into(),
|
reason: "exit 2".into(),
|
||||||
log_tail: Some("undefined reference to `foo'".into()),
|
log_tail: Some("undefined reference to `foo'".into()),
|
||||||
};
|
};
|
||||||
let err = hammer_core::Error::Other(anyhow::Error::new(bf));
|
let err = takana_core::Error::Other(anyhow::Error::new(bf));
|
||||||
let recovered = BuildFailure::from_error(&err).expect("debe recuperarse");
|
let recovered = BuildFailure::from_error(&err).expect("debe recuperarse");
|
||||||
assert_eq!(recovered.reason, "exit 2");
|
assert_eq!(recovered.reason, "exit 2");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -699,7 +699,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_error_none_for_plain_errors() {
|
fn from_error_none_for_plain_errors() {
|
||||||
let err = hammer_core::Error::Recipe("nope".into());
|
let err = takana_core::Error::Recipe("nope".into());
|
||||||
assert!(BuildFailure::from_error(&err).is_none());
|
assert!(BuildFailure::from_error(&err).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Puente entre `hammer_core::Mutation::SourcePatch` y el laboratorio.
|
//! Puente entre `takana_core::Mutation::SourcePatch` y el laboratorio.
|
||||||
//!
|
//!
|
||||||
//! Convierte la mutación serializada en una `Recipe` efímera + un archivo de patch
|
//! Convierte la mutación serializada en una `Recipe` efímera + un archivo de patch
|
||||||
//! temporal, y delega en `build` para sellar el artefacto en el store. Luego se hidrata.
|
//! temporal, y delega en `build` para sellar el artefacto en el store. Luego se hidrata.
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use hammer_core::{
|
use takana_core::{
|
||||||
swm::{Mutation, SwmBuild},
|
swm::{Mutation, SwmBuild},
|
||||||
ArtifactHash, Compiler, LinkMode, Recipe, Store,
|
ArtifactHash, Compiler, LinkMode, Recipe, Store,
|
||||||
};
|
};
|
||||||
@@ -27,7 +27,7 @@ pub fn build_source_patch(
|
|||||||
store: &Store,
|
store: &Store,
|
||||||
scratch_root: Option<&Path>,
|
scratch_root: Option<&Path>,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
) -> hammer_core::Result<ArtifactHash> {
|
) -> takana_core::Result<ArtifactHash> {
|
||||||
let catalog_dir = catalog_dir_for(cfg, scratch_root);
|
let catalog_dir = catalog_dir_for(cfg, scratch_root);
|
||||||
// El NOMBRE de la receta sintetizada decide el sufijo del store (`<hash>-<name>`) Y la
|
// El NOMBRE de la receta sintetizada decide el sufijo del store (`<hash>-<name>`) Y la
|
||||||
// clave de cache-hit (`store.has(hash, name)`). Si quien llama conoce el nombre del paquete
|
// clave de cache-hit (`store.has(hash, name)`). Si quien llama conoce el nombre del paquete
|
||||||
@@ -47,7 +47,7 @@ pub fn build_source_patch(
|
|||||||
.strip_prefix("b3:")
|
.strip_prefix("b3:")
|
||||||
.expect("of_inputs siempre devuelve 'b3:'");
|
.expect("of_inputs siempre devuelve 'b3:'");
|
||||||
if want_hex != got_hex {
|
if want_hex != got_hex {
|
||||||
return Err(hammer_core::Error::Recipe(format!(
|
return Err(takana_core::Error::Recipe(format!(
|
||||||
"expected_hash no coincide: declarado=b3:{want_hex}, obtenido={hash}"
|
"expected_hash no coincide: declarado=b3:{want_hex}, obtenido={hash}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ pub fn recipe_from_source_patch(
|
|||||||
mutation: &Mutation,
|
mutation: &Mutation,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
catalog_dir: &Path,
|
catalog_dir: &Path,
|
||||||
) -> hammer_core::Result<Recipe> {
|
) -> takana_core::Result<Recipe> {
|
||||||
let (repo, commit, tarball, sha256, strip_components, patch, patch_url, build_cfg, target_bin, deps, evidence, slots) =
|
let (repo, commit, tarball, sha256, strip_components, patch, patch_url, build_cfg, target_bin, deps, evidence, slots) =
|
||||||
match mutation {
|
match mutation {
|
||||||
Mutation::SourcePatch {
|
Mutation::SourcePatch {
|
||||||
@@ -83,33 +83,33 @@ pub fn recipe_from_source_patch(
|
|||||||
target_bin, deps, evidence, slots,
|
target_bin, deps, evidence, slots,
|
||||||
),
|
),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(hammer_core::Error::Recipe(
|
return Err(takana_core::Error::Recipe(
|
||||||
"recipe_from_source_patch: la mutación no es 'source_patch'".into(),
|
"recipe_from_source_patch: la mutación no es 'source_patch'".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// git xor tarball — misma regla que el schema (`swm_source_kind`).
|
// git xor tarball — misma regla que el schema (`swm_source_kind`).
|
||||||
let source_kind = hammer_core::swm::swm_source_kind(
|
let source_kind = takana_core::swm::swm_source_kind(
|
||||||
repo.as_deref(),
|
repo.as_deref(),
|
||||||
commit.as_deref(),
|
commit.as_deref(),
|
||||||
tarball.as_deref(),
|
tarball.as_deref(),
|
||||||
sha256.as_deref(),
|
sha256.as_deref(),
|
||||||
)
|
)
|
||||||
.map_err(hammer_core::Error::Recipe)?;
|
.map_err(takana_core::Error::Recipe)?;
|
||||||
|
|
||||||
std::fs::create_dir_all(catalog_dir)?;
|
std::fs::create_dir_all(catalog_dir)?;
|
||||||
|
|
||||||
// Clave estable del origen para nombrar el patch a disco: el commit (git) o el sha256
|
// Clave estable del origen para nombrar el patch a disco: el commit (git) o el sha256
|
||||||
// (tarball). Idéntica reaplicación ⇒ mismo nombre ⇒ hash de receta determinista.
|
// (tarball). Idéntica reaplicación ⇒ mismo nombre ⇒ hash de receta determinista.
|
||||||
let source_key: &str = match &source_kind {
|
let source_key: &str = match &source_kind {
|
||||||
hammer_core::SourceKind::Git { commit, .. } => commit,
|
takana_core::SourceKind::Git { commit, .. } => commit,
|
||||||
hammer_core::SourceKind::Tarball { sha256, .. } => sha256,
|
takana_core::SourceKind::Tarball { sha256, .. } => sha256,
|
||||||
// Inalcanzable por construcción —`swm_source_kind` sólo produce Git o Tarball— pero se
|
// Inalcanzable por construcción —`swm_source_kind` sólo produce Git o Tarball— pero se
|
||||||
// escribe como error y no como `unreachable!`: esto es una librería, y un panic aquí
|
// escribe como error y no como `unreachable!`: esto es una librería, y un panic aquí
|
||||||
// mataría al llamador por una receta mal formada en vez de decírselo.
|
// mataría al llamador por una receta mal formada en vez de decírselo.
|
||||||
hammer_core::SourceKind::Dir { .. } => {
|
takana_core::SourceKind::Dir { .. } => {
|
||||||
return Err(hammer_core::Error::Recipe(
|
return Err(takana_core::Error::Recipe(
|
||||||
"un .swm no puede llevar un source por directorio".into(),
|
"un .swm no puede llevar un source por directorio".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -162,31 +162,31 @@ fn derive_name(target_bin: &str) -> String {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn synthesize_recipe(
|
fn synthesize_recipe(
|
||||||
name: &str,
|
name: &str,
|
||||||
source: &hammer_core::SourceKind<'_>,
|
source: &takana_core::SourceKind<'_>,
|
||||||
build_cfg: &SwmBuild,
|
build_cfg: &SwmBuild,
|
||||||
strip_components: Option<usize>,
|
strip_components: Option<usize>,
|
||||||
deps: &hammer_core::Deps,
|
deps: &takana_core::Deps,
|
||||||
evidence: &hammer_core::Evidence,
|
evidence: &takana_core::Evidence,
|
||||||
slots: &hammer_core::Slots,
|
slots: &takana_core::Slots,
|
||||||
patches: Vec<String>,
|
patches: Vec<String>,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> hammer_core::Result<Recipe> {
|
) -> takana_core::Result<Recipe> {
|
||||||
let compiler = parse_compiler(&build_cfg.compiler)?;
|
let compiler = parse_compiler(&build_cfg.compiler)?;
|
||||||
let link = parse_link(&build_cfg.link)?;
|
let link = parse_link(&build_cfg.link)?;
|
||||||
// Sección `[source]` y sufijo de versión según el modo de origen. El validador de
|
// 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.
|
// `Source::kind` ya garantiza git xor tarball; acá sólo emitimos el TOML correspondiente.
|
||||||
let (version_key, source_block) = match source {
|
let (version_key, source_block) = match source {
|
||||||
hammer_core::SourceKind::Git { repo, commit } => (
|
takana_core::SourceKind::Git { repo, commit } => (
|
||||||
*commit,
|
*commit,
|
||||||
format!("repo = \"{repo}\"\ncommit = \"{commit}\"\n"),
|
format!("repo = \"{repo}\"\ncommit = \"{commit}\"\n"),
|
||||||
),
|
),
|
||||||
hammer_core::SourceKind::Tarball { url, sha256 } => (
|
takana_core::SourceKind::Tarball { url, sha256 } => (
|
||||||
*sha256,
|
*sha256,
|
||||||
format!("tarball = \"{url}\"\nsha256 = \"{sha256}\"\n"),
|
format!("tarball = \"{url}\"\nsha256 = \"{sha256}\"\n"),
|
||||||
),
|
),
|
||||||
// Ver la nota del `source_key` de arriba: un `.swm` no puede traer un `dir`.
|
// Ver la nota del `source_key` de arriba: un `.swm` no puede traer un `dir`.
|
||||||
hammer_core::SourceKind::Dir { .. } => {
|
takana_core::SourceKind::Dir { .. } => {
|
||||||
return Err(hammer_core::Error::Recipe(
|
return Err(takana_core::Error::Recipe(
|
||||||
"un .swm no puede llevar un source por directorio".into(),
|
"un .swm no puede llevar un source por directorio".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -241,22 +241,22 @@ flags = []
|
|||||||
Ok(recipe)
|
Ok(recipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_compiler(s: &str) -> hammer_core::Result<Compiler> {
|
fn parse_compiler(s: &str) -> takana_core::Result<Compiler> {
|
||||||
match s {
|
match s {
|
||||||
"zig-cc" => Ok(Compiler::ZigCc),
|
"zig-cc" => Ok(Compiler::ZigCc),
|
||||||
"clang" => Ok(Compiler::Clang),
|
"clang" => Ok(Compiler::Clang),
|
||||||
"gcc" => Ok(Compiler::Gcc),
|
"gcc" => Ok(Compiler::Gcc),
|
||||||
other => Err(hammer_core::Error::Recipe(format!(
|
other => Err(takana_core::Error::Recipe(format!(
|
||||||
"compiler desconocido: '{other}' (esperado zig-cc/clang/gcc)"
|
"compiler desconocido: '{other}' (esperado zig-cc/clang/gcc)"
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_link(s: &str) -> hammer_core::Result<LinkMode> {
|
fn parse_link(s: &str) -> takana_core::Result<LinkMode> {
|
||||||
match s {
|
match s {
|
||||||
"static" => Ok(LinkMode::Static),
|
"static" => Ok(LinkMode::Static),
|
||||||
"dynamic" => Ok(LinkMode::Dynamic),
|
"dynamic" => Ok(LinkMode::Dynamic),
|
||||||
other => Err(hammer_core::Error::Recipe(format!(
|
other => Err(takana_core::Error::Recipe(format!(
|
||||||
"link desconocido: '{other}' (esperado static/dynamic)"
|
"link desconocido: '{other}' (esperado static/dynamic)"
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
@@ -284,9 +284,9 @@ logo = "b3:azul"
|
|||||||
[slots.requires]
|
[slots.requires]
|
||||||
"wayland-protocol" = "b3:stock"
|
"wayland-protocol" = "b3:stock"
|
||||||
"#;
|
"#;
|
||||||
let recipe = hammer_core::Recipe::from_toml(toml).unwrap();
|
let recipe = takana_core::Recipe::from_toml(toml).unwrap();
|
||||||
let swm =
|
let swm =
|
||||||
hammer_core::Swm::from_recipe(&recipe, "/usr/bin/tema", None, None, "2026-06-06").unwrap();
|
takana_core::Swm::from_recipe(&recipe, "/usr/bin/tema", None, None, "2026-06-06").unwrap();
|
||||||
// El .swm YAML debe llevar el bloque slots.
|
// El .swm YAML debe llevar el bloque slots.
|
||||||
let yaml = swm.to_yaml().unwrap();
|
let yaml = swm.to_yaml().unwrap();
|
||||||
assert!(yaml.contains("slots"), "el .swm debe transportar slots: {yaml}");
|
assert!(yaml.contains("slots"), "el .swm debe transportar slots: {yaml}");
|
||||||
@@ -327,15 +327,15 @@ logo = "b3:azul"
|
|||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
let r = synthesize_recipe(
|
let r = synthesize_recipe(
|
||||||
"grep",
|
"grep",
|
||||||
&hammer_core::SourceKind::Git {
|
&takana_core::SourceKind::Git {
|
||||||
repo: "git://example/grep.git",
|
repo: "git://example/grep.git",
|
||||||
commit: "a1b2c3d4e5f6a7b8c9",
|
commit: "a1b2c3d4e5f6a7b8c9",
|
||||||
},
|
},
|
||||||
&fake_swm_build(),
|
&fake_swm_build(),
|
||||||
None,
|
None,
|
||||||
&hammer_core::Deps::default(),
|
&takana_core::Deps::default(),
|
||||||
&hammer_core::Evidence::default(),
|
&takana_core::Evidence::default(),
|
||||||
&hammer_core::Slots::default(),
|
&takana_core::Slots::default(),
|
||||||
vec![],
|
vec![],
|
||||||
d.path(),
|
d.path(),
|
||||||
)
|
)
|
||||||
@@ -344,7 +344,7 @@ logo = "b3:azul"
|
|||||||
assert!(r.version.starts_with("swm-a1b2c3d4"));
|
assert!(r.version.starts_with("swm-a1b2c3d4"));
|
||||||
assert_eq!(r.build.flags, vec!["--enable-foo"]);
|
assert_eq!(r.build.flags, vec!["--enable-foo"]);
|
||||||
match r.source.kind().unwrap() {
|
match r.source.kind().unwrap() {
|
||||||
hammer_core::SourceKind::Git { repo, commit } => {
|
takana_core::SourceKind::Git { repo, commit } => {
|
||||||
assert_eq!(repo, "git://example/grep.git");
|
assert_eq!(repo, "git://example/grep.git");
|
||||||
assert_eq!(commit, "a1b2c3d4e5f6a7b8c9");
|
assert_eq!(commit, "a1b2c3d4e5f6a7b8c9");
|
||||||
}
|
}
|
||||||
@@ -357,15 +357,15 @@ logo = "b3:azul"
|
|||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
let r = synthesize_recipe(
|
let r = synthesize_recipe(
|
||||||
"grep",
|
"grep",
|
||||||
&hammer_core::SourceKind::Tarball {
|
&takana_core::SourceKind::Tarball {
|
||||||
url: "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz",
|
url: "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz",
|
||||||
sha256: "abcdef0123456789",
|
sha256: "abcdef0123456789",
|
||||||
},
|
},
|
||||||
&fake_swm_build(),
|
&fake_swm_build(),
|
||||||
None,
|
None,
|
||||||
&hammer_core::Deps::default(),
|
&takana_core::Deps::default(),
|
||||||
&hammer_core::Evidence::default(),
|
&takana_core::Evidence::default(),
|
||||||
&hammer_core::Slots::default(),
|
&takana_core::Slots::default(),
|
||||||
vec![],
|
vec![],
|
||||||
d.path(),
|
d.path(),
|
||||||
)
|
)
|
||||||
@@ -373,7 +373,7 @@ logo = "b3:azul"
|
|||||||
assert_eq!(r.name, "grep");
|
assert_eq!(r.name, "grep");
|
||||||
assert!(r.version.starts_with("swm-abcdef012345"));
|
assert!(r.version.starts_with("swm-abcdef012345"));
|
||||||
match r.source.kind().unwrap() {
|
match r.source.kind().unwrap() {
|
||||||
hammer_core::SourceKind::Tarball { url, sha256 } => {
|
takana_core::SourceKind::Tarball { url, sha256 } => {
|
||||||
assert_eq!(url, "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz");
|
assert_eq!(url, "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz");
|
||||||
assert_eq!(sha256, "abcdef0123456789");
|
assert_eq!(sha256, "abcdef0123456789");
|
||||||
}
|
}
|
||||||
+2
-2
@@ -9,8 +9,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_build::{build, BuildConfig};
|
use takana_build::{build, BuildConfig};
|
||||||
use hammer_core::{Recipe, Store};
|
use takana_core::{Recipe, Store};
|
||||||
|
|
||||||
const HELLO_C: &str = r#"
|
const HELLO_C: &str = r#"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
+2
-2
@@ -11,8 +11,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_build::{build, run_hydrate, BuildConfig};
|
use takana_build::{build, run_hydrate, BuildConfig};
|
||||||
use hammer_core::{LinkMode, Recipe, Store};
|
use takana_core::{LinkMode, Recipe, Store};
|
||||||
|
|
||||||
const RECIPE_TOML: &str = r#"
|
const RECIPE_TOML: &str = r#"
|
||||||
name = "grep"
|
name = "grep"
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
//! Integration test del pipeline completo de Fase 0:
|
//! Integration test del pipeline completo de Fase 0:
|
||||||
//! 1. Inicializa un repo git local con un `hello.c`.
|
//! 1. Inicializa un repo git local con un `hello.c`.
|
||||||
//! 2. Escribe una receta TOML que apunta a ese repo con phases override.
|
//! 2. Escribe una receta TOML que apunta a ese repo con phases override.
|
||||||
//! 3. Llama a `hammer_build::build` con un store/work temporales.
|
//! 3. Llama a `takana_build::build` con un store/work temporales.
|
||||||
//! 4. Verifica que el artefacto sellado contiene `usr/bin/hello`, es ELF estático y
|
//! 4. Verifica que el artefacto sellado contiene `usr/bin/hello`, es ELF estático y
|
||||||
//! se ejecuta dentro del sandbox imprimiendo el output esperado.
|
//! se ejecuta dentro del sandbox imprimiendo el output esperado.
|
||||||
//!
|
//!
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_build::{build, BuildConfig};
|
use takana_build::{build, BuildConfig};
|
||||||
use hammer_core::{Recipe, Store};
|
use takana_core::{Recipe, Store};
|
||||||
|
|
||||||
const HELLO_C: &str = r#"
|
const HELLO_C: &str = r#"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
+3
-3
@@ -4,7 +4,7 @@
|
|||||||
//! - Genero un tarball en un tempdir con contenido conocido.
|
//! - Genero un tarball en un tempdir con contenido conocido.
|
||||||
//! - Calculo su sha256 real con la misma crate (`sha2`) que usa el fetch.
|
//! - Calculo su sha256 real con la misma crate (`sha2`) que usa el fetch.
|
||||||
//! - Sirvo el tarball vía URL `file://` (curl la maneja nativamente).
|
//! - Sirvo el tarball vía URL `file://` (curl la maneja nativamente).
|
||||||
//! - Llamo a `hammer_build::fetch::fetch` con una receta TOML que apunta a esa URL.
|
//! - Llamo a `takana_build::fetch::fetch` con una receta TOML que apunta a esa URL.
|
||||||
//!
|
//!
|
||||||
//! Cubre: happy path con strip_components, mismatch de sha256 no envenena la caché, reuso
|
//! Cubre: happy path con strip_components, mismatch de sha256 no envenena la caché, reuso
|
||||||
//! de caché en segunda llamada, strip_components = 0 vs 1.
|
//! de caché en segunda llamada, strip_components = 0 vs 1.
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_build::fetch::fetch;
|
use takana_build::fetch::fetch;
|
||||||
use hammer_core::Recipe;
|
use takana_core::Recipe;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
/// Crea `<dir>/inner/{a.txt,sub/b.txt}` y empaqueta `inner/` en `<dir>/payload.tar.gz`.
|
/// Crea `<dir>/inner/{a.txt,sub/b.txt}` y empaqueta `inner/` en `<dir>/payload.tar.gz`.
|
||||||
+2
-2
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use hammer_build::hydrate::hydrate;
|
use takana_build::hydrate::hydrate;
|
||||||
use hammer_core::LinkMode;
|
use takana_core::LinkMode;
|
||||||
|
|
||||||
/// Artefacto que trae `usr/share/pkg` como symlink a `destino`.
|
/// Artefacto que trae `usr/share/pkg` como symlink a `destino`.
|
||||||
fn artefacto_con_symlink(dir: &Path, destino: &Path) {
|
fn artefacto_con_symlink(dir: &Path, destino: &Path) {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-cli"
|
name = "takana-cli"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -23,17 +23,17 @@ path = "src/main.rs"
|
|||||||
default = []
|
default = []
|
||||||
# Activa el traductor LLM real (Claude API) detrás del flag `hammer ai --llm`.
|
# Activa el traductor LLM real (Claude API) detrás del flag `hammer ai --llm`.
|
||||||
# Sin esta feature, `--llm` falla con un mensaje claro pidiendo recompilar.
|
# Sin esta feature, `--llm` falla con un mensaje claro pidiendo recompilar.
|
||||||
llm-claude = ["hammer-agent/llm-claude"]
|
llm-claude = ["takana-agent/llm-claude"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-build.workspace = true
|
takana-build.workspace = true
|
||||||
hammer-bootstrap.workspace = true
|
takana-bootstrap.workspace = true
|
||||||
hammer-mirror.workspace = true
|
takana-mirror.workspace = true
|
||||||
hammer-upgrade.workspace = true
|
takana-upgrade.workspace = true
|
||||||
hammer-overlay.workspace = true
|
takana-overlay.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
hammer-agent.workspace = true
|
takana-agent.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
@@ -334,7 +334,7 @@ package() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn extracts_source_patches_deps_phases() {
|
fn extracts_source_patches_deps_phases() {
|
||||||
let toml = recipe_from_apkbuild(APKBUILD_PATCHED).unwrap();
|
let toml = recipe_from_apkbuild(APKBUILD_PATCHED).unwrap();
|
||||||
let recipe = hammer_core::Recipe::from_toml(&toml).expect("recipe válida");
|
let recipe = takana_core::Recipe::from_toml(&toml).expect("recipe válida");
|
||||||
assert_eq!(recipe.name, "foo");
|
assert_eq!(recipe.name, "foo");
|
||||||
assert_eq!(recipe.version, "2.5");
|
assert_eq!(recipe.version, "2.5");
|
||||||
// tarball con $pkgver expandido:
|
// tarball con $pkgver expandido:
|
||||||
@@ -378,7 +378,7 @@ package() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn no_patches_is_empty() {
|
fn no_patches_is_empty() {
|
||||||
let apk = "pkgname=zlib\npkgver=1.3\nsource=\"https://zlib.net/zlib-$pkgver.tar.gz\"\nsha512sums=\"x z\"\n";
|
let apk = "pkgname=zlib\npkgver=1.3\nsource=\"https://zlib.net/zlib-$pkgver.tar.gz\"\nsha512sums=\"x z\"\n";
|
||||||
let recipe = hammer_core::Recipe::from_toml(&recipe_from_apkbuild(apk).unwrap()).unwrap();
|
let recipe = takana_core::Recipe::from_toml(&recipe_from_apkbuild(apk).unwrap()).unwrap();
|
||||||
assert_eq!(recipe.name, "zlib");
|
assert_eq!(recipe.name, "zlib");
|
||||||
assert!(recipe.source.patches.is_empty());
|
assert!(recipe.source.patches.is_empty());
|
||||||
assert_eq!(recipe.source.tarball.as_deref(), Some("https://zlib.net/zlib-1.3.tar.gz"));
|
assert_eq!(recipe.source.tarball.as_deref(), Some("https://zlib.net/zlib-1.3.tar.gz"));
|
||||||
@@ -8,8 +8,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use hammer_core::kernel::kconfig::KconfigTree;
|
use takana_core::kernel::kconfig::KconfigTree;
|
||||||
use hammer_core::kernel::KernelConfig;
|
use takana_core::kernel::KernelConfig;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
/// Dónde busca el árbol de Kconfig si no se pasa `--kconfig`.
|
/// Dónde busca el árbol de Kconfig si no se pasa `--kconfig`.
|
||||||
@@ -425,7 +425,7 @@ struct ClosureReport {
|
|||||||
/// De la clausura, los que un humano puede marcar en `menuconfig`.
|
/// De la clausura, los que un humano puede marcar en `menuconfig`.
|
||||||
visible: usize,
|
visible: usize,
|
||||||
/// `select` que entran desde fuera: reencienden algo de la clausura ignorando sus `depends on`.
|
/// `select` que entran desde fuera: reencienden algo de la clausura ignorando sus `depends on`.
|
||||||
leaks: Vec<hammer_core::kernel::SelectLeak>,
|
leaks: Vec<takana_core::kernel::SelectLeak>,
|
||||||
/// Símbolos distintos de la clausura alcanzados por esas fugas.
|
/// Símbolos distintos de la clausura alcanzados por esas fugas.
|
||||||
leaked_targets: usize,
|
leaked_targets: usize,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -447,7 +447,7 @@ struct FixpointReport {
|
|||||||
/// Lo que hubo que apagar de más, con la ronda en que apareció.
|
/// Lo que hubo que apagar de más, con la ronda en que apareció.
|
||||||
added: Vec<(usize, String)>,
|
added: Vec<(usize, String)>,
|
||||||
/// Fugas que sobrevivieron al tope de rondas. Vacío = cerró.
|
/// Fugas que sobrevivieron al tope de rondas. Vacío = cerró.
|
||||||
remaining_leaks: Vec<hammer_core::kernel::SelectLeak>,
|
remaining_leaks: Vec<takana_core::kernel::SelectLeak>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contraste contra un `.config` real: la validación §3 del SDD 22.
|
/// Contraste contra un `.config` real: la validación §3 del SDD 22.
|
||||||
@@ -520,7 +520,7 @@ fn closure(
|
|||||||
let on: Vec<String> = cl.iter().filter(|s| cfg.is_on(s)).cloned().collect();
|
let on: Vec<String> = cl.iter().filter(|s| cfg.is_on(s)).cloned().collect();
|
||||||
let off = cl
|
let off = cl
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|s| matches!(cfg.get(s), Some(hammer_core::kernel::ConfigValue::No)))
|
.filter(|s| matches!(cfg.get(s), Some(takana_core::kernel::ConfigValue::No)))
|
||||||
.count();
|
.count();
|
||||||
let absent = cl.iter().filter(|s| cfg.get(s).is_none()).count();
|
let absent = cl.iter().filter(|s| cfg.get(s).is_none()).count();
|
||||||
Some(AgainstReport {
|
Some(AgainstReport {
|
||||||
@@ -644,7 +644,7 @@ fn probe(
|
|||||||
with_hw: bool,
|
with_hw: bool,
|
||||||
json: bool,
|
json: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use hammer_core::kernel::reverse::{BundleState, HwSignal};
|
use takana_core::kernel::reverse::{BundleState, HwSignal};
|
||||||
|
|
||||||
let cfg_path = match config {
|
let cfg_path = match config {
|
||||||
Some(p) => p.to_path_buf(),
|
Some(p) => p.to_path_buf(),
|
||||||
@@ -655,9 +655,9 @@ fn probe(
|
|||||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", cfg_path.display());
|
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", cfg_path.display());
|
||||||
}
|
}
|
||||||
let tree = load_tree(kroot)?;
|
let tree = load_tree(kroot)?;
|
||||||
let cat = hammer_core::kernel::Catalog::load(catalog)?;
|
let cat = takana_core::kernel::Catalog::load(catalog)?;
|
||||||
let hw = with_hw.then(hammer_core::kernel::Hardware::probe_local);
|
let hw = with_hw.then(takana_core::kernel::Hardware::probe_local);
|
||||||
let rep = hammer_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
|
let rep = takana_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&rep)?);
|
println!("{}", serde_json::to_string_pretty(&rep)?);
|
||||||
@@ -793,7 +793,7 @@ struct BundleView {
|
|||||||
closure_len: usize,
|
closure_len: usize,
|
||||||
visible: usize,
|
visible: usize,
|
||||||
unknown_symbols: Vec<String>,
|
unknown_symbols: Vec<String>,
|
||||||
undeclared_leaks: Vec<hammer_core::kernel::SelectLeak>,
|
undeclared_leaks: Vec<takana_core::kernel::SelectLeak>,
|
||||||
accepted_leaks: usize,
|
accepted_leaks: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +811,7 @@ struct BundlesReport {
|
|||||||
struct KnobView {
|
struct KnobView {
|
||||||
id: String,
|
id: String,
|
||||||
title: String,
|
title: String,
|
||||||
side: hammer_core::kernel::Side,
|
side: takana_core::kernel::Side,
|
||||||
enable: Vec<String>,
|
enable: Vec<String>,
|
||||||
disable: Vec<String>,
|
disable: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -821,7 +821,7 @@ struct KnobView {
|
|||||||
|
|
||||||
fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()> {
|
fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()> {
|
||||||
let tree = load_tree(kroot)?;
|
let tree = load_tree(kroot)?;
|
||||||
let cat = hammer_core::kernel::Catalog::load(catalog)?;
|
let cat = takana_core::kernel::Catalog::load(catalog)?;
|
||||||
|
|
||||||
let mut views = Vec::new();
|
let mut views = Vec::new();
|
||||||
for b in &cat.bundles {
|
for b in &cat.bundles {
|
||||||
@@ -932,17 +932,17 @@ fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()>
|
|||||||
println!("NIVEL 2 — política y rendimiento (ojo al lado del que cae cada perilla)");
|
println!("NIVEL 2 — política y rendimiento (ojo al lado del que cae cada perilla)");
|
||||||
for k in &rep.knobs {
|
for k in &rep.knobs {
|
||||||
let lado = match k.side {
|
let lado = match k.side {
|
||||||
hammer_core::kernel::Side::Kconfig => "kconfig",
|
takana_core::kernel::Side::Kconfig => "kconfig",
|
||||||
hammer_core::kernel::Side::Recipe => "receta ",
|
takana_core::kernel::Side::Recipe => "receta ",
|
||||||
};
|
};
|
||||||
let que = match k.side {
|
let que = match k.side {
|
||||||
hammer_core::kernel::Side::Kconfig => {
|
takana_core::kernel::Side::Kconfig => {
|
||||||
let mut v: Vec<String> =
|
let mut v: Vec<String> =
|
||||||
k.enable.iter().map(|s| format!("+{s}")).collect();
|
k.enable.iter().map(|s| format!("+{s}")).collect();
|
||||||
v.extend(k.disable.iter().map(|s| format!("-{s}")));
|
v.extend(k.disable.iter().map(|s| format!("-{s}")));
|
||||||
v.join(" ")
|
v.join(" ")
|
||||||
}
|
}
|
||||||
hammer_core::kernel::Side::Recipe => {
|
takana_core::kernel::Side::Recipe => {
|
||||||
k.recipe_field.clone().unwrap_or_default()
|
k.recipe_field.clone().unwrap_or_default()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -988,9 +988,9 @@ fn plan(
|
|||||||
if bundle_ids.is_empty() && knob_ids.is_empty() {
|
if bundle_ids.is_empty() && knob_ids.is_empty() {
|
||||||
anyhow::bail!("un plan sin bundles ni perillas es la receta base: pasá --bundle/--knob");
|
anyhow::bail!("un plan sin bundles ni perillas es la receta base: pasá --bundle/--knob");
|
||||||
}
|
}
|
||||||
let base = hammer_core::Recipe::load_from_path(recipe_path)?;
|
let base = takana_core::Recipe::load_from_path(recipe_path)?;
|
||||||
let tree = load_tree(kroot)?;
|
let tree = load_tree(kroot)?;
|
||||||
let cat = hammer_core::kernel::Catalog::load(catalog)?;
|
let cat = takana_core::kernel::Catalog::load(catalog)?;
|
||||||
|
|
||||||
// Las recetas de kernel del repo NO son todas la misma versión (`linux`/`linux-metal` van por
|
// Las recetas de kernel del repo NO son todas la misma versión (`linux`/`linux-metal` van por
|
||||||
// 6.16.12; `linux-metal-dual`/`linux-generic` por 7.1.2). Planear una contra el árbol de la otra
|
// 6.16.12; `linux-metal-dual`/`linux-generic` por 7.1.2). Planear una contra el árbol de la otra
|
||||||
@@ -1015,7 +1015,7 @@ fn plan(
|
|||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut p = hammer_core::kernel::plan::make_plan(
|
let mut p = takana_core::kernel::plan::make_plan(
|
||||||
&base,
|
&base,
|
||||||
&recipe_path.display().to_string(),
|
&recipe_path.display().to_string(),
|
||||||
&cat,
|
&cat,
|
||||||
@@ -1027,14 +1027,14 @@ fn plan(
|
|||||||
// El plan DETERMINA el artefacto (SDD 22 §1), así que el hash va en el plan: la UI puede decir
|
// El plan DETERMINA el artefacto (SDD 22 §1), así que el hash va en el plan: la UI puede decir
|
||||||
// «esto ya está construido y firmado» sin construir nada. Si las deps no están resueltas, se
|
// «esto ya está construido y firmado» sin construir nada. Si las deps no están resueltas, se
|
||||||
// dice que no se pudo en vez de emitir un hash inventado.
|
// dice que no se pudo en vez de emitir un hash inventado.
|
||||||
let derived = hammer_core::kernel::plan::derive_recipe(&base, &p);
|
let derived = takana_core::kernel::plan::derive_recipe(&base, &p);
|
||||||
match hammer_core::Store::open(Path::new(store))
|
match takana_core::Store::open(Path::new(store))
|
||||||
.map_err(anyhow::Error::from)
|
.map_err(anyhow::Error::from)
|
||||||
.and_then(|s| {
|
.and_then(|s| {
|
||||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(s.root());
|
let cfg = takana_build::BuildConfig::from_env_or_defaults(s.root());
|
||||||
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
|
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
|
||||||
.map_err(anyhow::Error::from)?;
|
.map_err(anyhow::Error::from)?;
|
||||||
hammer_build::artifact_hash(&derived, &s, &lab).map_err(anyhow::Error::from)
|
takana_build::artifact_hash(&derived, &s, &lab).map_err(anyhow::Error::from)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Ok(h) => p.artifact_hash = Some(h.to_string()),
|
Ok(h) => p.artifact_hash = Some(h.to_string()),
|
||||||
@@ -1065,7 +1065,7 @@ fn plan(
|
|||||||
.map(|d| d.display().to_string())
|
.map(|d| d.display().to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let cuerpo = hammer_core::kernel::plan::render_recipe_toml(&derived)?;
|
let cuerpo = takana_core::kernel::plan::render_recipe_toml(&derived)?;
|
||||||
std::fs::write(ro, format!("{cabecera}{cuerpo}"))
|
std::fs::write(ro, format!("{cabecera}{cuerpo}"))
|
||||||
.with_context(|| format!("escribiendo {}", ro.display()))?;
|
.with_context(|| format!("escribiendo {}", ro.display()))?;
|
||||||
}
|
}
|
||||||
@@ -1091,8 +1091,8 @@ fn plan(
|
|||||||
println!("(se emiten RAÍCES: la clausura la calcula el olddefconfig del propio kernel)");
|
println!("(se emiten RAÍCES: la clausura la calcula el olddefconfig del propio kernel)");
|
||||||
for s in &p.symbols {
|
for s in &p.symbols {
|
||||||
let signo = match s.action {
|
let signo = match s.action {
|
||||||
hammer_core::kernel::plan::Action::Enable => "+",
|
takana_core::kernel::plan::Action::Enable => "+",
|
||||||
hammer_core::kernel::plan::Action::Disable => "-",
|
takana_core::kernel::plan::Action::Disable => "-",
|
||||||
};
|
};
|
||||||
println!(" {signo}{:<24} ← {} ({})", s.symbol, s.from, s.reason);
|
println!(" {signo}{:<24} ← {} ({})", s.symbol, s.from, s.reason);
|
||||||
}
|
}
|
||||||
@@ -1121,7 +1121,7 @@ fn plan(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
|
fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
|
||||||
let p: hammer_core::kernel::Plan = serde_json::from_str(
|
let p: takana_core::kernel::Plan = serde_json::from_str(
|
||||||
&std::fs::read_to_string(plan_path)
|
&std::fs::read_to_string(plan_path)
|
||||||
.with_context(|| format!("leyendo {}", plan_path.display()))?,
|
.with_context(|| format!("leyendo {}", plan_path.display()))?,
|
||||||
)
|
)
|
||||||
@@ -1130,7 +1130,7 @@ fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
|
|||||||
if cfg.is_empty() {
|
if cfg.is_empty() {
|
||||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", config.display());
|
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", config.display());
|
||||||
}
|
}
|
||||||
let d = hammer_core::kernel::diff_back(&p, &cfg);
|
let d = takana_core::kernel::diff_back(&p, &cfg);
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&d)?);
|
println!("{}", serde_json::to_string_pretty(&d)?);
|
||||||
@@ -1167,7 +1167,7 @@ fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
|
|||||||
// ---------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
fn hw(out: Option<&Path>) -> Result<()> {
|
fn hw(out: Option<&Path>) -> Result<()> {
|
||||||
let h = hammer_core::kernel::Hardware::probe_local();
|
let h = takana_core::kernel::Hardware::probe_local();
|
||||||
let texto = serde_json::to_string_pretty(&h)?;
|
let texto = serde_json::to_string_pretty(&h)?;
|
||||||
match out {
|
match out {
|
||||||
Some(o) => {
|
Some(o) => {
|
||||||
@@ -1201,7 +1201,7 @@ fn gate(
|
|||||||
if plan_path.is_none() && config.is_none() {
|
if plan_path.is_none() && config.is_none() {
|
||||||
anyhow::bail!("pasá --plan (qué apagaría) o --config (qué quedó): sin uno de los dos no hay nada que comparar");
|
anyhow::bail!("pasá --plan (qué apagaría) o --config (qué quedó): sin uno de los dos no hay nada que comparar");
|
||||||
}
|
}
|
||||||
let p: Option<hammer_core::kernel::Plan> = match plan_path {
|
let p: Option<takana_core::kernel::Plan> = match plan_path {
|
||||||
None => None,
|
None => None,
|
||||||
Some(pp) => Some(
|
Some(pp) => Some(
|
||||||
serde_json::from_str(
|
serde_json::from_str(
|
||||||
@@ -1210,9 +1210,9 @@ fn gate(
|
|||||||
.with_context(|| format!("{} no es un plan de `hammer kernel plan`", pp.display()))?,
|
.with_context(|| format!("{} no es un plan de `hammer kernel plan`", pp.display()))?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let cat = hammer_core::kernel::Catalog::load(catalog)?;
|
let cat = takana_core::kernel::Catalog::load(catalog)?;
|
||||||
let tree = load_tree(kroot)?;
|
let tree = load_tree(kroot)?;
|
||||||
let modmap = hammer_core::kernel::ModMap::parse(kroot)
|
let modmap = takana_core::kernel::ModMap::parse(kroot)
|
||||||
.with_context(|| format!("leyendo los Makefiles de {}", kroot.display()))?;
|
.with_context(|| format!("leyendo los Makefiles de {}", kroot.display()))?;
|
||||||
if modmap.is_empty() {
|
if modmap.is_empty() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
@@ -1223,11 +1223,11 @@ fn gate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hardware = match devices {
|
let hardware = match devices {
|
||||||
Some(d) => serde_json::from_str::<hammer_core::kernel::Hardware>(
|
Some(d) => serde_json::from_str::<takana_core::kernel::Hardware>(
|
||||||
&std::fs::read_to_string(d).with_context(|| format!("leyendo {}", d.display()))?,
|
&std::fs::read_to_string(d).with_context(|| format!("leyendo {}", d.display()))?,
|
||||||
)
|
)
|
||||||
.with_context(|| format!("{} no es un volcado de `hammer kernel hw`", d.display()))?,
|
.with_context(|| format!("{} no es un volcado de `hammer kernel hw`", d.display()))?,
|
||||||
None => hammer_core::kernel::Hardware::probe_local(),
|
None => takana_core::kernel::Hardware::probe_local(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let r = match config {
|
let r = match config {
|
||||||
@@ -1242,7 +1242,7 @@ fn gate(
|
|||||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", c.display());
|
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", c.display());
|
||||||
}
|
}
|
||||||
println!("referente {} ({} símbolos)", base_path.display(), base.len());
|
println!("referente {} ({} símbolos)", base_path.display(), base.len());
|
||||||
hammer_core::kernel::gate::run_against_config(
|
takana_core::kernel::gate::run_against_config(
|
||||||
&base,
|
&base,
|
||||||
&nuevo,
|
&nuevo,
|
||||||
&cat,
|
&cat,
|
||||||
@@ -1253,7 +1253,7 @@ fn gate(
|
|||||||
Some(&tree),
|
Some(&tree),
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
None => hammer_core::kernel::gate::run(
|
None => takana_core::kernel::gate::run(
|
||||||
p.as_ref().expect("validado arriba"),
|
p.as_ref().expect("validado arriba"),
|
||||||
&cat,
|
&cat,
|
||||||
&tree,
|
&tree,
|
||||||
@@ -1312,7 +1312,7 @@ fn gate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.verdict == hammer_core::kernel::Verdict::Bloquea {
|
if r.verdict == takana_core::kernel::Verdict::Bloquea {
|
||||||
anyhow::bail!("el gate bloquea");
|
anyhow::bail!("el gate bloquea");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1370,10 +1370,10 @@ fn deps_resuelven_desde(dir: &Path, deps: &[String]) -> bool {
|
|||||||
/// Primero por convención (`<dir>/<artifact>.toml`) y sólo si eso no da, barriendo el directorio:
|
/// Primero por convención (`<dir>/<artifact>.toml`) y sólo si eso no da, barriendo el directorio:
|
||||||
/// **el artefacto se nombra por el campo `name`, no por el fichero**, y en este corpus hay recetas
|
/// **el artefacto se nombra por el campo `name`, no por el fichero**, y en este corpus hay recetas
|
||||||
/// donde los dos difieren.
|
/// donde los dos difieren.
|
||||||
fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
|
fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<takana_core::Recipe> {
|
||||||
for d in dirs {
|
for d in dirs {
|
||||||
let directo = d.join(format!("{artifact}.toml"));
|
let directo = d.join(format!("{artifact}.toml"));
|
||||||
if let Ok(r) = hammer_core::Recipe::load_from_path(&directo) {
|
if let Ok(r) = takana_core::Recipe::load_from_path(&directo) {
|
||||||
if r.name == artifact {
|
if r.name == artifact {
|
||||||
return Some(con_catalogo(r, dirs));
|
return Some(con_catalogo(r, dirs));
|
||||||
}
|
}
|
||||||
@@ -1382,7 +1382,7 @@ fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
|
|||||||
for e in rd.flatten() {
|
for e in rd.flatten() {
|
||||||
let p = e.path();
|
let p = e.path();
|
||||||
if p.extension().is_some_and(|x| x == "toml") {
|
if p.extension().is_some_and(|x| x == "toml") {
|
||||||
if let Ok(r) = hammer_core::Recipe::load_from_path(&p) {
|
if let Ok(r) = takana_core::Recipe::load_from_path(&p) {
|
||||||
if r.name == artifact {
|
if r.name == artifact {
|
||||||
return Some(con_catalogo(r, dirs));
|
return Some(con_catalogo(r, dirs));
|
||||||
}
|
}
|
||||||
@@ -1404,7 +1404,7 @@ fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
|
|||||||
///
|
///
|
||||||
/// NO se toca si la receta trae `patches`: `base_dir` también los resuelve, y moverlo los rompería
|
/// NO se toca si la receta trae `patches`: `base_dir` también los resuelve, y moverlo los rompería
|
||||||
/// en silencio. Ahí es mejor no saber la vigencia (se comprueba todo) que hashear otra cosa.
|
/// en silencio. Ahí es mejor no saber la vigencia (se comprueba todo) que hashear otra cosa.
|
||||||
fn con_catalogo(mut r: hammer_core::Recipe, dirs: &[PathBuf]) -> hammer_core::Recipe {
|
fn con_catalogo(mut r: takana_core::Recipe, dirs: &[PathBuf]) -> takana_core::Recipe {
|
||||||
if r.deps.build.is_empty() || deps_resuelven_desde(&r.base_dir, &r.deps.build) {
|
if r.deps.build.is_empty() || deps_resuelven_desde(&r.base_dir, &r.deps.build) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -1434,13 +1434,13 @@ fn con_catalogo(mut r: hammer_core::Recipe, dirs: &[PathBuf]) -> hammer_core::Re
|
|||||||
/// lab de hoy — o sea que un kernel sellado con otro rootfs también sale superado, y es correcto
|
/// lab de hoy — o sea que un kernel sellado con otro rootfs también sale superado, y es correcto
|
||||||
/// (SDD 25: el lab entra en el artefacto).
|
/// (SDD 25: el lab entra en el artefacto).
|
||||||
fn dirs_vigentes(
|
fn dirs_vigentes(
|
||||||
c: &hammer_core::kernel::Contract,
|
c: &takana_core::kernel::Contract,
|
||||||
dirs: &[PathBuf],
|
dirs: &[PathBuf],
|
||||||
store: &str,
|
store: &str,
|
||||||
) -> Result<std::collections::BTreeMap<String, String>> {
|
) -> Result<std::collections::BTreeMap<String, String>> {
|
||||||
let st = hammer_core::Store::open(Path::new(store))?;
|
let st = takana_core::Store::open(Path::new(store))?;
|
||||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(st.root());
|
let cfg = takana_build::BuildConfig::from_env_or_defaults(st.root());
|
||||||
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
||||||
let mut out = std::collections::BTreeMap::new();
|
let mut out = std::collections::BTreeMap::new();
|
||||||
for t in &c.targets {
|
for t in &c.targets {
|
||||||
let Some(r) = receta_de(&t.artifact, dirs) else {
|
let Some(r) = receta_de(&t.artifact, dirs) else {
|
||||||
@@ -1451,7 +1451,7 @@ fn dirs_vigentes(
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
match hammer_build::artifact_hash(&r, &st, &lab) {
|
match takana_build::artifact_hash(&r, &st, &lab) {
|
||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
out.insert(t.artifact.clone(), h.store_dir_name(&r.name));
|
out.insert(t.artifact.clone(), h.store_dir_name(&r.name));
|
||||||
}
|
}
|
||||||
@@ -1464,10 +1464,10 @@ fn dirs_vigentes(
|
|||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ContractRun {
|
struct ContractRun {
|
||||||
contract: String,
|
contract: String,
|
||||||
reports: Vec<hammer_core::kernel::ContractReport>,
|
reports: Vec<takana_core::kernel::ContractReport>,
|
||||||
/// Sellados que la receta de HOY ya no produce. Se informan y no bloquean: ver `dirs_vigentes`.
|
/// Sellados que la receta de HOY ya no produce. Se informan y no bloquean: ver `dirs_vigentes`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
superados: Vec<hammer_core::kernel::ContractReport>,
|
superados: Vec<takana_core::kernel::ContractReport>,
|
||||||
/// Configs que NO se comprobaron por no tener perfil declarado. No cuentan como aprobados.
|
/// Configs que NO se comprobaron por no tener perfil declarado. No cuentan como aprobados.
|
||||||
sin_perfil: Vec<String>,
|
sin_perfil: Vec<String>,
|
||||||
/// Objetivos del contrato cuya receta de hoy NO tiene ni un sellado en el store. No se
|
/// Objetivos del contrato cuya receta de hoy NO tiene ni un sellado en el store. No se
|
||||||
@@ -1486,8 +1486,8 @@ fn contract_cmd(
|
|||||||
list: bool,
|
list: bool,
|
||||||
json: bool,
|
json: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use hammer_core::kernel::contract::sealed_kernel_configs;
|
use takana_core::kernel::contract::sealed_kernel_configs;
|
||||||
use hammer_core::kernel::Contract;
|
use takana_core::kernel::Contract;
|
||||||
|
|
||||||
let c = Contract::load(contract)?;
|
let c = Contract::load(contract)?;
|
||||||
|
|
||||||
@@ -1501,7 +1501,7 @@ fn contract_cmd(
|
|||||||
println!("revisado contra kernel {r}");
|
println!("revisado contra kernel {r}");
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
let req = hammer_core::kernel::contract::required_by(&c);
|
let req = takana_core::kernel::contract::required_by(&c);
|
||||||
for cap in &c.capabilities {
|
for cap in &c.capabilities {
|
||||||
let quien = req.get(cap.id.as_str()).map(|v| v.join(", ")).unwrap_or_default();
|
let quien = req.get(cap.id.as_str()).map(|v| v.join(", ")).unwrap_or_default();
|
||||||
let quien = if quien.is_empty() { "—".to_string() } else { quien };
|
let quien = if quien.is_empty() { "—".to_string() } else { quien };
|
||||||
@@ -1586,7 +1586,7 @@ fn contract_cmd(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (label, artifact, path) in &objetivos {
|
for (label, artifact, path) in &objetivos {
|
||||||
let cfg = hammer_core::kernel::KernelConfig::parse(&read_maybe_gz(path)?);
|
let cfg = takana_core::kernel::KernelConfig::parse(&read_maybe_gz(path)?);
|
||||||
if cfg.is_empty() {
|
if cfg.is_empty() {
|
||||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", path.display());
|
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", path.display());
|
||||||
}
|
}
|
||||||
@@ -1634,7 +1634,7 @@ fn contract_cmd(
|
|||||||
let bloquean = run
|
let bloquean = run
|
||||||
.reports
|
.reports
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||||
.count();
|
.count();
|
||||||
if bloquean > 0 {
|
if bloquean > 0 {
|
||||||
anyhow::bail!("el contrato NO se cumple en {bloquean} kernel(es)");
|
anyhow::bail!("el contrato NO se cumple en {bloquean} kernel(es)");
|
||||||
@@ -1655,7 +1655,7 @@ fn imprimir_contrato(run: &ContractRun) {
|
|||||||
println!("contrato {}", run.contract);
|
println!("contrato {}", run.contract);
|
||||||
println!();
|
println!();
|
||||||
for r in &run.reports {
|
for r in &run.reports {
|
||||||
let marca = if r.verdict == hammer_core::kernel::Verdict::Bloquea { "✗" } else { "✓" };
|
let marca = if r.verdict == takana_core::kernel::Verdict::Bloquea { "✗" } else { "✓" };
|
||||||
println!("{marca} {}", r.config);
|
println!("{marca} {}", r.config);
|
||||||
println!(" perfil {} ({} capacidad(es) miradas)", r.profile, r.checked);
|
println!(" perfil {} ({} capacidad(es) miradas)", r.profile, r.checked);
|
||||||
if let Some(k) = &r.kernel {
|
if let Some(k) = &r.kernel {
|
||||||
@@ -1699,13 +1699,13 @@ fn imprimir_contrato(run: &ContractRun) {
|
|||||||
let malos = run
|
let malos = run
|
||||||
.reports
|
.reports
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||||
.count();
|
.count();
|
||||||
if !run.superados.is_empty() {
|
if !run.superados.is_empty() {
|
||||||
let sup_malos = run
|
let sup_malos = run
|
||||||
.superados
|
.superados
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||||
.count();
|
.count();
|
||||||
println!(
|
println!(
|
||||||
"· {} sellado(s) SUPERADO(s) por la receta de hoy — no bloquean, {} con faltas:",
|
"· {} sellado(s) SUPERADO(s) por la receta de hoy — no bloquean, {} con faltas:",
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -417,7 +417,7 @@ mod tests {
|
|||||||
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
||||||
let toml = to_recipe_toml(&pkg).unwrap();
|
let toml = to_recipe_toml(&pkg).unwrap();
|
||||||
// Parsea como Recipe válida de hammer.
|
// Parsea como Recipe válida de hammer.
|
||||||
let recipe = hammer_core::Recipe::from_toml(&toml).expect("recipe válida");
|
let recipe = takana_core::Recipe::from_toml(&toml).expect("recipe válida");
|
||||||
assert_eq!(recipe.name, "hello");
|
assert_eq!(recipe.name, "hello");
|
||||||
assert_eq!(recipe.version, "2.12.1");
|
assert_eq!(recipe.version, "2.12.1");
|
||||||
// mirror://gnu/ se traduce a una URL concreta que el fetch de hammer entiende.
|
// mirror://gnu/ se traduce a una URL concreta que el fetch de hammer entiende.
|
||||||
@@ -438,7 +438,7 @@ mod tests {
|
|||||||
"is_rust": true, "main_program": "rg"
|
"is_rust": true, "main_program": "rg"
|
||||||
}"#;
|
}"#;
|
||||||
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
||||||
let recipe = hammer_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
|
let recipe = takana_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
|
||||||
assert_eq!(recipe.build.flags, vec!["--bin", "rg"]);
|
assert_eq!(recipe.build.flags, vec!["--bin", "rg"]);
|
||||||
let inst = recipe.build.phases.install.as_deref().unwrap();
|
let inst = recipe.build.phases.install.as_deref().unwrap();
|
||||||
assert!(inst.contains("cp target/release/rg /out/usr/bin/rg"), "{inst}");
|
assert!(inst.contains("cp target/release/rg /out/usr/bin/rg"), "{inst}");
|
||||||
@@ -456,7 +456,7 @@ mod tests {
|
|||||||
}"#;
|
}"#;
|
||||||
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
||||||
let toml = to_recipe_toml(&pkg).unwrap();
|
let toml = to_recipe_toml(&pkg).unwrap();
|
||||||
let recipe = hammer_core::Recipe::from_toml(&toml).unwrap();
|
let recipe = takana_core::Recipe::from_toml(&toml).unwrap();
|
||||||
assert!(recipe.deps.build.is_empty(), "Rust no emite deps activas: {:?}", recipe.deps.build);
|
assert!(recipe.deps.build.is_empty(), "Rust no emite deps activas: {:?}", recipe.deps.build);
|
||||||
assert!(toml.contains("# buildInputs de nix"), "deja provenance comentada: {toml}");
|
assert!(toml.contains("# buildInputs de nix"), "deja provenance comentada: {toml}");
|
||||||
assert!(toml.contains("pcre2"), "lista los nombres en el comentario: {toml}");
|
assert!(toml.contains("pcre2"), "lista los nombres en el comentario: {toml}");
|
||||||
@@ -471,7 +471,7 @@ mod tests {
|
|||||||
"native_build_inputs": ["install-shell-files", "version-check-hook"]
|
"native_build_inputs": ["install-shell-files", "version-check-hook"]
|
||||||
}"#;
|
}"#;
|
||||||
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
||||||
let recipe = hammer_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
|
let recipe = takana_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
|
||||||
// pcre2 sobrevive (paquete real); hooks/wrappers/shell-files y el toolchain rustc/cargo
|
// pcre2 sobrevive (paquete real); hooks/wrappers/shell-files y el toolchain rustc/cargo
|
||||||
// se filtran (rustc/cargo son el lab, no deps).
|
// se filtran (rustc/cargo son el lab, no deps).
|
||||||
assert_eq!(recipe.deps.build, vec!["pcre2"]);
|
assert_eq!(recipe.deps.build, vec!["pcre2"]);
|
||||||
@@ -503,7 +503,7 @@ mod tests {
|
|||||||
}"#;
|
}"#;
|
||||||
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
let pkg: NixPkg = serde_json::from_str(json).unwrap();
|
||||||
let toml = to_recipe_toml(&pkg).unwrap();
|
let toml = to_recipe_toml(&pkg).unwrap();
|
||||||
let recipe = hammer_core::Recipe::from_toml(&toml).unwrap();
|
let recipe = takana_core::Recipe::from_toml(&toml).unwrap();
|
||||||
assert_eq!(recipe.source.repo.as_deref(), Some("https://github.com/BurntSushi/ripgrep"));
|
assert_eq!(recipe.source.repo.as_deref(), Some("https://github.com/BurntSushi/ripgrep"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recipe.source.commit.as_deref(),
|
recipe.source.commit.as_deref(),
|
||||||
@@ -293,7 +293,7 @@ fn pull(
|
|||||||
download_to_file(url, &archive)?;
|
download_to_file(url, &archive)?;
|
||||||
|
|
||||||
// El corte. Si el contenido no es el que dice ser, se ABORTA — no se avisa y se sigue.
|
// El corte. Si el contenido no es el que dice ser, se ABORTA — no se avisa y se sigue.
|
||||||
hammer_build::download::verify_sha256(&archive, &sha).inspect_err(|_| {
|
takana_build::download::verify_sha256(&archive, &sha).inspect_err(|_| {
|
||||||
std::fs::remove_file(&archive).ok();
|
std::fs::remove_file(&archive).ok();
|
||||||
})?;
|
})?;
|
||||||
let archive_bytes = std::fs::metadata(&archive)?.len();
|
let archive_bytes = std::fs::metadata(&archive)?.len();
|
||||||
@@ -636,7 +636,7 @@ fn write_manifest(dir: &Path, m: &ImageManifest) -> Result<()> {
|
|||||||
// importada del store y la traída con `pull` serían dos imágenes distintas con los mismos bytes, y
|
// importada del store y la traída con `pull` serían dos imágenes distintas con los mismos bytes, y
|
||||||
// las instancias de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar.
|
// las instancias de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar.
|
||||||
fn import(root: &Path, store: &Path, hash: &str, copiar: bool) -> Result<()> {
|
fn import(root: &Path, store: &Path, hash: &str, copiar: bool) -> Result<()> {
|
||||||
let store = hammer_core::store::Store::open(store)
|
let store = takana_core::store::Store::open(store)
|
||||||
.map_err(|e| anyhow::anyhow!("abriendo el store: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("abriendo el store: {e}"))?;
|
||||||
let art = store.find_by_hash(hash)
|
let art = store.find_by_hash(hash)
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
use hammer_core::ArtifactHash;
|
use takana_core::ArtifactHash;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn install_aborta_ante_colision_de_fichero_y_force_slots_la_supera() {
|
fn install_aborta_ante_colision_de_fichero_y_force_slots_la_supera() {
|
||||||
@@ -10,9 +10,9 @@ use std::collections::BTreeMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
|
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
|
||||||
use hammer_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
|
use takana_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
|
||||||
use hammer_core::ArtifactHash;
|
use takana_core::ArtifactHash;
|
||||||
|
|
||||||
fn write(p: &Path, content: &str) {
|
fn write(p: &Path, content: &str) {
|
||||||
if let Some(parent) = p.parent() {
|
if let Some(parent) = p.parent() {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-core"
|
name = "takana-core"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -41,7 +41,7 @@ pub enum Command {
|
|||||||
/// Consulta de estado. `what` decide qué campos se usan:
|
/// Consulta de estado. `what` decide qué campos se usan:
|
||||||
/// - `"file"`: usa `path` (metadata del archivo).
|
/// - `"file"`: usa `path` (metadata del archivo).
|
||||||
/// - `"artifact"`: usa `name` o `path` (busca un hash en el store).
|
/// - `"artifact"`: usa `name` o `path` (busca un hash en el store).
|
||||||
/// - `"expr"`: usa `expr`, una expresión del mini-lenguaje (ver `hammer_core::query`).
|
/// - `"expr"`: usa `expr`, una expresión del mini-lenguaje (ver `takana_core::query`).
|
||||||
Query {
|
Query {
|
||||||
what: String,
|
what: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
@@ -162,9 +162,9 @@ pub struct Peer {
|
|||||||
pub pid: i32,
|
pub pid: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [H1c] Forma serializable del `EvidenceReport` de `hammer_build` que viaja en `BuildReady`.
|
/// [H1c] Forma serializable del `EvidenceReport` de `takana_build` que viaja en `BuildReady`.
|
||||||
/// El lado del build (hammerd) ejecuta la evidencia y la adjunta; el orquestador la lee sin
|
/// El lado del build (hammerd) ejecuta la evidencia y la adjunta; el orquestador la lee sin
|
||||||
/// depender de `hammer_build` (la separación agente-PROPONE / lab-CONSTRUYE se preserva).
|
/// depender de `takana_build` (la separación agente-PROPONE / lab-CONSTRUYE se preserva).
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct EvidenceVerdict {
|
pub struct EvidenceVerdict {
|
||||||
/// Gate: `false` ⇒ al menos un check declarado falló ⇒ el orquestador no propone la mutación.
|
/// Gate: `false` ⇒ al menos un check declarado falló ⇒ el orquestador no propone la mutación.
|
||||||
@@ -244,7 +244,7 @@ impl Swm {
|
|||||||
/// Construye un `.swm` de un único `source_patch` a partir de una [`crate::Recipe`] del
|
/// Construye un `.swm` de un único `source_patch` a partir de una [`crate::Recipe`] del
|
||||||
/// corpus — la dirección **forward** que cierra la paquetería (Etapa F): una receta que el
|
/// corpus — la dirección **forward** que cierra la paquetería (Etapa F): una receta que el
|
||||||
/// sistema YA sabe construir se vuelve un paquete distribuible y reproducible-desde-fuente.
|
/// sistema YA sabe construir se vuelve un paquete distribuible y reproducible-desde-fuente.
|
||||||
/// Es la inversa de `hammer_build::swm_bridge` (que va `source_patch` → `Recipe` → build).
|
/// Es la inversa de `takana_build::swm_bridge` (que va `source_patch` → `Recipe` → build).
|
||||||
///
|
///
|
||||||
/// `hammer-core` no toca disco: el caller resuelve y **lee** los patches de la receta
|
/// `hammer-core` no toca disco: el caller resuelve y **lee** los patches de la receta
|
||||||
/// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El
|
/// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-journal"
|
name = "takana-journal"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,7 +8,7 @@ repository.workspace = true
|
|||||||
description = "El diario de mutaciones de hammer: append-only JSON-líneas sobre eventos del FHS."
|
description = "El diario de mutaciones de hammer: append-only JSON-líneas sobre eventos del FHS."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Esta crate es **lógica pura**: nada de fanotify ni de mounts. El watcher real vive en
|
//! Esta crate es **lógica pura**: nada de fanotify ni de mounts. El watcher real vive en
|
||||||
//! `hammerd` y empuja eventos aquí vía `Journal::record`. El hook desde
|
//! `hammerd` y empuja eventos aquí vía `Journal::record`. El hook desde
|
||||||
//! `hammer_overlay::commit` también empuja por aquí.
|
//! `takana_overlay::commit` también empuja por aquí.
|
||||||
//!
|
//!
|
||||||
//! Diseño rationale (SDD 05):
|
//! Diseño rationale (SDD 05):
|
||||||
//! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables"
|
//! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-mirror"
|
name = "takana-mirror"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,7 +8,7 @@ repository.workspace = true
|
|||||||
description = "Mirror del store content-addressed de hammer (E3): replica artefactos por hash, verifica of_tree en recepción."
|
description = "Mirror del store content-addressed de hammer (E3): replica artefactos por hash, verifica of_tree en recepción."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use hammer_core::{ArtifactHash, Store};
|
use takana_core::{ArtifactHash, Store};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@@ -28,7 +28,7 @@ pub enum Error {
|
|||||||
#[error("io: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("core: {0}")]
|
#[error("core: {0}")]
|
||||||
Core(#[from] hammer_core::Error),
|
Core(#[from] takana_core::Error),
|
||||||
#[error(
|
#[error(
|
||||||
"integridad: el artefacto '{dir}' recibido hashea a {got} pero el índice anuncia {want} — \
|
"integridad: el artefacto '{dir}' recibido hashea a {got} pero el índice anuncia {want} — \
|
||||||
transferencia corrupta o manipulada; NO se selló"
|
transferencia corrupta o manipulada; NO se selló"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-overlay"
|
name = "takana-overlay"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,8 +8,8 @@ repository.workspace = true
|
|||||||
description = "El overlay de experimentación de hammer: try/commit/discard/status sobre overlayfs."
|
description = "El overlay de experimentación de hammer: try/commit/discard/status sobre overlayfs."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -248,7 +248,7 @@ pub fn commit(id: &OverlayId, state_root: &Path) -> Result<CommitReport> {
|
|||||||
pub fn commit_with_journal(
|
pub fn commit_with_journal(
|
||||||
id: &OverlayId,
|
id: &OverlayId,
|
||||||
state_root: &Path,
|
state_root: &Path,
|
||||||
journal: &hammer_journal::Journal,
|
journal: &takana_journal::Journal,
|
||||||
) -> Result<CommitReport> {
|
) -> Result<CommitReport> {
|
||||||
commit_inner(id, state_root, Some(journal))
|
commit_inner(id, state_root, Some(journal))
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ pub fn commit_with_journal(
|
|||||||
fn commit_inner(
|
fn commit_inner(
|
||||||
id: &OverlayId,
|
id: &OverlayId,
|
||||||
state_root: &Path,
|
state_root: &Path,
|
||||||
journal: Option<&hammer_journal::Journal>,
|
journal: Option<&takana_journal::Journal>,
|
||||||
) -> Result<CommitReport> {
|
) -> Result<CommitReport> {
|
||||||
let base = state_root.join(id.as_str());
|
let base = state_root.join(id.as_str());
|
||||||
let state_file = base.join("state.json");
|
let state_file = base.join("state.json");
|
||||||
@@ -295,11 +295,11 @@ fn commit_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn record_commit_to_journal(
|
fn record_commit_to_journal(
|
||||||
j: &hammer_journal::Journal,
|
j: &takana_journal::Journal,
|
||||||
id: &OverlayId,
|
id: &OverlayId,
|
||||||
report: &CommitReport,
|
report: &CommitReport,
|
||||||
) {
|
) {
|
||||||
use hammer_journal::{Actor, MutationEvent, MutationOp, Source};
|
use takana_journal::{Actor, MutationEvent, MutationOp, Source};
|
||||||
let actor = Actor {
|
let actor = Actor {
|
||||||
source: Source::HammerCommit {
|
source: Source::HammerCommit {
|
||||||
overlay: id.as_str().to_string(),
|
overlay: id.as_str().to_string(),
|
||||||
@@ -308,7 +308,7 @@ fn record_commit_to_journal(
|
|||||||
pid: Some(std::process::id()),
|
pid: Some(std::process::id()),
|
||||||
uid: Some(unsafe { libc::getuid() }),
|
uid: Some(unsafe { libc::getuid() }),
|
||||||
};
|
};
|
||||||
let ts = hammer_journal::now_rfc3339();
|
let ts = takana_journal::now_rfc3339();
|
||||||
for p in &report.copied {
|
for p in &report.copied {
|
||||||
let ev = MutationEvent {
|
let ev = MutationEvent {
|
||||||
ts: ts.clone(),
|
ts: ts.clone(),
|
||||||
@@ -665,7 +665,7 @@ mod tests {
|
|||||||
// No tocamos mount real: ejercitamos directamente la función privada que el commit
|
// No tocamos mount real: ejercitamos directamente la función privada que el commit
|
||||||
// delega para anotar al diario. Eso valida el contrato sin necesidad de overlayfs.
|
// delega para anotar al diario. Eso valida el contrato sin necesidad de overlayfs.
|
||||||
let d = tempfile::tempdir().unwrap();
|
let d = tempfile::tempdir().unwrap();
|
||||||
let journal = hammer_journal::Journal::open(d.path().join("journal")).unwrap();
|
let journal = takana_journal::Journal::open(d.path().join("journal")).unwrap();
|
||||||
let report = CommitReport {
|
let report = CommitReport {
|
||||||
copied: vec![PathBuf::from("/etc/hosts"), PathBuf::from("/bin/grep")],
|
copied: vec![PathBuf::from("/etc/hosts"), PathBuf::from("/bin/grep")],
|
||||||
removed: vec![PathBuf::from("/bin/obsoleto")],
|
removed: vec![PathBuf::from("/bin/obsoleto")],
|
||||||
@@ -678,13 +678,13 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|e| (e.op, e.path.display().to_string()))
|
.map(|e| (e.op, e.path.display().to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/etc/hosts".into())));
|
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/etc/hosts".into())));
|
||||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/bin/grep".into())));
|
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/bin/grep".into())));
|
||||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Delete, "/bin/obsoleto".into())));
|
assert!(ops_paths.contains(&(takana_journal::MutationOp::Delete, "/bin/obsoleto".into())));
|
||||||
// Todos comparten el mismo source con overlay id.
|
// Todos comparten el mismo source con overlay id.
|
||||||
for e in &events {
|
for e in &events {
|
||||||
match &e.by.source {
|
match &e.by.source {
|
||||||
hammer_journal::Source::HammerCommit { overlay, .. } => {
|
takana_journal::Source::HammerCommit { overlay, .. } => {
|
||||||
assert_eq!(overlay, "1234-1");
|
assert_eq!(overlay, "1234-1");
|
||||||
}
|
}
|
||||||
other => panic!("source inesperado: {other:?}"),
|
other => panic!("source inesperado: {other:?}"),
|
||||||
+2
-2
@@ -16,7 +16,7 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use hammer_overlay::{commit, discard, status, try_overlay, OverlayId};
|
use takana_overlay::{commit, discard, status, try_overlay, OverlayId};
|
||||||
|
|
||||||
fn have_bwrap() -> bool {
|
fn have_bwrap() -> bool {
|
||||||
Command::new("bwrap")
|
Command::new("bwrap")
|
||||||
@@ -197,7 +197,7 @@ fn body_nested_stack() {
|
|||||||
|
|
||||||
// --- guard LIFO: no se puede commitear la capa de abajo con la de arriba encima ---
|
// --- guard LIFO: no se puede commitear la capa de abajo con la de arriba encima ---
|
||||||
match commit(&id1, &state) {
|
match commit(&id1, &state) {
|
||||||
Err(hammer_overlay::Error::Shadowed { id, shadowed_by }) => {
|
Err(takana_overlay::Error::Shadowed { id, shadowed_by }) => {
|
||||||
assert_eq!(id, id1);
|
assert_eq!(id, id1);
|
||||||
assert_eq!(shadowed_by, vec![id2.clone()]);
|
assert_eq!(shadowed_by, vec![id2.clone()]);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-recover"
|
name = "takana-recover"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -7,10 +7,13 @@ authors.workspace = true
|
|||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
description = "Mini-binario de auto-recuperación de upgrades al arranque (E4 #4b): el /sbin/init lo corre para completar/deshacer un apply interrumpido antes de incarnar."
|
description = "Mini-binario de auto-recuperación de upgrades al arranque (E4 #4b): el /sbin/init lo corre para completar/deshacer un apply interrumpido antes de incarnar."
|
||||||
|
|
||||||
|
# El PAQUETE se renombra, el BINARIO no: `/usr/sbin/hammer-recover` está copiado en
|
||||||
|
# sistemas ya instalados y `hammer-live-install.sh` hornea un hook de arranque que lo
|
||||||
|
# invoca por ese nombre. Renombrar el binario rompe máquinas instaladas. Etapa 6.
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "hammer-recover"
|
name = "hammer-recover"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-upgrade.workspace = true
|
takana-upgrade.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
@@ -34,7 +34,7 @@ fn main() {
|
|||||||
let state = env_path("HAMMER_RECOVER_STATE", "/var/lib/hammer/upgrades");
|
let state = env_path("HAMMER_RECOVER_STATE", "/var/lib/hammer/upgrades");
|
||||||
let journal_dir = env_path("HAMMER_RECOVER_JOURNAL", "/var/lib/hammer/journal");
|
let journal_dir = env_path("HAMMER_RECOVER_JOURNAL", "/var/lib/hammer/journal");
|
||||||
|
|
||||||
match hammer_upgrade::pending(&state) {
|
match takana_upgrade::pending(&state) {
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
println!("hammer-recover: sin upgrade interrumpido");
|
println!("hammer-recover: sin upgrade interrumpido");
|
||||||
return;
|
return;
|
||||||
@@ -48,11 +48,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let journal = hammer_journal::Journal::open(&journal_dir).ok();
|
let journal = takana_journal::Journal::open(&journal_dir).ok();
|
||||||
let j = journal.as_ref();
|
let j = journal.as_ref();
|
||||||
|
|
||||||
if force_rollback {
|
if force_rollback {
|
||||||
match hammer_upgrade::recover(&store, &root, &state, j, true) {
|
match takana_upgrade::recover(&store, &root, &state, j, true) {
|
||||||
Ok(r) => println!("hammer-recover: DESHECHO (generación {} descartada)", r.generation),
|
Ok(r) => println!("hammer-recover: DESHECHO (generación {} descartada)", r.generation),
|
||||||
Err(e) => eprintln!("hammer-recover: rollback falló: {e}"),
|
Err(e) => eprintln!("hammer-recover: rollback falló: {e}"),
|
||||||
}
|
}
|
||||||
@@ -60,11 +60,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optimista: completar el upgrade. Si el roll-forward falla, deshacer ⇒ FHS consistente igual.
|
// Optimista: completar el upgrade. Si el roll-forward falla, deshacer ⇒ FHS consistente igual.
|
||||||
match hammer_upgrade::recover(&store, &root, &state, j, false) {
|
match takana_upgrade::recover(&store, &root, &state, j, false) {
|
||||||
Ok(r) => println!("hammer-recover: COMPLETADO (generación {})", r.generation),
|
Ok(r) => println!("hammer-recover: COMPLETADO (generación {})", r.generation),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("hammer-recover: roll-forward falló ({e}); deshaciendo para dejar el FHS consistente");
|
eprintln!("hammer-recover: roll-forward falló ({e}); deshaciendo para dejar el FHS consistente");
|
||||||
match hammer_upgrade::recover(&store, &root, &state, j, true) {
|
match takana_upgrade::recover(&store, &root, &state, j, true) {
|
||||||
Ok(r) => println!("hammer-recover: DESHECHO tras fallo (generación {})", r.generation),
|
Ok(r) => println!("hammer-recover: DESHECHO tras fallo (generación {})", r.generation),
|
||||||
Err(e2) => eprintln!("hammer-recover: rollback de respaldo también falló: {e2}"),
|
Err(e2) => eprintln!("hammer-recover: rollback de respaldo también falló: {e2}"),
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hammer-upgrade"
|
name = "takana-upgrade"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -8,8 +8,8 @@ repository.workspace = true
|
|||||||
description = "Upgrades atómicos de hammer (E4): aplica un árbol Stage1/producto del store al root vivo con generaciones y rollback."
|
description = "Upgrades atómicos de hammer (E4): aplica un árbol Stage1/producto del store al root vivo con generaciones y rollback."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hammer-core.workspace = true
|
takana-core.workspace = true
|
||||||
hammer-journal.workspace = true
|
takana-journal.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use hammer_core::ArtifactHash;
|
use takana_core::ArtifactHash;
|
||||||
use hammer_journal::Journal;
|
use takana_journal::Journal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//! Aplicar un árbol Stage1/producto **nuevo** al root vivo sin reinstalar, de forma atómica y con
|
//! Aplicar un árbol Stage1/producto **nuevo** al root vivo sin reinstalar, de forma atómica y con
|
||||||
//! **rollback** al árbol anterior. El árbol nuevo es un artefacto sellado del store (CAS BLAKE3); el
|
//! **rollback** al árbol anterior. El árbol nuevo es un artefacto sellado del store (CAS BLAKE3); el
|
||||||
//! "apply" lo proyecta sobre el FHS vivo (`target_root`, normalmente `/`), registra cada cambio en el
|
//! "apply" lo proyecta sobre el FHS vivo (`target_root`, normalmente `/`), registra cada cambio en el
|
||||||
//! [diario](hammer_journal) y deja una **generación** nueva. Si algo sale mal, `rollback` revierte
|
//! [diario](takana_journal) y deja una **generación** nueva. Si algo sale mal, `rollback` revierte
|
||||||
//! exactamente al árbol anterior.
|
//! exactamente al árbol anterior.
|
||||||
//!
|
//!
|
||||||
//! ## Modelo de generaciones (inspirado en NixOS, aplicado in-place al FHS)
|
//! ## Modelo de generaciones (inspirado en NixOS, aplicado in-place al FHS)
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use hammer_core::{ArtifactHash, Store};
|
use takana_core::{ArtifactHash, Store};
|
||||||
use hammer_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
use takana_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod boot_graph;
|
pub mod boot_graph;
|
||||||
@@ -48,9 +48,9 @@ pub enum Error {
|
|||||||
#[error("io: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("core: {0}")]
|
#[error("core: {0}")]
|
||||||
Core(#[from] hammer_core::Error),
|
Core(#[from] takana_core::Error),
|
||||||
#[error("journal: {0}")]
|
#[error("journal: {0}")]
|
||||||
Journal(#[from] hammer_journal::Error),
|
Journal(#[from] takana_journal::Error),
|
||||||
#[error("json: {0}")]
|
#[error("json: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error(
|
#[error(
|
||||||
@@ -789,7 +789,7 @@ fn journal_change(journal: Option<&Journal>, tree_dir: &str, dst: &Path, op: Cha
|
|||||||
ChangeOp::Removed => MutationOp::Delete,
|
ChangeOp::Removed => MutationOp::Delete,
|
||||||
};
|
};
|
||||||
let ev = MutationEvent {
|
let ev = MutationEvent {
|
||||||
ts: hammer_journal::now_rfc3339(),
|
ts: takana_journal::now_rfc3339(),
|
||||||
op: mop,
|
op: mop,
|
||||||
path: dst.to_path_buf(),
|
path: dst.to_path_buf(),
|
||||||
by: Actor {
|
by: Actor {
|
||||||
@@ -12,7 +12,7 @@ PKG="${1:?uso: alpine-import.sh <pkg> [main|community]}"
|
|||||||
REPO="${2:-main}"
|
REPO="${2:-main}"
|
||||||
BRANCH="${ALPINE_BRANCH:-master}"
|
BRANCH="${ALPINE_BRANCH:-master}"
|
||||||
OUTDIR="${OUTDIR:-.}"
|
OUTDIR="${OUTDIR:-.}"
|
||||||
HAMMER="${HAMMER:-cargo run -q -p hammer-cli --}"
|
HAMMER="${HAMMER:-cargo run -q -p takana-cli --}"
|
||||||
BASE="https://gitlab.alpinelinux.org/alpine/aports/-/raw/${BRANCH}/${REPO}/${PKG}"
|
BASE="https://gitlab.alpinelinux.org/alpine/aports/-/raw/${BRANCH}/${REPO}/${PKG}"
|
||||||
|
|
||||||
apk=$(curl -fsSL "${BASE}/APKBUILD") || { echo "no encontré ${REPO}/${PKG} en aports@${BRANCH}" >&2; exit 1; }
|
apk=$(curl -fsSL "${BASE}/APKBUILD") || { echo "no encontré ${REPO}/${PKG} en aports@${BRANCH}" >&2; exit 1; }
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ else
|
|||||||
# imprime (busca «LLD»/«GNU ld»/«GNU gold») y `--enable-lto=cross` necesita un linker con plugin
|
# imprime (busca «LLD»/«GNU ld»/«GNU gold») y `--enable-lto=cross` necesita un linker con plugin
|
||||||
# LLVM; `llvm22-linker-tools` da el LLVMgold.so para GNU ld, pero el camino soportado por
|
# LLVM; `llvm22-linker-tools` da el LLVMgold.so para GNU ld, pero el camino soportado por
|
||||||
# upstream —y el que usa el propio APKBUILD de Alpine con `--enable-linker=lld`— es lld.
|
# upstream —y el que usa el propio APKBUILD de Alpine con `--enable-linker=lld`— es lld.
|
||||||
# ⚠ DEUDA CONOCIDA: `lld` NO casa ningún prefijo de `TOOLCHAIN_PREFIXES` (hammer-core/src/lab.rs)
|
# ⚠ DEUDA CONOCIDA: `lld` NO casa ningún prefijo de `TOOLCHAIN_PREFIXES` (takana-core/src/lab.rs)
|
||||||
# ⇒ su versión NO entra en la huella del lab. Eso es lo que permite añadirlo HOY sin re-hashear
|
# ⇒ su versión NO entra en la huella del lab. Eso es lo que permite añadirlo HOY sin re-hashear
|
||||||
# los 837 artefactos sellados (medido: la huella no se movió), y a la vez es un agujero: dos labs
|
# los 837 artefactos sellados (medido: la huella no se movió), y a la vez es un agujero: dos labs
|
||||||
# con distinto lld pueden sellar bytes distintos en la misma dirección. Sólo expone a las recetas
|
# con distinto lld pueden sellar bytes distintos en la misma dirección. Sólo expone a las recetas
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ REPO="${REPO:-$(pwd)/dist/repo}"
|
|||||||
STORE="${STORE:-./store}"
|
STORE="${STORE:-./store}"
|
||||||
DISTRO="${DISTRO:-dev}"
|
DISTRO="${DISTRO:-dev}"
|
||||||
BUILD_TIMEOUT="${BUILD_TIMEOUT:-90}"
|
BUILD_TIMEOUT="${BUILD_TIMEOUT:-90}"
|
||||||
HAMMER="cargo run -q -p hammer-cli --"
|
HAMMER="cargo run -q -p takana-cli --"
|
||||||
|
|
||||||
rm -rf "$REPO"
|
rm -rf "$REPO"
|
||||||
mkdir -p "$REPO"
|
mkdir -p "$REPO"
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ while [ "$i" -le "$N" ]; do
|
|||||||
--exclude /dist --exclude /tandas --exclude /.dev-fs --exclude /.git --exclude /.scratch \
|
--exclude /dist --exclude /tandas --exclude /.dev-fs --exclude /.git --exclude /.scratch \
|
||||||
--exclude '*.png' --exclude '/content*' ./ "root@$ip:/opt/hammer/" >/dev/null 2>&1
|
--exclude '*.png' --exclude '/content*' ./ "root@$ip:/opt/hammer/" >/dev/null 2>&1
|
||||||
hN=$($SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd /opt/hammer && \
|
hN=$($SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd /opt/hammer && \
|
||||||
cargo build --release -q -p hammer-cli 2>/dev/null; \
|
cargo build --release -q -p takana-cli 2>/dev/null; \
|
||||||
timeout ${POR:-900} ./target/release/takana build $RECETA --store \$PWD/store 2>/dev/null \
|
timeout ${POR:-900} ./target/release/takana build $RECETA --store \$PWD/store 2>/dev/null \
|
||||||
| grep -oE '^b3:[a-f0-9]{64}' | head -1" 2>/dev/null | tr -d '\r')
|
| grep -oE '^b3:[a-f0-9]{64}' | head -1" 2>/dev/null | tr -d '\r')
|
||||||
echo " → ${hN:-FALLÓ}"
|
echo " → ${hN:-FALLÓ}"
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ fi
|
|||||||
# granja podaba eso: `store-gc.sh` mira el store, la poda de caché de CI mira la caché de CI, y este
|
# granja podaba eso: `store-gc.sh` mira el store, la poda de caché de CI mira la caché de CI, y este
|
||||||
# tercer montón crecía sin dueño desde que el store se mudó al volumen. Se recuperaron 70 G.
|
# tercer montón crecía sin dueño desde que el store se mudó al volumen. Se recuperaron 70 G.
|
||||||
#
|
#
|
||||||
# POR QUÉ ES SEGURO: no es una caché. Los dos caminos de `hammer-build/src/fetch.rs` borran el árbol
|
# POR QUÉ ES SEGURO: no es una caché. Los dos caminos de `takana-build/src/fetch.rs` borran el árbol
|
||||||
# y lo re-extraen en CADA build, sin una sola rama que reutilice ⇒ un árbol viejo no acelera nada y
|
# y lo re-extraen en CADA build, sin una sola rama que reutilice ⇒ un árbol viejo no acelera nada y
|
||||||
# borrarlo no cuesta ni la re-extracción. Lo único que se pierde es el post-mortem del último build
|
# borrarlo no cuesta ni la re-extracción. Lo único que se pierde es el post-mortem del último build
|
||||||
# de esa receta, y por eso el suelo son 24 h y no cero.
|
# de esa receta, y por eso el suelo son 24 h y no cero.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# ── POR QUÉ NO BASTA CON farm-sync ──────────────────────────────────────────────────────────────
|
# ── POR QUÉ NO BASTA CON farm-sync ──────────────────────────────────────────────────────────────
|
||||||
# `farm-sync.sh` sube el código con `--exclude /.dev-fs`, a propósito: el lab es del entorno, no del
|
# `farm-sync.sh` sube el código con `--exclude /.dev-fs`, a propósito: el lab es del entorno, no del
|
||||||
# repo. Eso estaba bien cuando el toolchain NO entraba en el hash. Desde el 2026-08-10 sí entra
|
# repo. Eso estaba bien cuando el toolchain NO entraba en el hash. Desde el 2026-08-10 sí entra
|
||||||
# (`hammer-core/src/lab.rs`), así que un worker con su lab horneado en la golden —rustc 1.96— sella
|
# (`takana-core/src/lab.rs`), así que un worker con su lab horneado en la golden —rustc 1.96— sella
|
||||||
# en direcciones DISTINTAS a las del hub —1.97— y su trabajo no le sirve a nadie: no es que compile
|
# en direcciones DISTINTAS a las del hub —1.97— y su trabajo no le sirve a nadie: no es que compile
|
||||||
# distinto, es que lo guarda en otra dirección y el hub nunca lo encuentra.
|
# distinto, es que lo guarda en otra dirección y el hub nunca lo encuentra.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ EOF
|
|||||||
./ "root@$ip:$REMOTE/" >/dev/null
|
./ "root@$ip:$REMOTE/" >/dev/null
|
||||||
$SSH "root@$ip" "MNT=/mnt/cosecha; mkdir -p \$MNT; mount $DEV \$MNT 2>/dev/null || (mkfs.ext4 -q -F $DEV && mount $DEV \$MNT); mkdir -p \$MNT/verdicts \$MNT/logs"
|
$SSH "root@$ip" "MNT=/mnt/cosecha; mkdir -p \$MNT; mount $DEV \$MNT 2>/dev/null || (mkfs.ext4 -q -F $DEV && mount $DEV \$MNT); mkdir -p \$MNT/verdicts \$MNT/logs"
|
||||||
scripts/farm/harkaq-farm-setup.sh "$ip" >/dev/null 2>&1 || echo " (setup con avisos, sigo)"
|
scripts/farm/harkaq-farm-setup.sh "$ip" >/dev/null 2>&1 || echo " (setup con avisos, sigo)"
|
||||||
$SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd $REMOTE && cargo build --release -q -p hammer-cli 2>&1 | tail -1"
|
$SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd $REMOTE && cargo build --release -q -p takana-cli 2>&1 | tail -1"
|
||||||
# Servicio con auto-shutdown (el script harkaq-campana-vol lo maneja).
|
# Servicio con auto-shutdown (el script harkaq-campana-vol lo maneja).
|
||||||
$SSH "root@$ip" "cat > /etc/systemd/system/harkaq-vol.service <<UNIT
|
$SSH "root@$ip" "cat > /etc/systemd/system/harkaq-vol.service <<UNIT
|
||||||
[Unit]
|
[Unit]
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ DRY="${DRY:-0}"
|
|||||||
SKIP_GUI="${SKIP_GUI:-1}"
|
SKIP_GUI="${SKIP_GUI:-1}"
|
||||||
GUI_RE='^(cairo|pango|gtk|harfbuzz|glib|gdk-pixbuf|libadwaita|gtksourceview|fribidi|graphene|fontconfig|adwaita|fcft|.*-hello|mirada|appstream|gnome)'
|
GUI_RE='^(cairo|pango|gtk|harfbuzz|glib|gdk-pixbuf|libadwaita|gtksourceview|fribidi|graphene|fontconfig|adwaita|fcft|.*-hello|mirada|appstream|gnome)'
|
||||||
|
|
||||||
[ -x "$HAMMER" ] || { echo "no encuentro hammer en $HAMMER (cargo build --release -p hammer-cli)"; exit 1; }
|
[ -x "$HAMMER" ] || { echo "no encuentro hammer en $HAMMER (cargo build --release -p takana-cli)"; exit 1; }
|
||||||
|
|
||||||
deuda=""; skipped_gui=""
|
deuda=""; skipped_gui=""
|
||||||
for f in $(grep -lE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' recipes/*.toml); do
|
for f in $(grep -lE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' recipes/*.toml); do
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ $SSH "root@$IP" "cd $REMOTE &&
|
|||||||
echo \" runtime base: \$(grep -c '^ro ' /root/.cache/harkaq/base.policy) reglas + \$(grep -c '^# expect' /root/.cache/harkaq/base.policy) esperadas\""
|
echo \" runtime base: \$(grep -c '^ro ' /root/.cache/harkaq/base.policy) reglas + \$(grep -c '^# expect' /root/.cache/harkaq/base.policy) esperadas\""
|
||||||
|
|
||||||
echo "== 4/4 comprobando la cadena de evidencia de punta a punta"
|
echo "== 4/4 comprobando la cadena de evidencia de punta a punta"
|
||||||
$SSH "root@$IP" "cd $REMOTE && cargo build -q -p hammer-cli 2>/dev/null || true; ls target/debug/hammer >/dev/null 2>&1 && echo ' hammer-cli listo ✓' || echo ' OJO: falta target/debug/hammer (cargo build -p hammer-cli)'"
|
$SSH "root@$IP" "cd $REMOTE && cargo build -q -p takana-cli 2>/dev/null || true; ls target/debug/hammer >/dev/null 2>&1 && echo ' hammer-cli listo ✓' || echo ' OJO: falta target/debug/hammer (cargo build -p takana-cli)'"
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ if [ -x "$HAMMER_MUSL" ]; then
|
|||||||
cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
|
cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
|
||||||
echo " + CLI hammer (static musl) para el menú de arranque por grafo"
|
echo " + CLI hammer (static musl) para el menú de arranque por grafo"
|
||||||
else
|
else
|
||||||
echo " ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p hammer-cli)" >&2
|
echo " ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p takana-cli)" >&2
|
||||||
fi
|
fi
|
||||||
# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
|
# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
|
||||||
# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
|
# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ if [ "${INSTALLER:-0}" = 1 ]; then
|
|||||||
RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover"
|
RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover"
|
||||||
if [ ! -x "$RECOVER_BIN" ]; then
|
if [ ! -x "$RECOVER_BIN" ]; then
|
||||||
echo "==> compilando hammer-recover (static musl) para el payload"
|
echo "==> compilando hammer-recover (static musl) para el payload"
|
||||||
cargo build --release --target x86_64-unknown-linux-musl -p hammer-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
|
cargo build --release --target x86_64-unknown-linux-musl -p takana-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
[ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; }
|
[ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; }
|
||||||
install -m 0755 "$RECOVER_BIN" "$IPL/hammer-recover"
|
install -m 0755 "$RECOVER_BIN" "$IPL/hammer-recover"
|
||||||
@@ -131,7 +131,7 @@ if [ "${INSTALLER:-0}" = 1 ]; then
|
|||||||
HAMMER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer"
|
HAMMER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer"
|
||||||
if [ ! -x "$HAMMER_BIN" ]; then
|
if [ ! -x "$HAMMER_BIN" ]; then
|
||||||
echo "==> compilando hammer CLI (static musl) para el payload"
|
echo "==> compilando hammer CLI (static musl) para el payload"
|
||||||
cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
|
cargo build --release --target x86_64-unknown-linux-musl -p takana-cli --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
[ -x "$HAMMER_BIN" ] || { echo "no pude construir el CLI hammer (target musl?)" >&2; exit 1; }
|
[ -x "$HAMMER_BIN" ] || { echo "no pude construir el CLI hammer (target musl?)" >&2; exit 1; }
|
||||||
install -m 0755 "$HAMMER_BIN" "$IPL/hammer"
|
install -m 0755 "$HAMMER_BIN" "$IPL/hammer"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# lab-image.sh — empaqueta y publica el ROOTFS YA BOOTSTRAPEADO como imagen pineada por sha256.
|
# lab-image.sh — empaqueta y publica el ROOTFS YA BOOTSTRAPEADO como imagen pineada por sha256.
|
||||||
#
|
#
|
||||||
# ── POR QUÉ EXISTE ──────────────────────────────────────────────────────────────────────────────
|
# ── POR QUÉ EXISTE ──────────────────────────────────────────────────────────────────────────────
|
||||||
# Desde el 2026-08-10 el toolchain del lab entra en `hash_inputs` (ver `hammer-core/src/lab.rs`):
|
# Desde el 2026-08-10 el toolchain del lab entra en `hash_inputs` (ver `takana-core/src/lab.rs`):
|
||||||
# dos máquinas con distinto `rustc` sellan en direcciones distintas. Eso cierra un agujero real
|
# dos máquinas con distinto `rustc` sellan en direcciones distintas. Eso cierra un agujero real
|
||||||
# —antes sellaban bytes distintos en la MISMA dirección— pero abre uno operativo:
|
# —antes sellaban bytes distintos en la MISMA dirección— pero abre uno operativo:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
# nixpkgs es el mayor set de recetas desde fuente; importamos la receta y hammer reconstruye.
|
# nixpkgs es el mayor set de recetas desde fuente; importamos la receta y hammer reconstruye.
|
||||||
#
|
#
|
||||||
# Requiere `nix` (con nix-command). Ej: scripts/nix-import.sh hello > recipes/hello.toml
|
# Requiere `nix` (con nix-command). Ej: scripts/nix-import.sh hello > recipes/hello.toml
|
||||||
# Env: NIXPKGS=<flakeref> (default nixpkgs), HAMMER=<cmd> (default cargo run -q -p hammer-cli --)
|
# Env: NIXPKGS=<flakeref> (default nixpkgs), HAMMER=<cmd> (default cargo run -q -p takana-cli --)
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ATTR="${1:?uso: nix-import.sh <attr-de-nixpkgs> (ej: hello, jq, ripgrep)}"
|
ATTR="${1:?uso: nix-import.sh <attr-de-nixpkgs> (ej: hello, jq, ripgrep)}"
|
||||||
NIXPKGS="${NIXPKGS:-nixpkgs}"
|
NIXPKGS="${NIXPKGS:-nixpkgs}"
|
||||||
HAMMER="${HAMMER:-cargo run -q -p hammer-cli --}"
|
HAMMER="${HAMMER:-cargo run -q -p takana-cli --}"
|
||||||
# NIX_STORE: si el /nix/store del sistema no es escribible (install single-user incompleta), apuntá
|
# NIX_STORE: si el /nix/store del sistema no es escribible (install single-user incompleta), apuntá
|
||||||
# a un store local, p.ej. NIX_STORE="local?root=$HOME/.nixstore".
|
# a un store local, p.ej. NIX_STORE="local?root=$HOME/.nixstore".
|
||||||
STORE_OPT=""
|
STORE_OPT=""
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user