From 4f73f46ba9ef64392b5ce45969acef8132c83bc7 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 13 Feb 2026 23:44:25 +0000 Subject: [PATCH] first time deps --- Cargo.lock | 1 + .../prometeu-build-pipeline/Cargo.toml | 1 + .../prometeu-build-pipeline/src/cli.rs | 30 +---- .../src/emit_artifacts.rs | 17 +++ .../prometeu-build-pipeline/src/lib.rs | 1 + crates/compiler/prometeu-deps/src/lib.rs | 1 - .../compiler/prometeu-deps/src/model/mod.rs | 1 - .../src/model/resolved_explanation.rs | 4 - .../src/model/resolved_project.rs | 5 +- .../src/workspace/phases/policy.rs | 19 ++- .../src/workspace/phases/run_all.rs | 54 +++++++-- .../src/workspace/phases/stack.rs | 99 +++++++++++++++- .../src/workspace/phases/validate.rs | 110 +++++++++++++++++- 13 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 crates/compiler/prometeu-build-pipeline/src/emit_artifacts.rs delete mode 100644 crates/compiler/prometeu-deps/src/model/resolved_explanation.rs diff --git a/Cargo.lock b/Cargo.lock index 0167cbe3..9fa848bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1883,6 +1883,7 @@ name = "prometeu-build-pipeline" version = "0.1.0" dependencies = [ "anyhow", + "camino", "clap", "prometeu-core", "prometeu-deps", diff --git a/crates/compiler/prometeu-build-pipeline/Cargo.toml b/crates/compiler/prometeu-build-pipeline/Cargo.toml index 9b302b06..d2beffa2 100644 --- a/crates/compiler/prometeu-build-pipeline/Cargo.toml +++ b/crates/compiler/prometeu-build-pipeline/Cargo.toml @@ -21,3 +21,4 @@ clap = { version = "4.5.54", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" anyhow = "1.0.100" +camino = "1.2.2" diff --git a/crates/compiler/prometeu-build-pipeline/src/cli.rs b/crates/compiler/prometeu-build-pipeline/src/cli.rs index a5351ad9..397fb7bb 100644 --- a/crates/compiler/prometeu-build-pipeline/src/cli.rs +++ b/crates/compiler/prometeu-build-pipeline/src/cli.rs @@ -4,6 +4,8 @@ use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use prometeu_deps::{load_sources, resolve_workspace, DepsConfig}; use std::path::{Path, PathBuf}; +use camino::Utf8Path; +use crate::emit_artifacts::{emit_artifacts, EmitOptions}; /// Command line interface for the Prometeu Compiler. #[derive(Parser)] @@ -121,20 +123,18 @@ pub fn run() -> Result<()> { } -fn run_pipeline(cfg: PipelineConfig, project_dir: &Path, explain_deps: bool) -> anyhow::Result { +fn run_pipeline(cfg: PipelineConfig, project_dir: &Path, explain_deps: bool) -> Result { let deps_cfg = DepsConfig { explain: explain_deps, cache_dir: Default::default(), registry_dirs: vec![], }; - let resolved = resolve_workspace(&deps_cfg, project_dir) + let project_dir_path_buf = Utf8Path::from_path(project_dir) + .with_context(|| format!("deps: failed to convert project_dir to Utf8Path: {:?}", project_dir))?; + let resolved = resolve_workspace(&deps_cfg, project_dir_path_buf) .with_context(|| format!("deps: failed to resolve project at {:?}", project_dir))?; - // resolved deve te dar pelo menos: - // - graph - // - stack (deps-first topo order) - let sources = load_sources(&deps_cfg, &resolved) .context("deps: failed to load sources")?; @@ -156,21 +156,3 @@ fn parse_mode(s: &str) -> Result { other => anyhow::bail!("invalid --mode '{}': expected debug|release|test", other), } } - -/// Emission options -struct EmitOptions { - out: Option, - emit_symbols: bool, - emit_disasm: bool, -} - -/// Placeholder emit function. -/// In hard reset, this can be a no-op until backend exists. -fn emit_artifacts(_opts: &EmitOptions, _outp: &PipelineOutput) -> Result<()> { - // Later: - // - decide output dir (opts.out or default) - // - write .pbc / program image - // - write symbols.json (if exists) - // - write disasm (if exists) - Ok(()) -} diff --git a/crates/compiler/prometeu-build-pipeline/src/emit_artifacts.rs b/crates/compiler/prometeu-build-pipeline/src/emit_artifacts.rs new file mode 100644 index 00000000..ce6f601f --- /dev/null +++ b/crates/compiler/prometeu-build-pipeline/src/emit_artifacts.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; +use crate::PipelineOutput; + +pub struct EmitOptions { + pub(crate) out: Option, + pub(crate) emit_symbols: bool, + pub(crate) emit_disasm: bool, +} + +pub fn emit_artifacts(_opts: &EmitOptions, _outp: &PipelineOutput) -> anyhow::Result<()> { + // Later: + // - decide output dir (opts.out or default) + // - write .pbc / program image + // - write symbols.json (if exists) + // - write disasm (if exists) + Ok(()) +} \ No newline at end of file diff --git a/crates/compiler/prometeu-build-pipeline/src/lib.rs b/crates/compiler/prometeu-build-pipeline/src/lib.rs index cbe3a7eb..9f0cb56b 100644 --- a/crates/compiler/prometeu-build-pipeline/src/lib.rs +++ b/crates/compiler/prometeu-build-pipeline/src/lib.rs @@ -3,6 +3,7 @@ pub mod config; pub mod ctx; pub mod pipeline; pub mod phases; +mod emit_artifacts; pub use config::*; pub use ctx::*; diff --git a/crates/compiler/prometeu-deps/src/lib.rs b/crates/compiler/prometeu-deps/src/lib.rs index 970efa2e..06b40cfb 100644 --- a/crates/compiler/prometeu-deps/src/lib.rs +++ b/crates/compiler/prometeu-deps/src/lib.rs @@ -7,7 +7,6 @@ pub use load_sources::load_sources; pub use model::manifest::*; pub use model::resolved_project::ResolvedWorkspace; -pub use model::resolved_explanation::ResolveExplanation; pub use model::deps_config::DepsConfig; pub use model::project_descriptor::ProjectDescriptor; pub use model::build_stack::BuildStack; diff --git a/crates/compiler/prometeu-deps/src/model/mod.rs b/crates/compiler/prometeu-deps/src/model/mod.rs index 6bdf6d56..d75c23c6 100644 --- a/crates/compiler/prometeu-deps/src/model/mod.rs +++ b/crates/compiler/prometeu-deps/src/model/mod.rs @@ -7,6 +7,5 @@ pub mod project_sources; pub mod loaded_file; pub mod cache_blobs; pub mod resolved_project; -pub mod resolved_explanation; pub mod cache_plan; pub mod manifest; \ No newline at end of file diff --git a/crates/compiler/prometeu-deps/src/model/resolved_explanation.rs b/crates/compiler/prometeu-deps/src/model/resolved_explanation.rs deleted file mode 100644 index bd3cd592..00000000 --- a/crates/compiler/prometeu-deps/src/model/resolved_explanation.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(Debug, Clone)] -pub struct ResolveExplanation { - -} \ No newline at end of file diff --git a/crates/compiler/prometeu-deps/src/model/resolved_project.rs b/crates/compiler/prometeu-deps/src/model/resolved_project.rs index 9c43016d..a9af9f3d 100644 --- a/crates/compiler/prometeu-deps/src/model/resolved_project.rs +++ b/crates/compiler/prometeu-deps/src/model/resolved_project.rs @@ -1,8 +1,9 @@ -use crate::{BuildStack, ResolvedGraph, ResolveExplanation, CachePlan}; +use prometeu_core::ProjectId; +use crate::{BuildStack, ResolvedGraph}; #[derive(Debug, Clone)] pub struct ResolvedWorkspace { + pub project_id: ProjectId, pub graph: ResolvedGraph, pub stack: BuildStack, - pub explain: Option } diff --git a/crates/compiler/prometeu-deps/src/workspace/phases/policy.rs b/crates/compiler/prometeu-deps/src/workspace/phases/policy.rs index d73419f6..f2340ef7 100644 --- a/crates/compiler/prometeu-deps/src/workspace/phases/policy.rs +++ b/crates/compiler/prometeu-deps/src/workspace/phases/policy.rs @@ -1,6 +1,17 @@ -use crate::DepsConfig; +use anyhow::{bail, Result}; + use crate::workspace::phases::state::ResolverState; -pub(crate) fn policy(p0: &DepsConfig, p1: &mut ResolverState) -> _ { - todo!() -} \ No newline at end of file +pub fn policy(_cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result<()> { + for node in &state.nodes { + if node.source_policy.extensions.is_empty() { + bail!( + "deps: project {}@{} has empty source_policy.extensions (language={})", + node.name, + node.version, + node.language_id + ); + } + } + Ok(()) +} diff --git a/crates/compiler/prometeu-deps/src/workspace/phases/run_all.rs b/crates/compiler/prometeu-deps/src/workspace/phases/run_all.rs index ae2e3781..62d71d6b 100644 --- a/crates/compiler/prometeu-deps/src/workspace/phases/run_all.rs +++ b/crates/compiler/prometeu-deps/src/workspace/phases/run_all.rs @@ -1,14 +1,50 @@ +use anyhow::{Context, Result}; use camino::Utf8Path; -use crate::{DepsConfig, ResolvedWorkspace}; + +use crate::{BuildStack, DepsConfig, ProjectDescriptor, ResolvedGraph, ResolvedWorkspace}; use crate::workspace::host::FsHost; use crate::workspace::phases::{discover, localize, materialize, policy, stack, state, validate}; pub fn run_all(cfg: &DepsConfig, fs_host: &FsHost, root_dir: &Utf8Path) -> Result { - let mut state = state::seed(cfg, root_dir)?; - discover::discover(cfg, fs_host, &mut state)?; - materialize::materialize(cfg, &mut state)?; - localize::localize(cfg, &mut state)?; - validate::validate(cfg, &mut state)?; - policy::policy(cfg, &mut state)?; - stack::stack(cfg, &mut state) -} \ No newline at end of file + let mut st = state::seed(cfg, root_dir)?; + + discover::discover(cfg, fs_host, &mut st)?; + materialize::materialize(cfg, &mut st)?; + localize::localize(cfg, &mut st)?; + + validate::validate(cfg, &st)?; + policy::policy(cfg, &mut st)?; + + let build_stack: BuildStack = stack::stack(cfg, &mut st)?; + + let root = st + .root + .context("deps: internal error: root ProjectId not set")?; + + // Build the arena expected by ResolvedGraph: index == ProjectId.0 + // materialize already assigns ProjectId(idx), so st.nodes order is stable. + let mut projects: Vec = Vec::with_capacity(st.nodes.len()); + for n in &st.nodes { + projects.push(ProjectDescriptor { + project_id: n.id, + name: n.name.clone(), + version: n.version.clone(), + project_dir: n.dir.clone(), + source_roots: n.source_roots.clone(), + language_id: n.language_id.clone(), + source_policy: n.source_policy.clone(), + }); + } + + let graph = ResolvedGraph { + root, + projects, + edges: st.edges, + }; + + Ok(ResolvedWorkspace { + project_id: root, + graph, + stack: build_stack, + }) +} diff --git a/crates/compiler/prometeu-deps/src/workspace/phases/stack.rs b/crates/compiler/prometeu-deps/src/workspace/phases/stack.rs index 6436577f..34e5e49a 100644 --- a/crates/compiler/prometeu-deps/src/workspace/phases/stack.rs +++ b/crates/compiler/prometeu-deps/src/workspace/phases/stack.rs @@ -1,6 +1,97 @@ -use crate::{DepsConfig, ResolvedWorkspace}; +use anyhow::{Context, Result}; +use prometeu_core::ProjectId; +use std::collections::VecDeque; +use crate::BuildStack; use crate::workspace::phases::state::ResolverState; -pub(crate) fn stack(cfg: &DepsConfig, state: &mut ResolverState) -> Result { - todo!() -} \ No newline at end of file +/// Phase: BuildStack (deps-first topo order). +/// +/// Output: +/// - state.stack: Vec where deps appear before dependents. +/// +/// Determinism: +/// - ties are resolved by ProjectId order (stable across runs if discovery is stable). +pub fn stack(cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result { + let n = state.nodes.len(); + let _root = state.root.context("deps: internal error: root ProjectId not set")?; + + // Build indegree + let mut indeg = vec![0usize; n]; + for outs in &state.edges { + for &to in outs { + indeg[to.0 as usize] += 1; + } + } + + // Deterministic queue: push in ProjectId order + let mut q = VecDeque::new(); + for i in 0..n { + if indeg[i] == 0 { + q.push_back(i); + } + } + + let mut order: Vec = Vec::with_capacity(n); + + while let Some(i) = q.pop_front() { + order.push(ProjectId(i as u32)); + + // Ensure deterministic traversal of outgoing edges too + // (your localize already sort/dedup edges, but this doesn't hurt) + for &to in &state.edges[i] { + let j = to.0 as usize; + indeg[j] -= 1; + if indeg[j] == 0 { + // Deterministic insert: keep queue ordered by ProjectId + // Simple O(n) insertion is fine for now. + insert_sorted_by_id(&mut q, j); + } + } + } + + // If validate ran, this should already be cycle-free; still keep a guard. + if order.len() != n { + anyhow::bail!( + "deps: internal error: stack generation did not visit all nodes ({} of {})", + order.len(), + n + ); + } + + if cfg.explain { + eprintln!("[deps][stack] build order:"); + for id in &order { + let node = &state.nodes[id.0 as usize]; + eprintln!(" - {:?} {}@{} dir={:?}", id, node.name, node.version, node.dir); + } + } + + Ok(BuildStack { + projects: order, + }) +} + +/// Insert node index `i` into queue `q` keeping it sorted by ProjectId (index). +fn insert_sorted_by_id(q: &mut VecDeque, i: usize) { + // Common fast path: append if >= last + if let Some(&last) = q.back() { + if i >= last { + q.push_back(i); + return; + } + } + + // Otherwise find insertion point + let mut pos = 0usize; + for &v in q.iter() { + if i < v { + break; + } + pos += 1; + } + + // VecDeque has no insert, so rebuild (small sizes OK for hard reset) + let mut tmp: Vec = q.drain(..).collect(); + tmp.insert(pos, i); + *q = VecDeque::from(tmp); +} diff --git a/crates/compiler/prometeu-deps/src/workspace/phases/validate.rs b/crates/compiler/prometeu-deps/src/workspace/phases/validate.rs index e90da002..b49a61ff 100644 --- a/crates/compiler/prometeu-deps/src/workspace/phases/validate.rs +++ b/crates/compiler/prometeu-deps/src/workspace/phases/validate.rs @@ -1,6 +1,108 @@ -use crate::DepsConfig; +use anyhow::{bail, Context, Result}; +use prometeu_core::ProjectId; +use std::collections::{HashMap, VecDeque}; + use crate::workspace::phases::state::ResolverState; -pub(crate) fn validate(p0: &DepsConfig, p1: &mut ResolverState) -> _ { - todo!() -} \ No newline at end of file +/// Phase: Validate workspace graph & invariants (v0). +/// +/// Checks: +/// - root present +/// - edges are in-range +/// - no cycles +/// - no version conflicts for same project name +pub fn validate(cfg: &crate::DepsConfig, state: &ResolverState) -> Result<()> { + // 1) root present + let root = state.root.context("deps: internal error: root ProjectId not set")?; + if cfg.explain { + eprintln!("[deps][validate] root={:?}", root); + } + + // 2) edges sanity + let n = state.nodes.len(); + for (from_idx, outs) in state.edges.iter().enumerate() { + for &to in outs { + let to_idx = to.0 as usize; + if to_idx >= n { + bail!( + "deps: invalid edge: from {:?} -> {:?} (to out of range; nodes={})", + ProjectId(from_idx as u32), + to, + n + ); + } + } + } + + // 3) version conflicts by name + // name -> (version -> ProjectId) + let mut by_name: HashMap<&str, HashMap<&str, ProjectId>> = HashMap::new(); + for node in &state.nodes { + let vmap = by_name.entry(node.name.as_str()).or_default(); + vmap.entry(node.version.as_str()).or_insert(node.id); + } + for (name, versions) in &by_name { + if versions.len() > 1 { + // create deterministic message + let mut vs: Vec<(&str, ProjectId)> = versions.iter().map(|(v, id)| (*v, *id)).collect(); + vs.sort_by(|a, b| a.0.cmp(b.0)); + + let mut msg = format!("deps: version conflict for project '{}':", name); + for (v, id) in vs { + let dir = &state.nodes[id.0 as usize].dir; + msg.push_str(&format!("\n - {} at {:?} (id={:?})", v, dir, id)); + } + bail!(msg); + } + } + + // 4) cycle detection (Kahn + leftover nodes) + // Build indegree + let mut indeg = vec![0usize; n]; + for outs in &state.edges { + for &to in outs { + indeg[to.0 as usize] += 1; + } + } + + let mut q = VecDeque::new(); + for i in 0..n { + if indeg[i] == 0 { + q.push_back(i); + } + } + + let mut visited = 0usize; + while let Some(i) = q.pop_front() { + visited += 1; + for &to in &state.edges[i] { + let j = to.0 as usize; + indeg[j] -= 1; + if indeg[j] == 0 { + q.push_back(j); + } + } + } + + if visited != n { + // Nodes with indeg>0 are part of cycles (or downstream of them) + let mut cyclic: Vec = Vec::new(); + for i in 0..n { + if indeg[i] > 0 { + cyclic.push(ProjectId(i as u32)); + } + } + + // Deterministic error output + cyclic.sort_by_key(|id| id.0); + + let mut msg = "deps: dependency cycle detected among:".to_string(); + for id in cyclic { + let node = &state.nodes[id.0 as usize]; + msg.push_str(&format!("\n - {:?} {}@{} dir={:?}", id, node.name, node.version, node.dir)); + } + bail!(msg); + } + + Ok(()) +}