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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"camino",
|
||||||
"clap",
|
"clap",
|
||||||
"prometeu-core",
|
"prometeu-core",
|
||||||
"prometeu-deps",
|
"prometeu-deps",
|
||||||
|
|||||||
@ -21,3 +21,4 @@ clap = { version = "4.5.54", features = ["derive"] }
|
|||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
|
camino = "1.2.2"
|
||||||
|
|||||||
@ -4,6 +4,8 @@ use anyhow::{Context, Result};
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use prometeu_deps::{load_sources, resolve_workspace, DepsConfig};
|
use prometeu_deps::{load_sources, resolve_workspace, DepsConfig};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use camino::Utf8Path;
|
||||||
|
use crate::emit_artifacts::{emit_artifacts, EmitOptions};
|
||||||
|
|
||||||
/// Command line interface for the Prometeu Compiler.
|
/// Command line interface for the Prometeu Compiler.
|
||||||
#[derive(Parser)]
|
#[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 {
|
let deps_cfg = DepsConfig {
|
||||||
explain: explain_deps,
|
explain: explain_deps,
|
||||||
cache_dir: Default::default(),
|
cache_dir: Default::default(),
|
||||||
registry_dirs: vec![],
|
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))?;
|
.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)
|
let sources = load_sources(&deps_cfg, &resolved)
|
||||||
.context("deps: failed to load sources")?;
|
.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),
|
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 ctx;
|
||||||
pub mod pipeline;
|
pub mod pipeline;
|
||||||
pub mod phases;
|
pub mod phases;
|
||||||
|
mod emit_artifacts;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use ctx::*;
|
pub use ctx::*;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ pub use load_sources::load_sources;
|
|||||||
|
|
||||||
pub use model::manifest::*;
|
pub use model::manifest::*;
|
||||||
pub use model::resolved_project::ResolvedWorkspace;
|
pub use model::resolved_project::ResolvedWorkspace;
|
||||||
pub use model::resolved_explanation::ResolveExplanation;
|
|
||||||
pub use model::deps_config::DepsConfig;
|
pub use model::deps_config::DepsConfig;
|
||||||
pub use model::project_descriptor::ProjectDescriptor;
|
pub use model::project_descriptor::ProjectDescriptor;
|
||||||
pub use model::build_stack::BuildStack;
|
pub use model::build_stack::BuildStack;
|
||||||
|
|||||||
@ -7,6 +7,5 @@ pub mod project_sources;
|
|||||||
pub mod loaded_file;
|
pub mod loaded_file;
|
||||||
pub mod cache_blobs;
|
pub mod cache_blobs;
|
||||||
pub mod resolved_project;
|
pub mod resolved_project;
|
||||||
pub mod resolved_explanation;
|
|
||||||
pub mod cache_plan;
|
pub mod cache_plan;
|
||||||
pub mod manifest;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ResolvedWorkspace {
|
pub struct ResolvedWorkspace {
|
||||||
|
pub project_id: ProjectId,
|
||||||
pub graph: ResolvedGraph,
|
pub graph: ResolvedGraph,
|
||||||
pub stack: BuildStack,
|
pub stack: BuildStack,
|
||||||
pub explain: Option<ResolveExplanation>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
use crate::DepsConfig;
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
use crate::workspace::phases::state::ResolverState;
|
use crate::workspace::phases::state::ResolverState;
|
||||||
|
|
||||||
pub(crate) fn policy(p0: &DepsConfig, p1: &mut ResolverState) -> _ {
|
pub fn policy(_cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result<()> {
|
||||||
todo!()
|
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 camino::Utf8Path;
|
||||||
use crate::{DepsConfig, ResolvedWorkspace};
|
|
||||||
|
use crate::{BuildStack, DepsConfig, ProjectDescriptor, ResolvedGraph, ResolvedWorkspace};
|
||||||
use crate::workspace::host::FsHost;
|
use crate::workspace::host::FsHost;
|
||||||
use crate::workspace::phases::{discover, localize, materialize, policy, stack, state, validate};
|
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> {
|
pub fn run_all(cfg: &DepsConfig, fs_host: &FsHost, root_dir: &Utf8Path) -> Result<ResolvedWorkspace> {
|
||||||
let mut state = state::seed(cfg, root_dir)?;
|
let mut st = state::seed(cfg, root_dir)?;
|
||||||
discover::discover(cfg, fs_host, &mut state)?;
|
|
||||||
materialize::materialize(cfg, &mut state)?;
|
discover::discover(cfg, fs_host, &mut st)?;
|
||||||
localize::localize(cfg, &mut state)?;
|
materialize::materialize(cfg, &mut st)?;
|
||||||
validate::validate(cfg, &mut state)?;
|
localize::localize(cfg, &mut st)?;
|
||||||
policy::policy(cfg, &mut state)?;
|
|
||||||
stack::stack(cfg, &mut state)
|
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;
|
use crate::workspace::phases::state::ResolverState;
|
||||||
|
|
||||||
pub(crate) fn stack(cfg: &DepsConfig, state: &mut ResolverState) -> Result<ResolvedWorkspace, _> {
|
/// Phase: BuildStack (deps-first topo order).
|
||||||
todo!()
|
///
|
||||||
}
|
/// 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;
|
use crate::workspace::phases::state::ResolverState;
|
||||||
|
|
||||||
pub(crate) fn validate(p0: &DepsConfig, p1: &mut ResolverState) -> _ {
|
/// Phase: Validate workspace graph & invariants (v0).
|
||||||
todo!()
|
///
|
||||||
}
|
/// 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