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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "hammerd"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"hammer-build",
|
||||
"hammer-core",
|
||||
"hammer-journal",
|
||||
"hammer-overlay",
|
||||
"libc",
|
||||
"nix",
|
||||
"postcard",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"takana-build",
|
||||
"takana-core",
|
||||
"takana-journal",
|
||||
"takana-overlay",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
@@ -1381,6 +1223,164 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
|
||||
+18
-18
@@ -1,16 +1,16 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/hammer-core",
|
||||
"crates/hammer-build",
|
||||
"crates/hammer-bootstrap",
|
||||
"crates/hammer-overlay",
|
||||
"crates/hammer-journal",
|
||||
"crates/hammer-mirror",
|
||||
"crates/hammer-upgrade",
|
||||
"crates/hammer-recover",
|
||||
"crates/hammer-agent",
|
||||
"crates/hammer-cli",
|
||||
"crates/takana-core",
|
||||
"crates/takana-build",
|
||||
"crates/takana-bootstrap",
|
||||
"crates/takana-overlay",
|
||||
"crates/takana-journal",
|
||||
"crates/takana-mirror",
|
||||
"crates/takana-upgrade",
|
||||
"crates/takana-recover",
|
||||
"crates/takana-agent",
|
||||
"crates/takana-cli",
|
||||
"crates/hammerd",
|
||||
"crates/netup",
|
||||
]
|
||||
@@ -23,14 +23,14 @@ authors = ["sergio <gerencia@jlsoltech.com>"]
|
||||
repository = "https://gitea.gioser.net/sergio/hammer"
|
||||
|
||||
[workspace.dependencies]
|
||||
hammer-core = { path = "crates/hammer-core" }
|
||||
hammer-build = { path = "crates/hammer-build" }
|
||||
hammer-bootstrap = { path = "crates/hammer-bootstrap" }
|
||||
hammer-overlay = { path = "crates/hammer-overlay" }
|
||||
hammer-journal = { path = "crates/hammer-journal" }
|
||||
hammer-mirror = { path = "crates/hammer-mirror" }
|
||||
hammer-upgrade = { path = "crates/hammer-upgrade" }
|
||||
hammer-agent = { path = "crates/hammer-agent" }
|
||||
takana-core = { path = "crates/takana-core" }
|
||||
takana-build = { path = "crates/takana-build" }
|
||||
takana-bootstrap = { path = "crates/takana-bootstrap" }
|
||||
takana-overlay = { path = "crates/takana-overlay" }
|
||||
takana-journal = { path = "crates/takana-journal" }
|
||||
takana-mirror = { path = "crates/takana-mirror" }
|
||||
takana-upgrade = { path = "crates/takana-upgrade" }
|
||||
takana-agent = { path = "crates/takana-agent" }
|
||||
|
||||
# shared third-party deps, pinned at the workspace level
|
||||
anyhow = "1"
|
||||
|
||||
@@ -12,10 +12,10 @@ name = "hammerd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-build.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
hammer-overlay.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-build.workspace = true
|
||||
takana-journal.workspace = true
|
||||
takana-overlay.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
+26
-26
@@ -25,7 +25,7 @@ use std::thread;
|
||||
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
|
||||
|
||||
use crate::events::EventBus;
|
||||
use hammer_core::proto::{
|
||||
use takana_core::proto::{
|
||||
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, RecipeInline,
|
||||
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;
|
||||
/// 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))
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ fn dispatch(
|
||||
.ok();
|
||||
}
|
||||
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,
|
||||
Err(e) => {
|
||||
let _ = tx.send(Event::Error {
|
||||
@@ -279,10 +279,10 @@ fn dispatch(
|
||||
return;
|
||||
}
|
||||
};
|
||||
let report = match hammer_build::run_hydrate(
|
||||
let report = match takana_build::run_hydrate(
|
||||
&artifact_dir,
|
||||
Path::new(&target),
|
||||
hammer_core::LinkMode::Static,
|
||||
takana_core::LinkMode::Static,
|
||||
None,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
@@ -333,7 +333,7 @@ fn dispatch(
|
||||
// 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
|
||||
// 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 => {
|
||||
let _ = tx.send(Event::Error {
|
||||
@@ -363,7 +363,7 @@ fn dispatch(
|
||||
|
||||
fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
||||
let name = recipe.name.clone();
|
||||
let mutation = hammer_core::swm::Mutation::SourcePatch {
|
||||
let mutation = takana_core::swm::Mutation::SourcePatch {
|
||||
repo: recipe.repo,
|
||||
commit: recipe.commit,
|
||||
tarball: recipe.tarball,
|
||||
@@ -371,7 +371,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
||||
strip_components: None,
|
||||
patch: recipe.patch,
|
||||
patch_url: None,
|
||||
build: hammer_core::swm::SwmBuild {
|
||||
build: takana_core::swm::SwmBuild {
|
||||
compiler: recipe.compiler,
|
||||
target: recipe.target,
|
||||
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í.
|
||||
slots: Default::default(),
|
||||
};
|
||||
let store = match hammer_core::Store::open(&store_root) {
|
||||
let store = match takana_core::Store::open(&store_root) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
let _ = tx.send(Event::BuildFailed {
|
||||
@@ -400,8 +400,8 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(store.root());
|
||||
match hammer_build::build_source_patch(&mutation, &cfg, &store, None, None) {
|
||||
let cfg = takana_build::BuildConfig::from_env_or_defaults(store.root());
|
||||
match takana_build::build_source_patch(&mutation, &cfg, &store, None, None) {
|
||||
Ok(h) => {
|
||||
// [H1c] El artefacto está sellado; ahora EJECUTAMOS la evidencia declarada y
|
||||
// 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) => {
|
||||
// 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.
|
||||
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()),
|
||||
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
|
||||
/// build-dep, error de sandbox…): eso también debe bloquear la propuesta, no pasar en silencio.
|
||||
fn evidence_verdict(
|
||||
mutation: &hammer_core::swm::Mutation,
|
||||
cfg: &hammer_build::BuildConfig,
|
||||
store: &hammer_core::Store,
|
||||
mutation: &takana_core::swm::Mutation,
|
||||
cfg: &takana_build::BuildConfig,
|
||||
store: &takana_core::Store,
|
||||
) -> Option<EvidenceVerdict> {
|
||||
let has_evidence = matches!(
|
||||
mutation,
|
||||
hammer_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
|
||||
takana_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
|
||||
);
|
||||
if !has_evidence {
|
||||
return None;
|
||||
@@ -456,7 +456,7 @@ fn evidence_verdict(
|
||||
all_passed: false,
|
||||
max_level: None,
|
||||
checks: vec![EvidenceCheckVerdict {
|
||||
kind: hammer_core::EvidenceKind::CmdExit,
|
||||
kind: takana_core::EvidenceKind::CmdExit,
|
||||
cmd: "<runner>".into(),
|
||||
passed: false,
|
||||
detail: format!("la evidencia no pudo ejecutarse: {e}"),
|
||||
@@ -467,14 +467,14 @@ fn evidence_verdict(
|
||||
}
|
||||
|
||||
fn run_evidence_inner(
|
||||
mutation: &hammer_core::swm::Mutation,
|
||||
cfg: &hammer_build::BuildConfig,
|
||||
store: &hammer_core::Store,
|
||||
) -> hammer_core::Result<EvidenceVerdict> {
|
||||
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(cfg, None);
|
||||
mutation: &takana_core::swm::Mutation,
|
||||
cfg: &takana_build::BuildConfig,
|
||||
store: &takana_core::Store,
|
||||
) -> takana_core::Result<EvidenceVerdict> {
|
||||
let catalog_dir = takana_build::swm_bridge::catalog_dir_for(cfg, None);
|
||||
std::fs::create_dir_all(&catalog_dir)?;
|
||||
let recipe = hammer_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
|
||||
let report = hammer_build::run_evidence(&recipe, cfg, store)?;
|
||||
let recipe = takana_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
|
||||
let report = takana_build::run_evidence(&recipe, cfg, store)?;
|
||||
Ok(EvidenceVerdict {
|
||||
all_passed: report.all_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 {
|
||||
let store = match hammer_core::Store::open(store_root) {
|
||||
let store = match takana_core::Store::open(store_root) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return serde_json::json!({"error": e.to_string()}),
|
||||
};
|
||||
@@ -552,7 +552,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn config_policy_resolves_by_uid_gid() {
|
||||
let cfg = hammer_core::AgentCapsConfig::from_toml(
|
||||
let cfg = takana_core::AgentCapsConfig::from_toml(
|
||||
r#"
|
||||
default = ["query"]
|
||||
[[rule]]
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use hammer_core::proto::Event;
|
||||
use takana_core::proto::Event;
|
||||
|
||||
use crate::events::EventBus;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hammer_core::proto::Event;
|
||||
use takana_core::proto::Event;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventBus {
|
||||
@@ -58,7 +58,7 @@ impl EventBus {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hammer_core::proto::Event;
|
||||
use takana_core::proto::Event;
|
||||
|
||||
#[test]
|
||||
fn publish_reaches_all_subs() {
|
||||
|
||||
@@ -33,7 +33,7 @@ struct Args {
|
||||
journal: String,
|
||||
/// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
|
||||
/// 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,
|
||||
/// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
|
||||
#[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.
|
||||
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();
|
||||
dirs.extend(args.extra_watch.clone());
|
||||
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.
|
||||
// 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)) => {
|
||||
tracing::info!(path = %args.agent_caps.display(), rules = cfg.rule.len(),
|
||||
"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
|
||||
//! 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
|
||||
//! (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).
|
||||
//! * 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
|
||||
@@ -27,7 +27,7 @@ use std::os::fd::AsFd;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
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::sys::fanotify::{EventFFlags, Fanotify, InitFlags, MarkFlags, MaskFlags};
|
||||
|
||||
@@ -48,7 +48,7 @@ pub enum Error {
|
||||
#[error("read_events: {0}")]
|
||||
Read(nix::errno::Errno),
|
||||
#[error("journal: {0}")]
|
||||
Journal(#[from] hammer_journal::Error),
|
||||
Journal(#[from] takana_journal::Error),
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
@@ -160,7 +160,7 @@ impl Watcher {
|
||||
let content_hash = if deleted {
|
||||
None
|
||||
} else {
|
||||
match hammer_journal::hash_file(&path) {
|
||||
match takana_journal::hash_file(&path) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
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 me = MutationEvent {
|
||||
ts: hammer_journal::now_rfc3339(),
|
||||
ts: takana_journal::now_rfc3339(),
|
||||
op,
|
||||
path,
|
||||
by: Actor {
|
||||
@@ -188,7 +188,7 @@ impl Watcher {
|
||||
}
|
||||
recorded += 1;
|
||||
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(),
|
||||
op: me.op.as_str().to_string(),
|
||||
ts: me.ts.clone(),
|
||||
@@ -216,7 +216,7 @@ impl Watcher {
|
||||
let Some(root) = &self.overlay_state_root else {
|
||||
return Vec::new();
|
||||
};
|
||||
match hammer_overlay::status(root) {
|
||||
match takana_overlay::status(root) {
|
||||
Ok(overlays) => overlays
|
||||
.into_iter()
|
||||
.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 {
|
||||
MutationEvent {
|
||||
ts: hammer_journal::now_rfc3339(),
|
||||
ts: takana_journal::now_rfc3339(),
|
||||
op,
|
||||
path: PathBuf::from(path),
|
||||
by: Actor { source: Source::External, pid: Some(1), uid: Some(0) },
|
||||
|
||||
@@ -24,7 +24,7 @@ use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hammer_core::proto::Event;
|
||||
use takana_core::proto::Event;
|
||||
use serde::Serialize;
|
||||
use ulid::Ulid;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
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
|
||||
/// test gracias al `tempfile::TempDir` que se le pasa al caller.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-agent"
|
||||
name = "takana-agent"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -14,8 +14,8 @@ default = []
|
||||
llm-claude = ["dep:ureq"]
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-overlay.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-overlay.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -26,5 +26,5 @@ ureq = { version = "2", optional = true, default-features = false, features = ["
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
takana-journal.workspace = true
|
||||
base64.workspace = true
|
||||
@@ -17,7 +17,7 @@ use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::thread;
|
||||
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)]
|
||||
pub enum ClientError {
|
||||
@@ -49,7 +49,7 @@ pub struct Welcome {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompileOutcome {
|
||||
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.
|
||||
@@ -111,7 +111,7 @@ impl AgentClient {
|
||||
},
|
||||
};
|
||||
me.send(&Command::Hello {
|
||||
ver: hammer_core::proto::PROTOCOL_VERSION,
|
||||
ver: takana_core::proto::PROTOCOL_VERSION,
|
||||
client: client.to_string(),
|
||||
})?;
|
||||
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.
|
||||
/// 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> {
|
||||
self.send(&Command::Query {
|
||||
what: "expr".into(),
|
||||
@@ -38,11 +38,11 @@ pub enum Error {
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("overlay: {0}")]
|
||||
Overlay(#[from] hammer_overlay::Error),
|
||||
Overlay(#[from] takana_overlay::Error),
|
||||
#[error("apply: {0}")]
|
||||
Apply(#[from] hammer_core::apply::ApplyError),
|
||||
Apply(#[from] takana_core::apply::ApplyError),
|
||||
#[error("core: {0}")]
|
||||
Core(#[from] hammer_core::Error),
|
||||
Core(#[from] takana_core::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -7,7 +7,7 @@
|
||||
//! schema → swm.verify_schema()
|
||||
//! base → swm.verify_base(local) → debe ser Ok
|
||||
//! 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
|
||||
//! verify → spot-checks post-condición sobre disco
|
||||
//! propose → devuelve Proposal con overlay_id + log de checks
|
||||
@@ -18,11 +18,11 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
|
||||
use hammer_core::proto::RecipeInline;
|
||||
use hammer_core::swm::{Mutation, Swm};
|
||||
use hammer_core::BaseRef;
|
||||
use hammer_overlay::OverlayId;
|
||||
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
|
||||
use takana_core::proto::RecipeInline;
|
||||
use takana_core::swm::{Mutation, Swm};
|
||||
use takana_core::BaseRef;
|
||||
use takana_overlay::OverlayId;
|
||||
|
||||
use crate::client::AgentClient;
|
||||
use crate::translator::{IntentTranslator, SystemContext};
|
||||
@@ -79,7 +79,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
||||
|
||||
// 3. BASE
|
||||
match swm.verify_base(&self.base) {
|
||||
hammer_core::BaseCompat::Ok => {}
|
||||
takana_core::BaseCompat::Ok => {}
|
||||
other => {
|
||||
return Err(Error::Orchestrate(format!(
|
||||
"base incompatible: {other:?}"
|
||||
@@ -91,7 +91,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
||||
let (overlay_id, prefix): (Option<OverlayId>, Option<PathBuf>) = match &self.apply {
|
||||
ApplyTarget::Overlay { 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)
|
||||
}
|
||||
ApplyTarget::Prefix { prefix } => {
|
||||
@@ -147,8 +147,8 @@ impl<T: IntentTranslator> Orchestrator<T> {
|
||||
}
|
||||
Mutation::InitRule { action, service, command } => {
|
||||
let rules_dir =
|
||||
rebase_path(hammer_core::apply::INIT_RULES_DIR, prefix.as_deref());
|
||||
let p = hammer_core::apply::apply_init_rule(
|
||||
rebase_path(takana_core::apply::INIT_RULES_DIR, prefix.as_deref());
|
||||
let p = takana_core::apply::apply_init_rule(
|
||||
&rules_dir, action, service, command,
|
||||
)
|
||||
.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
|
||||
// segundo Inject que duplicaría trabajo).
|
||||
let store = hammer_core::Store::open(store)?;
|
||||
let store = takana_core::Store::open(store)?;
|
||||
let artifact_dir = store
|
||||
.find_by_hash(&artifact)
|
||||
.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;
|
||||
// Drenamos primero la cola asíncrona ya acumulada (eventos llegados durante el `run`).
|
||||
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 });
|
||||
}
|
||||
}
|
||||
while let Some(remaining) = deadline.checked_duration_since(std::time::Instant::now()) {
|
||||
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 });
|
||||
}
|
||||
Ok(_other) => continue,
|
||||
@@ -554,7 +554,7 @@ impl Default for RepairPolicy {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::translator::MockTranslator;
|
||||
use hammer_core::swm::{Base, Mutation};
|
||||
use takana_core::swm::{Base, Mutation};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn baseref() -> BaseRef {
|
||||
@@ -626,7 +626,7 @@ mod tests {
|
||||
strip_components: None,
|
||||
patch: None,
|
||||
patch_url: None,
|
||||
build: hammer_core::swm::SwmBuild {
|
||||
build: takana_core::swm::SwmBuild {
|
||||
compiler: "zig-cc".into(),
|
||||
target: "x86_64-linux-musl".into(),
|
||||
link: "static".into(),
|
||||
@@ -12,7 +12,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use hammer_core::{BaseRef, Swm};
|
||||
use takana_core::{BaseRef, Swm};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TranslateError {
|
||||
@@ -165,7 +165,7 @@ impl IntentTranslator for MockTranslator {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hammer_core::swm::{Base, Mutation};
|
||||
use takana_core::swm::{Base, Mutation};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn empty_ctx() -> SystemContext {
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
//! ```
|
||||
|
||||
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;
|
||||
/// los tests pasan una fake.
|
||||
@@ -279,7 +279,7 @@ mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
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.
|
||||
struct FakeHttp {
|
||||
@@ -11,12 +11,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use hammer_agent::{
|
||||
use takana_agent::{
|
||||
orchestrator::{ApplyTarget, CompileMode},
|
||||
MockTranslator, Orchestrator,
|
||||
};
|
||||
use hammer_core::swm::{Base, Mutation, Swm};
|
||||
use hammer_core::{ArtifactHash, BaseRef};
|
||||
use takana_core::swm::{Base, Mutation, Swm};
|
||||
use takana_core::{ArtifactHash, BaseRef};
|
||||
|
||||
fn baseref() -> BaseRef {
|
||||
let mut pins = BTreeMap::new();
|
||||
@@ -86,7 +86,7 @@ fn intent_to_overlay_proposal_via_mock_translator() {
|
||||
proposal
|
||||
.checks
|
||||
.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"
|
||||
);
|
||||
|
||||
+8
-8
@@ -10,7 +10,7 @@ use std::path::PathBuf;
|
||||
use std::thread;
|
||||
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> {
|
||||
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
|
||||
// ejercitar el camino H1c del cliente (sin lab real que la ejecute).
|
||||
let verdict = (!recipe.evidence.is_empty()).then(|| {
|
||||
hammer_core::proto::EvidenceVerdict {
|
||||
takana_core::proto::EvidenceVerdict {
|
||||
all_passed: true,
|
||||
max_level: recipe.evidence.checks.iter().map(|c| c.kind).max(),
|
||||
checks: recipe
|
||||
.evidence
|
||||
.checks
|
||||
.iter()
|
||||
.map(|c| hammer_core::proto::EvidenceCheckVerdict {
|
||||
.map(|c| takana_core::proto::EvidenceCheckVerdict {
|
||||
kind: c.kind,
|
||||
cmd: c.cmd.clone(),
|
||||
passed: true,
|
||||
@@ -152,7 +152,7 @@ fn client_handshake_compile_and_async_modified() {
|
||||
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!(client.welcome.caps.contains(&Cap::Compile));
|
||||
|
||||
@@ -167,9 +167,9 @@ fn client_handshake_compile_and_async_modified() {
|
||||
target: "x86_64-linux-musl".into(),
|
||||
link: "static".into(),
|
||||
flags: vec![],
|
||||
evidence: hammer_core::Evidence {
|
||||
checks: vec![hammer_core::EvidenceCheck {
|
||||
kind: hammer_core::EvidenceKind::CmdExit,
|
||||
evidence: takana_core::Evidence {
|
||||
checks: vec![takana_core::EvidenceCheck {
|
||||
kind: takana_core::EvidenceKind::CmdExit,
|
||||
cmd: "grep --version".into(),
|
||||
expected_exit: 0,
|
||||
expected_output: None,
|
||||
@@ -184,7 +184,7 @@ fn client_handshake_compile_and_async_modified() {
|
||||
let verdict = outcome.verdict.expect("verdict presente (declaramos evidencia)");
|
||||
assert!(verdict.all_passed);
|
||||
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
|
||||
// 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::time::Duration;
|
||||
|
||||
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
|
||||
use hammer_agent::MockTranslator;
|
||||
use hammer_core::proto::{
|
||||
use takana_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
|
||||
use takana_agent::MockTranslator;
|
||||
use takana_core::proto::{
|
||||
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, PROTOCOL_VERSION,
|
||||
};
|
||||
use hammer_core::swm::{Base, Mutation, Swm, SwmBuild};
|
||||
use hammer_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
|
||||
use takana_core::swm::{Base, Mutation, Swm, SwmBuild};
|
||||
use takana_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// 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::time::Duration;
|
||||
|
||||
use hammer_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
|
||||
use hammer_core::swm::{Base, Mutation};
|
||||
use hammer_core::{BaseRef, Swm};
|
||||
use takana_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
|
||||
use takana_core::swm::{Base, Mutation};
|
||||
use takana_core::{BaseRef, Swm};
|
||||
|
||||
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
|
||||
use hammer_agent::{MockTranslator, Orchestrator};
|
||||
use takana_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
|
||||
use takana_agent::{MockTranslator, Orchestrator};
|
||||
|
||||
/// 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.
|
||||
@@ -112,7 +112,7 @@ fn swm_touch(path: &str) -> Swm {
|
||||
mutations: vec![Mutation::FileDrop {
|
||||
path: path.into(),
|
||||
// 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_url: None,
|
||||
}],
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-bootstrap"
|
||||
name = "takana-bootstrap"
|
||||
version.workspace = true
|
||||
edition.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)."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-build.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-build.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -17,8 +17,8 @@ pub mod manifest;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_build::download;
|
||||
use hammer_core::{ArtifactHash, Recipe, Store};
|
||||
use takana_build::download;
|
||||
use takana_core::{ArtifactHash, Recipe, Store};
|
||||
|
||||
pub use manifest::{BootstrapManifest, StageEntry};
|
||||
|
||||
@@ -27,7 +27,7 @@ pub enum Error {
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("core: {0}")]
|
||||
Core(#[from] hammer_core::Error),
|
||||
Core(#[from] takana_core::Error),
|
||||
#[error("tar -x de la semilla falló (exit {0:?})")]
|
||||
Untar(Option<i32>),
|
||||
#[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
|
||||
/// 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.
|
||||
pub fn seed_build_config(
|
||||
base: &hammer_build::BuildConfig,
|
||||
base: &takana_build::BuildConfig,
|
||||
seed: &SeedSpec,
|
||||
store: &Store,
|
||||
) -> Result<hammer_build::BuildConfig> {
|
||||
) -> Result<takana_build::BuildConfig> {
|
||||
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**
|
||||
@@ -265,15 +265,15 @@ pub struct Stage1Spec {
|
||||
///
|
||||
/// 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`
|
||||
/// `hammer_build::build` falla limpio antes de ensamblar nada.
|
||||
/// `takana_build::build` falla limpio antes de ensamblar nada.
|
||||
pub fn stage1(
|
||||
spec: &Stage1Spec,
|
||||
base_cfg: &hammer_build::BuildConfig,
|
||||
base_cfg: &takana_build::BuildConfig,
|
||||
store: &Store,
|
||||
) -> Result<RootfsHash> {
|
||||
// Toolchain = la semilla sellada, no el zig del host.
|
||||
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).
|
||||
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| {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ fn assemble_rootfs(
|
||||
// Cada componente se proyecta al rootfs por hardlink (mismo store, mismo filesystem).
|
||||
for (name, h) in components {
|
||||
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
|
||||
@@ -605,14 +605,14 @@ fn assemble_product_rootfs(
|
||||
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
|
||||
// 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í).
|
||||
for (name, h) in userland.iter().chain(services.iter()) {
|
||||
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"));
|
||||
|
||||
@@ -681,7 +681,7 @@ fn assemble_product_rootfs(
|
||||
fn build_components(
|
||||
names: &[&str],
|
||||
recipes_dir: &Path,
|
||||
cfg: &hammer_build::BuildConfig,
|
||||
cfg: &takana_build::BuildConfig,
|
||||
store: &Store,
|
||||
) -> Result<Vec<(String, ArtifactHash)>> {
|
||||
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| {
|
||||
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));
|
||||
}
|
||||
Ok(out)
|
||||
@@ -703,7 +703,7 @@ fn build_components(
|
||||
/// semilla): cada componente usa su `zig_version` propia, y si ya está sellado sale cacheado.
|
||||
pub fn product(
|
||||
base: &RootfsHash,
|
||||
base_cfg: &hammer_build::BuildConfig,
|
||||
base_cfg: &takana_build::BuildConfig,
|
||||
recipes_dir: &Path,
|
||||
store: &Store,
|
||||
) -> Result<RootfsHash> {
|
||||
@@ -725,7 +725,7 @@ pub fn product_from_repo(
|
||||
base: &RootfsHash,
|
||||
repo_dir: &Path,
|
||||
trust_dir: &Path,
|
||||
base_cfg: &hammer_build::BuildConfig,
|
||||
base_cfg: &takana_build::BuildConfig,
|
||||
store: &Store,
|
||||
) -> Result<RootfsHash> {
|
||||
// 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],
|
||||
repo_dir: &Path,
|
||||
trust_dir: &Path,
|
||||
cfg: &hammer_build::BuildConfig,
|
||||
cfg: &takana_build::BuildConfig,
|
||||
store: &Store,
|
||||
) -> Result<Vec<(String, ArtifactHash)>> {
|
||||
use hammer_core::{RepoIndex, SigStatus, TrustStore};
|
||||
use takana_core::{RepoIndex, SigStatus, TrustStore};
|
||||
|
||||
let index = RepoIndex::load(repo_dir)?;
|
||||
// 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)?;
|
||||
|
||||
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(|| {
|
||||
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,
|
||||
Some(&dep.name),
|
||||
&catalog_dir,
|
||||
@@ -829,7 +829,7 @@ pub fn install_components_from_repo(
|
||||
let mutation = source_patch_of(&swm).ok_or_else(|| {
|
||||
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");
|
||||
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í).
|
||||
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)
|
||||
.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())))
|
||||
}
|
||||
|
||||
/// 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
|
||||
.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) ───────
|
||||
@@ -966,7 +966,7 @@ fn assemble_attested_product_rootfs(
|
||||
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"));
|
||||
|
||||
// 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.
|
||||
pub fn product_attested(
|
||||
base: &RootfsHash,
|
||||
base_cfg: &hammer_build::BuildConfig,
|
||||
base_cfg: &takana_build::BuildConfig,
|
||||
recipes_dir: &Path,
|
||||
store: &Store,
|
||||
cfg: &AttestConfig,
|
||||
@@ -1245,7 +1245,7 @@ pub struct AllReport {
|
||||
pub fn all(
|
||||
seed: &SeedSpec,
|
||||
recipes_dir: &Path,
|
||||
base_cfg: &hammer_build::BuildConfig,
|
||||
base_cfg: &takana_build::BuildConfig,
|
||||
store: &Store,
|
||||
) -> Result<AllReport> {
|
||||
let seed_hash = stage0(seed, store)?;
|
||||
@@ -1505,7 +1505,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
|
||||
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.
|
||||
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() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
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();
|
||||
assert_eq!(cfg.zig_dir, seed.toolchain_dir(&store).unwrap());
|
||||
// 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 r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
||||
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:"));
|
||||
}
|
||||
}
|
||||
@@ -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 r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
|
||||
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:");
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
.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()));
|
||||
assert!(h.as_str().starts_with("b3:"), "{}: hash con prefijo b3:", path.display());
|
||||
count += 1;
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use hammer_core::{ArtifactHash, Store};
|
||||
use takana_core::{ArtifactHash, Store};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, Result};
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-build"
|
||||
name = "takana-build"
|
||||
version.workspace = true
|
||||
edition.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."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
takana-core.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.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
|
||||
/// 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")
|
||||
.args([
|
||||
"-fsSL",
|
||||
@@ -25,9 +25,9 @@ pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
|
||||
url,
|
||||
])
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"curl {url} falló (exit {}): {}",
|
||||
out.status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into()),
|
||||
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).
|
||||
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)?;
|
||||
if let Some(parent) = dst.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
|
||||
/// 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.
|
||||
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};
|
||||
let mut f = std::fs::File::open(path)?;
|
||||
let mut hasher = Sha256::new();
|
||||
std::io::copy(&mut f, &mut hasher)?;
|
||||
let got = hex::encode(hasher.finalize());
|
||||
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}",
|
||||
path.display()
|
||||
)));
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 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
|
||||
//! 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::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.
|
||||
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()? {
|
||||
SourceKind::Git { repo, commit } => fetch_git(recipe, repo, commit, 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
|
||||
/// 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);
|
||||
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",
|
||||
origen.display()
|
||||
)));
|
||||
}
|
||||
let h = hammer_core::ArtifactHash::of_tree(&origen)
|
||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("hash del árbol {}: {e}", origen.display())))?;
|
||||
let h = takana_core::ArtifactHash::of_tree(&origen)
|
||||
.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 sources_dir = work_root.join("sources");
|
||||
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(&work_tree)
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"cp -a de {} falló",
|
||||
origen.display()
|
||||
)));
|
||||
@@ -72,7 +72,7 @@ fn fetch_git(
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
work_root: &Path,
|
||||
) -> hammer_core::Result<PathBuf> {
|
||||
) -> takana_core::Result<PathBuf> {
|
||||
let repos_dir = work_root.join("repos");
|
||||
let sources_dir = work_root.join("sources");
|
||||
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() {
|
||||
run_git(&["fetch", "--all", "--tags"], Some(&mirror))?;
|
||||
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"
|
||||
))
|
||||
})?;
|
||||
@@ -131,7 +131,7 @@ fn fetch_git(
|
||||
}
|
||||
|
||||
/// `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")
|
||||
.arg("-C")
|
||||
.arg(mirror)
|
||||
@@ -139,7 +139,7 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.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")
|
||||
.args(["-x", "-C"])
|
||||
@@ -148,10 +148,10 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"git archive | tar -x falló (tar exit {:?})",
|
||||
tar_status.code()
|
||||
)));
|
||||
@@ -184,13 +184,13 @@ fn materializar_submodulos(
|
||||
repos_dir: &Path,
|
||||
url_padre: &str,
|
||||
profundidad: usize,
|
||||
) -> hammer_core::Result<()> {
|
||||
) -> takana_core::Result<()> {
|
||||
let gitlinks = gitlinks_de(mirror, commit)?;
|
||||
if gitlinks.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
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} \
|
||||
({commit}): parece un ciclo"
|
||||
)));
|
||||
@@ -200,7 +200,7 @@ fn materializar_submodulos(
|
||||
|
||||
for (sha, ruta) in gitlinks {
|
||||
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"
|
||||
))
|
||||
})?;
|
||||
@@ -222,7 +222,7 @@ fn materializar_submodulos(
|
||||
if run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).is_err() {
|
||||
run_git(&["fetch", "--all", "--tags"], Some(&sub_mirror))?;
|
||||
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"
|
||||
))
|
||||
})?;
|
||||
@@ -246,7 +246,7 @@ fn materializar_submodulos(
|
||||
/// 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.
|
||||
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 mut out = Vec::new();
|
||||
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(
|
||||
mirror: &Path,
|
||||
commit: &str,
|
||||
) -> hammer_core::Result<std::collections::BTreeMap<String, String>> {
|
||||
) -> takana_core::Result<std::collections::BTreeMap<String, String>> {
|
||||
let blob = format!("{commit}:.gitmodules");
|
||||
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.
|
||||
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")
|
||||
.arg("-C")
|
||||
.arg(cwd)
|
||||
@@ -373,9 +373,9 @@ fn salida_git(args: &[&str], cwd: &Path) -> hammer_core::Result<String> {
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"git {args:?} falló (exit {:?})",
|
||||
out.status.code()
|
||||
)));
|
||||
@@ -544,7 +544,7 @@ fn fetch_tarball(
|
||||
url: &str,
|
||||
sha256: &str,
|
||||
work_root: &Path,
|
||||
) -> hammer_core::Result<PathBuf> {
|
||||
) -> takana_core::Result<PathBuf> {
|
||||
let tarballs_dir = work_root.join("tarballs");
|
||||
let sources_dir = work_root.join("sources");
|
||||
std::fs::create_dir_all(&tarballs_dir)?;
|
||||
@@ -595,9 +595,9 @@ fn fetch_tarball(
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"tar -x falló (exit {:?})",
|
||||
status.code()
|
||||
)));
|
||||
@@ -628,7 +628,7 @@ fn fetch_tarball(
|
||||
/// `{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`
|
||||
/// 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) {
|
||||
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
|
||||
@@ -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é).
|
||||
/// 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).
|
||||
fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
||||
fn descargar(url: &str, dst: &Path) -> takana_core::Result<()> {
|
||||
let mut cmd = Command::new("curl");
|
||||
cmd.args(["-fL", "--retry", "3", "--connect-timeout", "30"]);
|
||||
// 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())
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"curl {url} falló (exit {:?})",
|
||||
status.code()
|
||||
)));
|
||||
@@ -671,7 +671,7 @@ fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
|
||||
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");
|
||||
if let Err(e) = descargar(url, &tmp) {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
@@ -685,14 +685,14 @@ fn download_verify(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<(
|
||||
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};
|
||||
let mut f = std::fs::File::open(path)?;
|
||||
let mut hasher = Sha256::new();
|
||||
std::io::copy(&mut f, &mut hasher)?;
|
||||
let got = hex::encode(hasher.finalize());
|
||||
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}",
|
||||
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.
|
||||
///
|
||||
/// 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 mut args: Vec<&str> = vec!["vendor"];
|
||||
if locked {
|
||||
@@ -735,9 +735,9 @@ pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()
|
||||
.args(&args)
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"cargo vendor falló (exit {:?})",
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
// 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.
|
||||
@@ -787,13 +787,13 @@ pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.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, \
|
||||
cf. scripts/bootstrap-devfs.sh / vps-setup.sh)"
|
||||
))
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"{label} falló (exit {:?})",
|
||||
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`.
|
||||
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 {
|
||||
let patch_path = recipe.base_dir.join(p);
|
||||
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}",
|
||||
patch_path.display()
|
||||
))
|
||||
@@ -818,18 +818,18 @@ pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
use std::io::Write;
|
||||
stdin
|
||||
.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
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"patch {} falló (exit {:?})",
|
||||
patch_path.display(),
|
||||
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");
|
||||
if let Some(c) = cwd {
|
||||
cmd.arg("-C").arg(c);
|
||||
@@ -1058,9 +1058,9 @@ fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.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() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"git {args:?} falló (exit {:?})",
|
||||
status.code()
|
||||
)));
|
||||
@@ -70,19 +70,19 @@ impl Harkaq {
|
||||
deps: &[PathBuf],
|
||||
contratos: &[String],
|
||||
dst: &Path,
|
||||
) -> hammer_core::Result<()> {
|
||||
) -> takana_core::Result<()> {
|
||||
let mut ro: Vec<String> = Vec::new();
|
||||
let mut dirs: std::collections::BTreeSet<String> = Default::default();
|
||||
|
||||
for dep in deps {
|
||||
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)?;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Harkaq {
|
||||
|
||||
if let Some(base) = &self.base {
|
||||
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();
|
||||
f.write_all(txt.as_bytes()).ok();
|
||||
@@ -130,7 +130,7 @@ impl Harkaq {
|
||||
deps: &[PathBuf],
|
||||
contratos: &[String],
|
||||
id: &str,
|
||||
) -> hammer_core::Result<Corrida> {
|
||||
) -> takana_core::Result<Corrida> {
|
||||
let tmp = std::env::temp_dir().join(format!("harkaq-{id}-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&tmp).ok();
|
||||
let policy = tmp.join("policy");
|
||||
@@ -146,7 +146,7 @@ impl Harkaq {
|
||||
|
||||
let verdict = tmp.join("verdict.json");
|
||||
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"))
|
||||
.arg("--canary")
|
||||
@@ -157,7 +157,7 @@ impl Harkaq {
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.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`?",
|
||||
self.bin.display()
|
||||
))
|
||||
@@ -199,7 +199,7 @@ fn recorrer(
|
||||
dir: &Path,
|
||||
ro: &mut Vec<String>,
|
||||
dirs: &mut std::collections::BTreeSet<String>,
|
||||
) -> hammer_core::Result<()> {
|
||||
) -> takana_core::Result<()> {
|
||||
let rd = match std::fs::read_dir(dir) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return Ok(()),
|
||||
@@ -15,7 +15,7 @@ use std::os::unix::fs::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_core::LinkMode;
|
||||
use takana_core::LinkMode;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HydratedFile {
|
||||
@@ -61,7 +61,7 @@ pub fn hydrate(
|
||||
target_fhs: &Path,
|
||||
mode: LinkMode,
|
||||
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
|
||||
// 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
|
||||
@@ -71,7 +71,7 @@ pub fn hydrate(
|
||||
.map(|mut e| e.next().is_none())
|
||||
.unwrap_or(true);
|
||||
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: {}",
|
||||
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:
|
||||
// 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| {
|
||||
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();
|
||||
@@ -121,12 +121,12 @@ pub fn hydrate(
|
||||
/// 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`
|
||||
/// (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| {
|
||||
hammer_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
|
||||
takana_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
|
||||
})?;
|
||||
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\
|
||||
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.",
|
||||
@@ -145,7 +145,7 @@ fn walk_and_link(
|
||||
raiz: &Path,
|
||||
spec: Option<&DynamicSpec>,
|
||||
report: &mut HydrateReport,
|
||||
) -> hammer_core::Result<()> {
|
||||
) -> takana_core::Result<()> {
|
||||
for entry in std::fs::read_dir(cur)? {
|
||||
let entry = entry?;
|
||||
let src = entry.path();
|
||||
@@ -183,7 +183,7 @@ fn walk_and_link(
|
||||
report.files.push(HydratedFile { src, dst });
|
||||
} 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.
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"tipo no soportado en {}: {ft:?}",
|
||||
src.display()
|
||||
)));
|
||||
@@ -195,7 +195,7 @@ fn walk_and_link(
|
||||
/// 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
|
||||
/// 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() {
|
||||
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.
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
std::fs::hard_link(src, &tmp).map_err(|e| {
|
||||
hammer_core::Error::Store(format!(
|
||||
takana_core::Error::Store(format!(
|
||||
"hardlink {} → {}: {e}",
|
||||
src.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).
|
||||
if let Err(e) = std::fs::rename(&tmp, dst) {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
return Err(hammer_core::Error::Store(format!(
|
||||
return Err(takana_core::Error::Store(format!(
|
||||
"rename {} → {}: {e}",
|
||||
tmp.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;
|
||||
/// 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;
|
||||
let mut f = std::fs::File::open(path)?;
|
||||
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.
|
||||
fn ensure_patchelf() -> hammer_core::Result<()> {
|
||||
fn ensure_patchelf() -> takana_core::Result<()> {
|
||||
match Command::new("patchelf").arg("--version").output() {
|
||||
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: {}",
|
||||
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}"
|
||||
))),
|
||||
}
|
||||
@@ -251,14 +251,14 @@ fn ensure_patchelf() -> hammer_core::Result<()> {
|
||||
/// 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
|
||||
/// **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() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let tmp = with_suffix(dst, ".hammer-tmp");
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
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) {
|
||||
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) {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
return Err(hammer_core::Error::Store(format!(
|
||||
return Err(takana_core::Error::Store(format!(
|
||||
"rename {} → {}: {e}",
|
||||
tmp.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
|
||||
/// 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");
|
||||
if let Some(interp) = &spec.interpreter {
|
||||
cmd.arg("--set-interpreter").arg(interp);
|
||||
@@ -287,13 +287,13 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
||||
}
|
||||
cmd.arg(file);
|
||||
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}",
|
||||
file.display()
|
||||
))
|
||||
})?;
|
||||
if !out.status.success() {
|
||||
return Err(hammer_core::Error::Other(anyhow::anyhow!(
|
||||
return Err(takana_core::Error::Other(anyhow::anyhow!(
|
||||
"patchelf falló sobre {}: {}",
|
||||
file.display(),
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
@@ -302,7 +302,7 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
|
||||
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;
|
||||
if let Some(parent) = dst.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 _ = std::fs::remove_file(&tmp);
|
||||
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) {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
return Err(hammer_core::Error::Store(format!(
|
||||
return Err(takana_core::Error::Store(format!(
|
||||
"rename {} → {}: {e}",
|
||||
tmp.display(),
|
||||
dst.display()
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::collections::HashMap;
|
||||
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 download;
|
||||
@@ -33,8 +33,8 @@ pub use swm_bridge::build_source_patch;
|
||||
pub fn artifact_hash(
|
||||
recipe: &Recipe,
|
||||
_store: &Store,
|
||||
lab: &hammer_core::lab::LabFingerprint,
|
||||
) -> hammer_core::Result<ArtifactHash> {
|
||||
lab: &takana_core::lab::LabFingerprint,
|
||||
) -> takana_core::Result<ArtifactHash> {
|
||||
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
|
||||
// converge en qtbase/kcoreaddons, alcanzables por decenas de rutas). Sin memoizar, el walk
|
||||
@@ -50,11 +50,11 @@ fn artifact_hash_rec(
|
||||
recipe: &Recipe,
|
||||
stack: &mut Vec<String>,
|
||||
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
||||
lab: &hammer_core::lab::LabFingerprint,
|
||||
) -> hammer_core::Result<ArtifactHash> {
|
||||
lab: &takana_core::lab::LabFingerprint,
|
||||
) -> takana_core::Result<ArtifactHash> {
|
||||
if stack.iter().any(|n| n == &recipe.name) {
|
||||
stack.push(recipe.name.clone());
|
||||
return Err(hammer_core::Error::Recipe(format!(
|
||||
return Err(takana_core::Error::Recipe(format!(
|
||||
"ciclo de dependencias de build: {}",
|
||||
stack.join(" -> ")
|
||||
)));
|
||||
@@ -92,13 +92,13 @@ fn resolve_build_dep_hashes(
|
||||
recipe: &Recipe,
|
||||
stack: &mut Vec<String>,
|
||||
memo: &mut HashMap<PathBuf, ArtifactHash>,
|
||||
lab: &hammer_core::lab::LabFingerprint,
|
||||
) -> hammer_core::Result<Vec<ArtifactHash>> {
|
||||
lab: &takana_core::lab::LabFingerprint,
|
||||
) -> takana_core::Result<Vec<ArtifactHash>> {
|
||||
if recipe.deps.build.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
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; \
|
||||
cárgala con Recipe::load_from_path para resolver el catálogo",
|
||||
recipe.name, recipe.deps.build
|
||||
@@ -116,7 +116,7 @@ fn resolve_build_dep_hashes(
|
||||
continue;
|
||||
}
|
||||
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}",
|
||||
recipe.name,
|
||||
dep_path.display()
|
||||
@@ -138,12 +138,12 @@ fn materialize_build_deps(
|
||||
recipe: &Recipe,
|
||||
cfg: &BuildConfig,
|
||||
store: &Store,
|
||||
) -> hammer_core::Result<Vec<PathBuf>> {
|
||||
) -> takana_core::Result<Vec<PathBuf>> {
|
||||
if recipe.deps.build.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
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; \
|
||||
cárgala con Recipe::load_from_path para resolver el catálogo",
|
||||
recipe.name, recipe.deps.build
|
||||
@@ -153,7 +153,7 @@ fn materialize_build_deps(
|
||||
for dep in &recipe.deps.build {
|
||||
let dep_path = resolve_dep_path(&recipe.base_dir, dep);
|
||||
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}",
|
||||
recipe.name,
|
||||
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
|
||||
/// **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.
|
||||
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 {
|
||||
return Ok(cfg.zig_dir.clone());
|
||||
};
|
||||
let parent = cfg.zig_dir.parent().unwrap_or_else(|| Path::new("."));
|
||||
let dir = parent.join(format!("zig-x86_64-linux-{version}"));
|
||||
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 {} \
|
||||
(bajá ese zig a {}/)",
|
||||
dir.display(),
|
||||
@@ -192,13 +192,13 @@ pub fn build(
|
||||
recipe: &Recipe,
|
||||
cfg: &BuildConfig,
|
||||
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
|
||||
// `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
|
||||
// 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.
|
||||
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)?;
|
||||
if store.has(&h, &recipe.name) {
|
||||
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");
|
||||
sb.run(cmd)?;
|
||||
} else {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
return Err(takana_core::Error::Recipe(
|
||||
"receta sin fase 'compile' y heurística no encontró ninguna".into(),
|
||||
));
|
||||
}
|
||||
@@ -350,7 +350,7 @@ pub fn build(
|
||||
tracing::info!(cmd, "phase: install");
|
||||
sb.run(cmd)?;
|
||||
} else {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
return Err(takana_core::Error::Recipe(
|
||||
"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
|
||||
// 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).
|
||||
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() {
|
||||
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).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EvidenceOutcome {
|
||||
pub kind: hammer_core::EvidenceKind,
|
||||
pub kind: takana_core::EvidenceKind,
|
||||
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:
|
||||
@@ -437,7 +437,7 @@ impl EvidenceReport {
|
||||
}
|
||||
/// 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ó.
|
||||
pub fn max_level_passed(&self) -> Option<hammer_core::EvidenceKind> {
|
||||
pub fn max_level_passed(&self) -> Option<takana_core::EvidenceKind> {
|
||||
self.results
|
||||
.iter()
|
||||
.filter(|r| r.outcome.passed)
|
||||
@@ -460,7 +460,7 @@ pub fn run_evidence(
|
||||
recipe: &Recipe,
|
||||
cfg: &BuildConfig,
|
||||
store: &Store,
|
||||
) -> hammer_core::Result<EvidenceReport> {
|
||||
) -> takana_core::Result<EvidenceReport> {
|
||||
recipe.evidence.validate()?;
|
||||
if recipe.evidence.checks.is_empty() {
|
||||
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
|
||||
/// 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.
|
||||
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 text = std::fs::read_to_string(&manifest)?;
|
||||
// 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
|
||||
/// 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();
|
||||
// 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.
|
||||
@@ -1303,7 +1303,7 @@ commit = "deadbeef"
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
touch(d.path(), "Cargo.toml");
|
||||
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();
|
||||
assert!(c.contains("target-feature=-crt-static"), "{c}");
|
||||
}
|
||||
@@ -1359,7 +1359,7 @@ fn unique_out_dir(
|
||||
work_root: &Path,
|
||||
name: &str,
|
||||
h: &ArtifactHash,
|
||||
) -> hammer_core::Result<PathBuf> {
|
||||
) -> takana_core::Result<PathBuf> {
|
||||
let dir = work_root
|
||||
.join("out")
|
||||
.join(h.store_dir_name(name));
|
||||
@@ -1378,7 +1378,7 @@ pub fn hydrate(
|
||||
target_fhs: &Path,
|
||||
mode: LinkMode,
|
||||
dynamic: Option<&hydrate::DynamicSpec>,
|
||||
) -> hammer_core::Result<HydrateReport> {
|
||||
) -> takana_core::Result<HydrateReport> {
|
||||
let artifact_dir = store.find_by_hash(h.as_str())?;
|
||||
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_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 no_dep = artifact_hash(&load(d.path(), "app_nodep"), &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, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||
// `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.)
|
||||
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(), "lib", "l0", &["base"]);
|
||||
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`.
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -1444,7 +1444,7 @@ mod dep_hash_tests {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let store = store_in(d.path());
|
||||
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("app"), "y la receta que la pide: {err}");
|
||||
}
|
||||
@@ -1455,7 +1455,7 @@ mod dep_hash_tests {
|
||||
let store = store_in(d.path());
|
||||
write_recipe(d.path(), "a", "a0", &["b"]);
|
||||
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("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",
|
||||
)
|
||||
.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}");
|
||||
}
|
||||
|
||||
@@ -1477,8 +1477,8 @@ mod dep_hash_tests {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let store = store_in(d.path());
|
||||
write_recipe(d.path(), "solo", "s0", &[]);
|
||||
let a = artifact_hash(&load(d.path(), "solo"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||
let b = 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, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
|
||||
assert_eq!(a, b, "sin deps el hash es estable");
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex};
|
||||
const TAIL_LINES: usize = 50;
|
||||
|
||||
/// 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`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BuildFailure {
|
||||
@@ -41,9 +41,9 @@ impl std::error::Error for BuildFailure {}
|
||||
impl BuildFailure {
|
||||
/// Recupera un `BuildFailure` de un error de hammer-core, si lo lleva dentro. El bus lo
|
||||
/// 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 {
|
||||
hammer_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
|
||||
takana_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -87,8 +87,8 @@ impl Sandbox {
|
||||
/// 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`]
|
||||
/// líneas; si el exit code es != 0, devuelve un [`BuildFailure`] con esa cola en
|
||||
/// `log_tail`, envuelto en `hammer_core::Error::Other`.
|
||||
pub fn run(&self, cmd: &str) -> hammer_core::Result<()> {
|
||||
/// `log_tail`, envuelto en `takana_core::Error::Other`.
|
||||
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
|
||||
// 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
|
||||
@@ -109,7 +109,7 @@ impl Sandbox {
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.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.
|
||||
let tail = Arc::new(Mutex::new(VecDeque::<String>::with_capacity(TAIL_LINES)));
|
||||
@@ -120,7 +120,7 @@ impl Sandbox {
|
||||
|
||||
let status = child
|
||||
.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.
|
||||
let _ = h_out.join();
|
||||
let _ = h_err.join();
|
||||
@@ -148,7 +148,7 @@ impl Sandbox {
|
||||
"build phase falló (exit {}): {cmd}",
|
||||
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,
|
||||
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
|
||||
/// 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.
|
||||
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);
|
||||
tracing::debug!(?args, "bwrap (capture)");
|
||||
let mut child = Command::new("bwrap")
|
||||
@@ -169,7 +169,7 @@ impl Sandbox {
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.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 err = child.stderr.take().expect("stderr piped");
|
||||
@@ -184,7 +184,7 @@ impl Sandbox {
|
||||
|
||||
let status = child
|
||||
.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 _ = 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.
|
||||
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.
|
||||
// Comprobamos el archivo regular real para no engañarnos.
|
||||
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.display()
|
||||
)));
|
||||
}
|
||||
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_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
|
||||
/// á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.
|
||||
fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
|
||||
fn write_cc_wrappers(rootfs: &Path) -> takana_core::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let bindir = rootfs.join("usr/local/bin");
|
||||
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,
|
||||
// -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++")] {
|
||||
let path = bindir.join(name);
|
||||
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| {
|
||||
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"
|
||||
);
|
||||
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| {
|
||||
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(())
|
||||
@@ -688,7 +688,7 @@ mod tests {
|
||||
reason: "exit 2".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");
|
||||
assert_eq!(recovered.reason, "exit 2");
|
||||
assert_eq!(
|
||||
@@ -699,7 +699,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//! 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 hammer_core::{
|
||||
use takana_core::{
|
||||
swm::{Mutation, SwmBuild},
|
||||
ArtifactHash, Compiler, LinkMode, Recipe, Store,
|
||||
};
|
||||
@@ -27,7 +27,7 @@ pub fn build_source_patch(
|
||||
store: &Store,
|
||||
scratch_root: Option<&Path>,
|
||||
name: Option<&str>,
|
||||
) -> hammer_core::Result<ArtifactHash> {
|
||||
) -> takana_core::Result<ArtifactHash> {
|
||||
let catalog_dir = catalog_dir_for(cfg, scratch_root);
|
||||
// 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
|
||||
@@ -47,7 +47,7 @@ pub fn build_source_patch(
|
||||
.strip_prefix("b3:")
|
||||
.expect("of_inputs siempre devuelve 'b3:'");
|
||||
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}"
|
||||
)));
|
||||
}
|
||||
@@ -72,7 +72,7 @@ pub fn recipe_from_source_patch(
|
||||
mutation: &Mutation,
|
||||
name: Option<&str>,
|
||||
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) =
|
||||
match mutation {
|
||||
Mutation::SourcePatch {
|
||||
@@ -83,33 +83,33 @@ pub fn recipe_from_source_patch(
|
||||
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(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// 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(),
|
||||
commit.as_deref(),
|
||||
tarball.as_deref(),
|
||||
sha256.as_deref(),
|
||||
)
|
||||
.map_err(hammer_core::Error::Recipe)?;
|
||||
.map_err(takana_core::Error::Recipe)?;
|
||||
|
||||
std::fs::create_dir_all(catalog_dir)?;
|
||||
|
||||
// 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.
|
||||
let source_key: &str = match &source_kind {
|
||||
hammer_core::SourceKind::Git { commit, .. } => commit,
|
||||
hammer_core::SourceKind::Tarball { sha256, .. } => sha256,
|
||||
takana_core::SourceKind::Git { commit, .. } => commit,
|
||||
takana_core::SourceKind::Tarball { sha256, .. } => sha256,
|
||||
// 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í
|
||||
// mataría al llamador por una receta mal formada en vez de decírselo.
|
||||
hammer_core::SourceKind::Dir { .. } => {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
takana_core::SourceKind::Dir { .. } => {
|
||||
return Err(takana_core::Error::Recipe(
|
||||
"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)]
|
||||
fn synthesize_recipe(
|
||||
name: &str,
|
||||
source: &hammer_core::SourceKind<'_>,
|
||||
source: &takana_core::SourceKind<'_>,
|
||||
build_cfg: &SwmBuild,
|
||||
strip_components: Option<usize>,
|
||||
deps: &hammer_core::Deps,
|
||||
evidence: &hammer_core::Evidence,
|
||||
slots: &hammer_core::Slots,
|
||||
deps: &takana_core::Deps,
|
||||
evidence: &takana_core::Evidence,
|
||||
slots: &takana_core::Slots,
|
||||
patches: Vec<String>,
|
||||
base_dir: &Path,
|
||||
) -> hammer_core::Result<Recipe> {
|
||||
) -> takana_core::Result<Recipe> {
|
||||
let compiler = parse_compiler(&build_cfg.compiler)?;
|
||||
let link = parse_link(&build_cfg.link)?;
|
||||
// Sección `[source]` y sufijo de versión según el modo de origen. El validador de
|
||||
// `Source::kind` ya garantiza git xor tarball; acá sólo emitimos el TOML correspondiente.
|
||||
let (version_key, source_block) = match source {
|
||||
hammer_core::SourceKind::Git { repo, commit } => (
|
||||
takana_core::SourceKind::Git { repo, commit } => (
|
||||
*commit,
|
||||
format!("repo = \"{repo}\"\ncommit = \"{commit}\"\n"),
|
||||
),
|
||||
hammer_core::SourceKind::Tarball { url, sha256 } => (
|
||||
takana_core::SourceKind::Tarball { url, sha256 } => (
|
||||
*sha256,
|
||||
format!("tarball = \"{url}\"\nsha256 = \"{sha256}\"\n"),
|
||||
),
|
||||
// Ver la nota del `source_key` de arriba: un `.swm` no puede traer un `dir`.
|
||||
hammer_core::SourceKind::Dir { .. } => {
|
||||
return Err(hammer_core::Error::Recipe(
|
||||
takana_core::SourceKind::Dir { .. } => {
|
||||
return Err(takana_core::Error::Recipe(
|
||||
"un .swm no puede llevar un source por directorio".into(),
|
||||
))
|
||||
}
|
||||
@@ -241,22 +241,22 @@ flags = []
|
||||
Ok(recipe)
|
||||
}
|
||||
|
||||
fn parse_compiler(s: &str) -> hammer_core::Result<Compiler> {
|
||||
fn parse_compiler(s: &str) -> takana_core::Result<Compiler> {
|
||||
match s {
|
||||
"zig-cc" => Ok(Compiler::ZigCc),
|
||||
"clang" => Ok(Compiler::Clang),
|
||||
"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)"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_link(s: &str) -> hammer_core::Result<LinkMode> {
|
||||
fn parse_link(s: &str) -> takana_core::Result<LinkMode> {
|
||||
match s {
|
||||
"static" => Ok(LinkMode::Static),
|
||||
"dynamic" => Ok(LinkMode::Dynamic),
|
||||
other => Err(hammer_core::Error::Recipe(format!(
|
||||
other => Err(takana_core::Error::Recipe(format!(
|
||||
"link desconocido: '{other}' (esperado static/dynamic)"
|
||||
))),
|
||||
}
|
||||
@@ -284,9 +284,9 @@ logo = "b3:azul"
|
||||
[slots.requires]
|
||||
"wayland-protocol" = "b3:stock"
|
||||
"#;
|
||||
let recipe = hammer_core::Recipe::from_toml(toml).unwrap();
|
||||
let recipe = takana_core::Recipe::from_toml(toml).unwrap();
|
||||
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.
|
||||
let yaml = swm.to_yaml().unwrap();
|
||||
assert!(yaml.contains("slots"), "el .swm debe transportar slots: {yaml}");
|
||||
@@ -327,15 +327,15 @@ logo = "b3:azul"
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let r = synthesize_recipe(
|
||||
"grep",
|
||||
&hammer_core::SourceKind::Git {
|
||||
&takana_core::SourceKind::Git {
|
||||
repo: "git://example/grep.git",
|
||||
commit: "a1b2c3d4e5f6a7b8c9",
|
||||
},
|
||||
&fake_swm_build(),
|
||||
None,
|
||||
&hammer_core::Deps::default(),
|
||||
&hammer_core::Evidence::default(),
|
||||
&hammer_core::Slots::default(),
|
||||
&takana_core::Deps::default(),
|
||||
&takana_core::Evidence::default(),
|
||||
&takana_core::Slots::default(),
|
||||
vec![],
|
||||
d.path(),
|
||||
)
|
||||
@@ -344,7 +344,7 @@ logo = "b3:azul"
|
||||
assert!(r.version.starts_with("swm-a1b2c3d4"));
|
||||
assert_eq!(r.build.flags, vec!["--enable-foo"]);
|
||||
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!(commit, "a1b2c3d4e5f6a7b8c9");
|
||||
}
|
||||
@@ -357,15 +357,15 @@ logo = "b3:azul"
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let r = synthesize_recipe(
|
||||
"grep",
|
||||
&hammer_core::SourceKind::Tarball {
|
||||
&takana_core::SourceKind::Tarball {
|
||||
url: "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz",
|
||||
sha256: "abcdef0123456789",
|
||||
},
|
||||
&fake_swm_build(),
|
||||
None,
|
||||
&hammer_core::Deps::default(),
|
||||
&hammer_core::Evidence::default(),
|
||||
&hammer_core::Slots::default(),
|
||||
&takana_core::Deps::default(),
|
||||
&takana_core::Evidence::default(),
|
||||
&takana_core::Slots::default(),
|
||||
vec![],
|
||||
d.path(),
|
||||
)
|
||||
@@ -373,7 +373,7 @@ logo = "b3:azul"
|
||||
assert_eq!(r.name, "grep");
|
||||
assert!(r.version.starts_with("swm-abcdef012345"));
|
||||
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!(sha256, "abcdef0123456789");
|
||||
}
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_build::{build, BuildConfig};
|
||||
use hammer_core::{Recipe, Store};
|
||||
use takana_build::{build, BuildConfig};
|
||||
use takana_core::{Recipe, Store};
|
||||
|
||||
const HELLO_C: &str = r#"
|
||||
#include <stdio.h>
|
||||
+2
-2
@@ -11,8 +11,8 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_build::{build, run_hydrate, BuildConfig};
|
||||
use hammer_core::{LinkMode, Recipe, Store};
|
||||
use takana_build::{build, run_hydrate, BuildConfig};
|
||||
use takana_core::{LinkMode, Recipe, Store};
|
||||
|
||||
const RECIPE_TOML: &str = r#"
|
||||
name = "grep"
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
//! Integration test del pipeline completo de Fase 0:
|
||||
//! 1. Inicializa un repo git local con un `hello.c`.
|
||||
//! 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
|
||||
//! se ejecuta dentro del sandbox imprimiendo el output esperado.
|
||||
//!
|
||||
@@ -12,8 +12,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_build::{build, BuildConfig};
|
||||
use hammer_core::{Recipe, Store};
|
||||
use takana_build::{build, BuildConfig};
|
||||
use takana_core::{Recipe, Store};
|
||||
|
||||
const HELLO_C: &str = r#"
|
||||
#include <stdio.h>
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
//! - Genero un tarball en un tempdir con contenido conocido.
|
||||
//! - Calculo su sha256 real con la misma crate (`sha2`) que usa el fetch.
|
||||
//! - 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
|
||||
//! de caché en segunda llamada, strip_components = 0 vs 1.
|
||||
@@ -12,8 +12,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use hammer_build::fetch::fetch;
|
||||
use hammer_core::Recipe;
|
||||
use takana_build::fetch::fetch;
|
||||
use takana_core::Recipe;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// 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 hammer_build::hydrate::hydrate;
|
||||
use hammer_core::LinkMode;
|
||||
use takana_build::hydrate::hydrate;
|
||||
use takana_core::LinkMode;
|
||||
|
||||
/// Artefacto que trae `usr/share/pkg` como symlink a `destino`.
|
||||
fn artefacto_con_symlink(dir: &Path, destino: &Path) {
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-cli"
|
||||
name = "takana-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -23,17 +23,17 @@ path = "src/main.rs"
|
||||
default = []
|
||||
# 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.
|
||||
llm-claude = ["hammer-agent/llm-claude"]
|
||||
llm-claude = ["takana-agent/llm-claude"]
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-build.workspace = true
|
||||
hammer-bootstrap.workspace = true
|
||||
hammer-mirror.workspace = true
|
||||
hammer-upgrade.workspace = true
|
||||
hammer-overlay.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
hammer-agent.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-build.workspace = true
|
||||
takana-bootstrap.workspace = true
|
||||
takana-mirror.workspace = true
|
||||
takana-upgrade.workspace = true
|
||||
takana-overlay.workspace = true
|
||||
takana-journal.workspace = true
|
||||
takana-agent.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
toml.workspace = true
|
||||
@@ -334,7 +334,7 @@ package() {
|
||||
#[test]
|
||||
fn extracts_source_patches_deps_phases() {
|
||||
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.version, "2.5");
|
||||
// tarball con $pkgver expandido:
|
||||
@@ -378,7 +378,7 @@ package() {
|
||||
#[test]
|
||||
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 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!(recipe.source.patches.is_empty());
|
||||
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 clap::Subcommand;
|
||||
use hammer_core::kernel::kconfig::KconfigTree;
|
||||
use hammer_core::kernel::KernelConfig;
|
||||
use takana_core::kernel::kconfig::KconfigTree;
|
||||
use takana_core::kernel::KernelConfig;
|
||||
use serde::Serialize;
|
||||
|
||||
/// 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`.
|
||||
visible: usize,
|
||||
/// `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.
|
||||
leaked_targets: usize,
|
||||
#[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ó.
|
||||
added: Vec<(usize, String)>,
|
||||
/// 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.
|
||||
@@ -520,7 +520,7 @@ fn closure(
|
||||
let on: Vec<String> = cl.iter().filter(|s| cfg.is_on(s)).cloned().collect();
|
||||
let off = cl
|
||||
.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();
|
||||
let absent = cl.iter().filter(|s| cfg.get(s).is_none()).count();
|
||||
Some(AgainstReport {
|
||||
@@ -644,7 +644,7 @@ fn probe(
|
||||
with_hw: bool,
|
||||
json: bool,
|
||||
) -> Result<()> {
|
||||
use hammer_core::kernel::reverse::{BundleState, HwSignal};
|
||||
use takana_core::kernel::reverse::{BundleState, HwSignal};
|
||||
|
||||
let cfg_path = match config {
|
||||
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());
|
||||
}
|
||||
let tree = load_tree(kroot)?;
|
||||
let cat = hammer_core::kernel::Catalog::load(catalog)?;
|
||||
let hw = with_hw.then(hammer_core::kernel::Hardware::probe_local);
|
||||
let rep = hammer_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
|
||||
let cat = takana_core::kernel::Catalog::load(catalog)?;
|
||||
let hw = with_hw.then(takana_core::kernel::Hardware::probe_local);
|
||||
let rep = takana_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
|
||||
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&rep)?);
|
||||
@@ -793,7 +793,7 @@ struct BundleView {
|
||||
closure_len: usize,
|
||||
visible: usize,
|
||||
unknown_symbols: Vec<String>,
|
||||
undeclared_leaks: Vec<hammer_core::kernel::SelectLeak>,
|
||||
undeclared_leaks: Vec<takana_core::kernel::SelectLeak>,
|
||||
accepted_leaks: usize,
|
||||
}
|
||||
|
||||
@@ -811,7 +811,7 @@ struct BundlesReport {
|
||||
struct KnobView {
|
||||
id: String,
|
||||
title: String,
|
||||
side: hammer_core::kernel::Side,
|
||||
side: takana_core::kernel::Side,
|
||||
enable: Vec<String>,
|
||||
disable: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -821,7 +821,7 @@ struct KnobView {
|
||||
|
||||
fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()> {
|
||||
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();
|
||||
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)");
|
||||
for k in &rep.knobs {
|
||||
let lado = match k.side {
|
||||
hammer_core::kernel::Side::Kconfig => "kconfig",
|
||||
hammer_core::kernel::Side::Recipe => "receta ",
|
||||
takana_core::kernel::Side::Kconfig => "kconfig",
|
||||
takana_core::kernel::Side::Recipe => "receta ",
|
||||
};
|
||||
let que = match k.side {
|
||||
hammer_core::kernel::Side::Kconfig => {
|
||||
takana_core::kernel::Side::Kconfig => {
|
||||
let mut v: Vec<String> =
|
||||
k.enable.iter().map(|s| format!("+{s}")).collect();
|
||||
v.extend(k.disable.iter().map(|s| format!("-{s}")));
|
||||
v.join(" ")
|
||||
}
|
||||
hammer_core::kernel::Side::Recipe => {
|
||||
takana_core::kernel::Side::Recipe => {
|
||||
k.recipe_field.clone().unwrap_or_default()
|
||||
}
|
||||
};
|
||||
@@ -988,9 +988,9 @@ fn plan(
|
||||
if bundle_ids.is_empty() && knob_ids.is_empty() {
|
||||
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 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
|
||||
// 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(_) => {}
|
||||
}
|
||||
|
||||
let mut p = hammer_core::kernel::plan::make_plan(
|
||||
let mut p = takana_core::kernel::plan::make_plan(
|
||||
&base,
|
||||
&recipe_path.display().to_string(),
|
||||
&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
|
||||
// «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.
|
||||
let derived = hammer_core::kernel::plan::derive_recipe(&base, &p);
|
||||
match hammer_core::Store::open(Path::new(store))
|
||||
let derived = takana_core::kernel::plan::derive_recipe(&base, &p);
|
||||
match takana_core::Store::open(Path::new(store))
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|s| {
|
||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(s.root());
|
||||
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
|
||||
let cfg = takana_build::BuildConfig::from_env_or_defaults(s.root());
|
||||
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
|
||||
.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()),
|
||||
@@ -1065,7 +1065,7 @@ fn plan(
|
||||
.map(|d| d.display().to_string())
|
||||
.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}"))
|
||||
.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)");
|
||||
for s in &p.symbols {
|
||||
let signo = match s.action {
|
||||
hammer_core::kernel::plan::Action::Enable => "+",
|
||||
hammer_core::kernel::plan::Action::Disable => "-",
|
||||
takana_core::kernel::plan::Action::Enable => "+",
|
||||
takana_core::kernel::plan::Action::Disable => "-",
|
||||
};
|
||||
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<()> {
|
||||
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)
|
||||
.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() {
|
||||
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 {
|
||||
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<()> {
|
||||
let h = hammer_core::kernel::Hardware::probe_local();
|
||||
let h = takana_core::kernel::Hardware::probe_local();
|
||||
let texto = serde_json::to_string_pretty(&h)?;
|
||||
match out {
|
||||
Some(o) => {
|
||||
@@ -1201,7 +1201,7 @@ fn gate(
|
||||
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");
|
||||
}
|
||||
let p: Option<hammer_core::kernel::Plan> = match plan_path {
|
||||
let p: Option<takana_core::kernel::Plan> = match plan_path {
|
||||
None => None,
|
||||
Some(pp) => Some(
|
||||
serde_json::from_str(
|
||||
@@ -1210,9 +1210,9 @@ fn gate(
|
||||
.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 modmap = hammer_core::kernel::ModMap::parse(kroot)
|
||||
let modmap = takana_core::kernel::ModMap::parse(kroot)
|
||||
.with_context(|| format!("leyendo los Makefiles de {}", kroot.display()))?;
|
||||
if modmap.is_empty() {
|
||||
anyhow::bail!(
|
||||
@@ -1223,11 +1223,11 @@ fn gate(
|
||||
}
|
||||
|
||||
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()))?,
|
||||
)
|
||||
.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 {
|
||||
@@ -1242,7 +1242,7 @@ fn gate(
|
||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", c.display());
|
||||
}
|
||||
println!("referente {} ({} símbolos)", base_path.display(), base.len());
|
||||
hammer_core::kernel::gate::run_against_config(
|
||||
takana_core::kernel::gate::run_against_config(
|
||||
&base,
|
||||
&nuevo,
|
||||
&cat,
|
||||
@@ -1253,7 +1253,7 @@ fn gate(
|
||||
Some(&tree),
|
||||
)?
|
||||
}
|
||||
None => hammer_core::kernel::gate::run(
|
||||
None => takana_core::kernel::gate::run(
|
||||
p.as_ref().expect("validado arriba"),
|
||||
&cat,
|
||||
&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");
|
||||
}
|
||||
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:
|
||||
/// **el artefacto se nombra por el campo `name`, no por el fichero**, y en este corpus hay recetas
|
||||
/// 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 {
|
||||
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 {
|
||||
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() {
|
||||
let p = e.path();
|
||||
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 {
|
||||
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
|
||||
/// 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) {
|
||||
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
|
||||
/// (SDD 25: el lab entra en el artefacto).
|
||||
fn dirs_vigentes(
|
||||
c: &hammer_core::kernel::Contract,
|
||||
c: &takana_core::kernel::Contract,
|
||||
dirs: &[PathBuf],
|
||||
store: &str,
|
||||
) -> Result<std::collections::BTreeMap<String, String>> {
|
||||
let st = hammer_core::Store::open(Path::new(store))?;
|
||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(st.root());
|
||||
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
||||
let st = takana_core::Store::open(Path::new(store))?;
|
||||
let cfg = takana_build::BuildConfig::from_env_or_defaults(st.root());
|
||||
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
|
||||
let mut out = std::collections::BTreeMap::new();
|
||||
for t in &c.targets {
|
||||
let Some(r) = receta_de(&t.artifact, dirs) else {
|
||||
@@ -1451,7 +1451,7 @@ fn dirs_vigentes(
|
||||
);
|
||||
continue;
|
||||
};
|
||||
match hammer_build::artifact_hash(&r, &st, &lab) {
|
||||
match takana_build::artifact_hash(&r, &st, &lab) {
|
||||
Ok(h) => {
|
||||
out.insert(t.artifact.clone(), h.store_dir_name(&r.name));
|
||||
}
|
||||
@@ -1464,10 +1464,10 @@ fn dirs_vigentes(
|
||||
#[derive(Serialize)]
|
||||
struct ContractRun {
|
||||
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`.
|
||||
#[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.
|
||||
sin_perfil: Vec<String>,
|
||||
/// 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,
|
||||
json: bool,
|
||||
) -> Result<()> {
|
||||
use hammer_core::kernel::contract::sealed_kernel_configs;
|
||||
use hammer_core::kernel::Contract;
|
||||
use takana_core::kernel::contract::sealed_kernel_configs;
|
||||
use takana_core::kernel::Contract;
|
||||
|
||||
let c = Contract::load(contract)?;
|
||||
|
||||
@@ -1501,7 +1501,7 @@ fn contract_cmd(
|
||||
println!("revisado contra kernel {r}");
|
||||
}
|
||||
println!();
|
||||
let req = hammer_core::kernel::contract::required_by(&c);
|
||||
let req = takana_core::kernel::contract::required_by(&c);
|
||||
for cap in &c.capabilities {
|
||||
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 };
|
||||
@@ -1586,7 +1586,7 @@ fn contract_cmd(
|
||||
};
|
||||
|
||||
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() {
|
||||
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", path.display());
|
||||
}
|
||||
@@ -1634,7 +1634,7 @@ fn contract_cmd(
|
||||
let bloquean = run
|
||||
.reports
|
||||
.iter()
|
||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
||||
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||
.count();
|
||||
if bloquean > 0 {
|
||||
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!();
|
||||
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!(" perfil {} ({} capacidad(es) miradas)", r.profile, r.checked);
|
||||
if let Some(k) = &r.kernel {
|
||||
@@ -1699,13 +1699,13 @@ fn imprimir_contrato(run: &ContractRun) {
|
||||
let malos = run
|
||||
.reports
|
||||
.iter()
|
||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
||||
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||
.count();
|
||||
if !run.superados.is_empty() {
|
||||
let sup_malos = run
|
||||
.superados
|
||||
.iter()
|
||||
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
|
||||
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
|
||||
.count();
|
||||
println!(
|
||||
"· {} 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 toml = to_recipe_toml(&pkg).unwrap();
|
||||
// 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.version, "2.12.1");
|
||||
// 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"
|
||||
}"#;
|
||||
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"]);
|
||||
let inst = recipe.build.phases.install.as_deref().unwrap();
|
||||
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 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!(toml.contains("# buildInputs de nix"), "deja provenance comentada: {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"]
|
||||
}"#;
|
||||
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
|
||||
// se filtran (rustc/cargo son el lab, no deps).
|
||||
assert_eq!(recipe.deps.build, vec!["pcre2"]);
|
||||
@@ -503,7 +503,7 @@ mod tests {
|
||||
}"#;
|
||||
let pkg: NixPkg = serde_json::from_str(json).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.commit.as_deref(),
|
||||
@@ -293,7 +293,7 @@ fn pull(
|
||||
download_to_file(url, &archive)?;
|
||||
|
||||
// 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();
|
||||
})?;
|
||||
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
|
||||
// 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<()> {
|
||||
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}"))?;
|
||||
let art = store.find_by_hash(hash)
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
@@ -8,7 +8,7 @@
|
||||
use std::process::Command;
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use hammer_core::ArtifactHash;
|
||||
use takana_core::ArtifactHash;
|
||||
|
||||
#[test]
|
||||
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 base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
|
||||
use hammer_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
|
||||
use hammer_core::ArtifactHash;
|
||||
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
|
||||
use takana_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
|
||||
use takana_core::ArtifactHash;
|
||||
|
||||
fn write(p: &Path, content: &str) {
|
||||
if let Some(parent) = p.parent() {
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-core"
|
||||
name = "takana-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -41,7 +41,7 @@ pub enum Command {
|
||||
/// Consulta de estado. `what` decide qué campos se usan:
|
||||
/// - `"file"`: usa `path` (metadata del archivo).
|
||||
/// - `"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 {
|
||||
what: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -162,9 +162,9 @@ pub struct Peer {
|
||||
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
|
||||
/// 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)]
|
||||
pub struct EvidenceVerdict {
|
||||
/// 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
|
||||
/// 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.
|
||||
/// 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
|
||||
/// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-journal"
|
||||
name = "takana-journal"
|
||||
version.workspace = true
|
||||
edition.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."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
takana-core.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.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
|
||||
//! `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):
|
||||
//! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables"
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-mirror"
|
||||
name = "takana-mirror"
|
||||
version.workspace = true
|
||||
edition.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."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
takana-core.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use hammer_core::{ArtifactHash, Store};
|
||||
use takana_core::{ArtifactHash, Store};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -28,7 +28,7 @@ pub enum Error {
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("core: {0}")]
|
||||
Core(#[from] hammer_core::Error),
|
||||
Core(#[from] takana_core::Error),
|
||||
#[error(
|
||||
"integridad: el artefacto '{dir}' recibido hashea a {got} pero el índice anuncia {want} — \
|
||||
transferencia corrupta o manipulada; NO se selló"
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-overlay"
|
||||
name = "takana-overlay"
|
||||
version.workspace = true
|
||||
edition.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."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-journal.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -248,7 +248,7 @@ pub fn commit(id: &OverlayId, state_root: &Path) -> Result<CommitReport> {
|
||||
pub fn commit_with_journal(
|
||||
id: &OverlayId,
|
||||
state_root: &Path,
|
||||
journal: &hammer_journal::Journal,
|
||||
journal: &takana_journal::Journal,
|
||||
) -> Result<CommitReport> {
|
||||
commit_inner(id, state_root, Some(journal))
|
||||
}
|
||||
@@ -256,7 +256,7 @@ pub fn commit_with_journal(
|
||||
fn commit_inner(
|
||||
id: &OverlayId,
|
||||
state_root: &Path,
|
||||
journal: Option<&hammer_journal::Journal>,
|
||||
journal: Option<&takana_journal::Journal>,
|
||||
) -> Result<CommitReport> {
|
||||
let base = state_root.join(id.as_str());
|
||||
let state_file = base.join("state.json");
|
||||
@@ -295,11 +295,11 @@ fn commit_inner(
|
||||
}
|
||||
|
||||
fn record_commit_to_journal(
|
||||
j: &hammer_journal::Journal,
|
||||
j: &takana_journal::Journal,
|
||||
id: &OverlayId,
|
||||
report: &CommitReport,
|
||||
) {
|
||||
use hammer_journal::{Actor, MutationEvent, MutationOp, Source};
|
||||
use takana_journal::{Actor, MutationEvent, MutationOp, Source};
|
||||
let actor = Actor {
|
||||
source: Source::HammerCommit {
|
||||
overlay: id.as_str().to_string(),
|
||||
@@ -308,7 +308,7 @@ fn record_commit_to_journal(
|
||||
pid: Some(std::process::id()),
|
||||
uid: Some(unsafe { libc::getuid() }),
|
||||
};
|
||||
let ts = hammer_journal::now_rfc3339();
|
||||
let ts = takana_journal::now_rfc3339();
|
||||
for p in &report.copied {
|
||||
let ev = MutationEvent {
|
||||
ts: ts.clone(),
|
||||
@@ -665,7 +665,7 @@ mod tests {
|
||||
// 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.
|
||||
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 {
|
||||
copied: vec![PathBuf::from("/etc/hosts"), PathBuf::from("/bin/grep")],
|
||||
removed: vec![PathBuf::from("/bin/obsoleto")],
|
||||
@@ -678,13 +678,13 @@ mod tests {
|
||||
.iter()
|
||||
.map(|e| (e.op, e.path.display().to_string()))
|
||||
.collect();
|
||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/etc/hosts".into())));
|
||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/bin/grep".into())));
|
||||
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Delete, "/bin/obsoleto".into())));
|
||||
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/etc/hosts".into())));
|
||||
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/bin/grep".into())));
|
||||
assert!(ops_paths.contains(&(takana_journal::MutationOp::Delete, "/bin/obsoleto".into())));
|
||||
// Todos comparten el mismo source con overlay id.
|
||||
for e in &events {
|
||||
match &e.by.source {
|
||||
hammer_journal::Source::HammerCommit { overlay, .. } => {
|
||||
takana_journal::Source::HammerCommit { overlay, .. } => {
|
||||
assert_eq!(overlay, "1234-1");
|
||||
}
|
||||
other => panic!("source inesperado: {other:?}"),
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
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 {
|
||||
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 ---
|
||||
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!(shadowed_by, vec![id2.clone()]);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-recover"
|
||||
name = "takana-recover"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -7,10 +7,13 @@ authors.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."
|
||||
|
||||
# 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]]
|
||||
name = "hammer-recover"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
hammer-upgrade.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
takana-upgrade.workspace = true
|
||||
takana-journal.workspace = true
|
||||
@@ -34,7 +34,7 @@ fn main() {
|
||||
let state = env_path("HAMMER_RECOVER_STATE", "/var/lib/hammer/upgrades");
|
||||
let journal_dir = env_path("HAMMER_RECOVER_JOURNAL", "/var/lib/hammer/journal");
|
||||
|
||||
match hammer_upgrade::pending(&state) {
|
||||
match takana_upgrade::pending(&state) {
|
||||
Ok(None) => {
|
||||
println!("hammer-recover: sin upgrade interrumpido");
|
||||
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();
|
||||
|
||||
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),
|
||||
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.
|
||||
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),
|
||||
Err(e) => {
|
||||
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),
|
||||
Err(e2) => eprintln!("hammer-recover: rollback de respaldo también falló: {e2}"),
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hammer-upgrade"
|
||||
name = "takana-upgrade"
|
||||
version.workspace = true
|
||||
edition.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."
|
||||
|
||||
[dependencies]
|
||||
hammer-core.workspace = true
|
||||
hammer-journal.workspace = true
|
||||
takana-core.workspace = true
|
||||
takana-journal.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -22,8 +22,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use hammer_core::ArtifactHash;
|
||||
use hammer_journal::Journal;
|
||||
use takana_core::ArtifactHash;
|
||||
use takana_journal::Journal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -3,7 +3,7 @@
|
||||
//! 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
|
||||
//! "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.
|
||||
//!
|
||||
//! ## Modelo de generaciones (inspirado en NixOS, aplicado in-place al FHS)
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use hammer_core::{ArtifactHash, Store};
|
||||
use hammer_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
||||
use takana_core::{ArtifactHash, Store};
|
||||
use takana_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod boot_graph;
|
||||
@@ -48,9 +48,9 @@ pub enum Error {
|
||||
#[error("io: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("core: {0}")]
|
||||
Core(#[from] hammer_core::Error),
|
||||
Core(#[from] takana_core::Error),
|
||||
#[error("journal: {0}")]
|
||||
Journal(#[from] hammer_journal::Error),
|
||||
Journal(#[from] takana_journal::Error),
|
||||
#[error("json: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(
|
||||
@@ -789,7 +789,7 @@ fn journal_change(journal: Option<&Journal>, tree_dir: &str, dst: &Path, op: Cha
|
||||
ChangeOp::Removed => MutationOp::Delete,
|
||||
};
|
||||
let ev = MutationEvent {
|
||||
ts: hammer_journal::now_rfc3339(),
|
||||
ts: takana_journal::now_rfc3339(),
|
||||
op: mop,
|
||||
path: dst.to_path_buf(),
|
||||
by: Actor {
|
||||
@@ -12,7 +12,7 @@ PKG="${1:?uso: alpine-import.sh <pkg> [main|community]}"
|
||||
REPO="${2:-main}"
|
||||
BRANCH="${ALPINE_BRANCH:-master}"
|
||||
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}"
|
||||
|
||||
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
|
||||
# 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.
|
||||
# ⚠ 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
|
||||
# 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
|
||||
|
||||
@@ -21,7 +21,7 @@ REPO="${REPO:-$(pwd)/dist/repo}"
|
||||
STORE="${STORE:-./store}"
|
||||
DISTRO="${DISTRO:-dev}"
|
||||
BUILD_TIMEOUT="${BUILD_TIMEOUT:-90}"
|
||||
HAMMER="cargo run -q -p hammer-cli --"
|
||||
HAMMER="cargo run -q -p takana-cli --"
|
||||
|
||||
rm -rf "$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 '*.png' --exclude '/content*' ./ "root@$ip:/opt/hammer/" >/dev/null 2>&1
|
||||
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 \
|
||||
| grep -oE '^b3:[a-f0-9]{64}' | head -1" 2>/dev/null | tr -d '\r')
|
||||
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
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# ── 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
|
||||
# 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
|
||||
# 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
|
||||
$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)"
|
||||
$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).
|
||||
$SSH "root@$ip" "cat > /etc/systemd/system/harkaq-vol.service <<UNIT
|
||||
[Unit]
|
||||
|
||||
@@ -34,7 +34,7 @@ DRY="${DRY:-0}"
|
||||
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)'
|
||||
|
||||
[ -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=""
|
||||
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 "== 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
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ if [ -x "$HAMMER_MUSL" ]; then
|
||||
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"
|
||||
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
|
||||
# /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.
|
||||
|
||||
@@ -121,7 +121,7 @@ if [ "${INSTALLER:-0}" = 1 ]; then
|
||||
RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover"
|
||||
if [ ! -x "$RECOVER_BIN" ]; then
|
||||
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
|
||||
[ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; }
|
||||
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"
|
||||
if [ ! -x "$HAMMER_BIN" ]; then
|
||||
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
|
||||
[ -x "$HAMMER_BIN" ] || { echo "no pude construir el CLI hammer (target musl?)" >&2; exit 1; }
|
||||
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.
|
||||
#
|
||||
# ── 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
|
||||
# —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.
|
||||
#
|
||||
# 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
|
||||
|
||||
ATTR="${1:?uso: nix-import.sh <attr-de-nixpkgs> (ej: hello, jq, ripgrep)}"
|
||||
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á
|
||||
# a un store local, p.ej. NIX_STORE="local?root=$HOME/.nixstore".
|
||||
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