first time deps

This commit is contained in:
bQUARKz 2026-02-13 23:44:25 +00:00
parent f28d7515b8
commit 4f73f46ba9
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
13 changed files with 290 additions and 53 deletions

1
Cargo.lock generated
View File

@ -1883,6 +1883,7 @@ name = "prometeu-build-pipeline"
version = "0.1.0"
dependencies = [
"anyhow",
"camino",
"clap",
"prometeu-core",
"prometeu-deps",

View File

@ -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"

View File

@ -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<PipelineOutput> {
fn run_pipeline(cfg: PipelineConfig, project_dir: &Path, explain_deps: bool) -> Result<PipelineOutput> {
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<BuildMode> {
other => anyhow::bail!("invalid --mode '{}': expected debug|release|test", other),
}
}
/// Emission options
struct EmitOptions {
out: Option<PathBuf>,
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(())
}

View File

@ -0,0 +1,17 @@
use std::path::PathBuf;
use crate::PipelineOutput;
pub struct EmitOptions {
pub(crate) out: Option<PathBuf>,
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(())
}

View File

@ -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::*;

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +0,0 @@
#[derive(Debug, Clone)]
pub struct ResolveExplanation {
}

View File

@ -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<ResolveExplanation>
}

View File

@ -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!()
}
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(())
}

View File

@ -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<ResolvedWorkspace> {
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)
}
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<ProjectDescriptor> = 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,
})
}

View File

@ -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<ResolvedWorkspace, _> {
todo!()
}
/// Phase: BuildStack (deps-first topo order).
///
/// Output:
/// - state.stack: Vec<ProjectId> 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<BuildStack> {
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<ProjectId> = 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<usize>, 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<usize> = q.drain(..).collect();
tmp.insert(pos, i);
*q = VecDeque::from(tmp);
}

View File

@ -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!()
}
/// 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<ProjectId> = 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(())
}