first time deps
This commit is contained in:
parent
f28d7515b8
commit
4f73f46ba9
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1883,6 +1883,7 @@ name = "prometeu-build-pipeline"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"clap",
|
||||
"prometeu-core",
|
||||
"prometeu-deps",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
@ -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::*;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -1,4 +0,0 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolveExplanation {
|
||||
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user