pr 57
This commit is contained in:
parent
ad1650592d
commit
6330c6cbae
1
crates/prometeu-compiler/src/building/mod.rs
Normal file
1
crates/prometeu-compiler/src/building/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod plan;
|
||||
247
crates/prometeu-compiler/src/building/plan.rs
Normal file
247
crates/prometeu-compiler/src/building/plan.rs
Normal file
@ -0,0 +1,247 @@
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::deps::resolver::{ProjectId, ResolvedGraph};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum BuildTarget {
|
||||
Main,
|
||||
Test,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildStep {
|
||||
pub project_id: ProjectId,
|
||||
pub project_dir: PathBuf,
|
||||
pub target: BuildTarget,
|
||||
pub sources: Vec<PathBuf>,
|
||||
pub deps: BTreeMap<String, ProjectId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildPlan {
|
||||
pub steps: Vec<BuildStep>,
|
||||
}
|
||||
|
||||
impl BuildPlan {
|
||||
pub fn from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Self {
|
||||
let mut steps = Vec::new();
|
||||
let sorted_ids = topological_sort(graph);
|
||||
|
||||
for id in sorted_ids {
|
||||
if let Some(node) = graph.nodes.get(&id) {
|
||||
let sources_list: Vec<PathBuf> = match target {
|
||||
BuildTarget::Main => node.sources.files.clone(),
|
||||
BuildTarget::Test => node.sources.test_files.clone(),
|
||||
};
|
||||
|
||||
// Normalize to relative paths and sort lexicographically
|
||||
let mut sources: Vec<PathBuf> = sources_list
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
p.strip_prefix(&node.path)
|
||||
.map(|rp| rp.to_path_buf())
|
||||
.unwrap_or(p)
|
||||
})
|
||||
.collect();
|
||||
sources.sort();
|
||||
|
||||
let mut deps = BTreeMap::new();
|
||||
if let Some(edges) = graph.edges.get(&id) {
|
||||
for edge in edges {
|
||||
deps.insert(edge.alias.clone(), edge.to.clone());
|
||||
}
|
||||
}
|
||||
|
||||
steps.push(BuildStep {
|
||||
project_id: id.clone(),
|
||||
project_dir: node.path.clone(),
|
||||
target,
|
||||
sources,
|
||||
deps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Self { steps }
|
||||
}
|
||||
}
|
||||
|
||||
fn topological_sort(graph: &ResolvedGraph) -> Vec<ProjectId> {
|
||||
let mut in_degree = HashMap::new();
|
||||
let mut adj = HashMap::new();
|
||||
|
||||
for id in graph.nodes.keys() {
|
||||
in_degree.insert(id.clone(), 0);
|
||||
adj.insert(id.clone(), Vec::new());
|
||||
}
|
||||
|
||||
for (from, edges) in &graph.edges {
|
||||
for edge in edges {
|
||||
// from depends on edge.to
|
||||
// so edge.to must be built BEFORE from
|
||||
// edge.to -> from
|
||||
adj.get_mut(&edge.to).unwrap().push(from.clone());
|
||||
*in_degree.get_mut(from).unwrap() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut ready: std::collections::BinaryHeap<ReverseProjectId> = graph.nodes.keys()
|
||||
.filter(|id| *in_degree.get(id).unwrap() == 0)
|
||||
.map(|id| ReverseProjectId(id.clone()))
|
||||
.collect();
|
||||
|
||||
let mut result = Vec::new();
|
||||
while let Some(ReverseProjectId(u)) = ready.pop() {
|
||||
result.push(u.clone());
|
||||
|
||||
if let Some(neighbors) = adj.get(&u) {
|
||||
for v in neighbors {
|
||||
let degree = in_degree.get_mut(v).unwrap();
|
||||
*degree -= 1;
|
||||
if *degree == 0 {
|
||||
ready.push(ReverseProjectId(v.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct ReverseProjectId(ProjectId);
|
||||
|
||||
impl Ord for ReverseProjectId {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
// BinaryHeap is a max-heap. We want min-heap for lexicographic order.
|
||||
// So we reverse the comparison.
|
||||
other.0.name.cmp(&self.0.name)
|
||||
.then(other.0.version.cmp(&self.0.version))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ReverseProjectId {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::deps::resolver::{ProjectId, ResolvedNode, ResolvedEdge, ResolvedGraph};
|
||||
use crate::sources::ProjectSources;
|
||||
use crate::manifest::Manifest;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn mock_node(name: &str, version: &str) -> ResolvedNode {
|
||||
ResolvedNode {
|
||||
id: ProjectId { name: name.to_string(), version: version.to_string() },
|
||||
path: PathBuf::from(format!("/{}", name)),
|
||||
manifest: Manifest {
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
kind: crate::manifest::ManifestKind::Lib,
|
||||
dependencies: HashMap::new(),
|
||||
},
|
||||
sources: ProjectSources {
|
||||
main: None,
|
||||
files: vec![PathBuf::from("b.pbs"), PathBuf::from("a.pbs")],
|
||||
test_files: vec![PathBuf::from("test_b.pbs"), PathBuf::from("test_a.pbs")],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topo_sort_stability() {
|
||||
let mut graph = ResolvedGraph::default();
|
||||
|
||||
let a = mock_node("a", "1.0.0");
|
||||
let b = mock_node("b", "1.0.0");
|
||||
let c = mock_node("c", "1.0.0");
|
||||
|
||||
graph.nodes.insert(a.id.clone(), a);
|
||||
graph.nodes.insert(b.id.clone(), b);
|
||||
graph.nodes.insert(c.id.clone(), c);
|
||||
|
||||
// No edges, should be alphabetical: a, b, c
|
||||
let plan = BuildPlan::from_graph(&graph, BuildTarget::Main);
|
||||
assert_eq!(plan.steps[0].project_id.name, "a");
|
||||
assert_eq!(plan.steps[1].project_id.name, "b");
|
||||
assert_eq!(plan.steps[2].project_id.name, "c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topo_sort_dependencies() {
|
||||
let mut graph = ResolvedGraph::default();
|
||||
|
||||
let a = mock_node("a", "1.0.0");
|
||||
let b = mock_node("b", "1.0.0");
|
||||
let c = mock_node("c", "1.0.0");
|
||||
|
||||
graph.nodes.insert(a.id.clone(), a.clone());
|
||||
graph.nodes.insert(b.id.clone(), b.clone());
|
||||
graph.nodes.insert(c.id.clone(), c.clone());
|
||||
|
||||
// c depends on b, b depends on a
|
||||
// Sort should be: a, b, c
|
||||
graph.edges.insert(c.id.clone(), vec![ResolvedEdge { alias: "b_alias".to_string(), to: b.id.clone() }]);
|
||||
graph.edges.insert(b.id.clone(), vec![ResolvedEdge { alias: "a_alias".to_string(), to: a.id.clone() }]);
|
||||
|
||||
let plan = BuildPlan::from_graph(&graph, BuildTarget::Main);
|
||||
assert_eq!(plan.steps.len(), 3);
|
||||
assert_eq!(plan.steps[0].project_id.name, "a");
|
||||
assert_eq!(plan.steps[1].project_id.name, "b");
|
||||
assert_eq!(plan.steps[2].project_id.name, "c");
|
||||
|
||||
assert_eq!(plan.steps[2].deps.get("b_alias").unwrap(), &b.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topo_sort_complex() {
|
||||
let mut graph = ResolvedGraph::default();
|
||||
|
||||
// d -> b, c
|
||||
// b -> a
|
||||
// c -> a
|
||||
// a
|
||||
// Valid sorts: a, b, c, d OR a, c, b, d
|
||||
// Lexicographic rule says b before c. So a, b, c, d.
|
||||
|
||||
let a = mock_node("a", "1.0.0");
|
||||
let b = mock_node("b", "1.0.0");
|
||||
let c = mock_node("c", "1.0.0");
|
||||
let d = mock_node("d", "1.0.0");
|
||||
|
||||
graph.nodes.insert(a.id.clone(), a.clone());
|
||||
graph.nodes.insert(b.id.clone(), b.clone());
|
||||
graph.nodes.insert(c.id.clone(), c.clone());
|
||||
graph.nodes.insert(d.id.clone(), d.clone());
|
||||
|
||||
graph.edges.insert(d.id.clone(), vec![
|
||||
ResolvedEdge { alias: "b".to_string(), to: b.id.clone() },
|
||||
ResolvedEdge { alias: "c".to_string(), to: c.id.clone() },
|
||||
]);
|
||||
graph.edges.insert(b.id.clone(), vec![ResolvedEdge { alias: "a".to_string(), to: a.id.clone() }]);
|
||||
graph.edges.insert(c.id.clone(), vec![ResolvedEdge { alias: "a".to_string(), to: a.id.clone() }]);
|
||||
|
||||
let plan = BuildPlan::from_graph(&graph, BuildTarget::Main);
|
||||
let names: Vec<_> = plan.steps.iter().map(|s| s.project_id.name.as_str()).collect();
|
||||
assert_eq!(names, vec!["a", "b", "c", "d"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sources_sorting() {
|
||||
let mut graph = ResolvedGraph::default();
|
||||
let a = mock_node("a", "1.0.0");
|
||||
graph.nodes.insert(a.id.clone(), a);
|
||||
|
||||
let plan = BuildPlan::from_graph(&graph, BuildTarget::Main);
|
||||
assert_eq!(plan.steps[0].sources, vec![PathBuf::from("a.pbs"), PathBuf::from("b.pbs")]);
|
||||
|
||||
let plan_test = BuildPlan::from_graph(&graph, BuildTarget::Test);
|
||||
assert_eq!(plan_test.steps[0].sources, vec![PathBuf::from("test_a.pbs"), PathBuf::from("test_b.pbs")]);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,17 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::manifest::{Manifest, load_manifest};
|
||||
use crate::deps::fetch::{fetch_dependency, FetchError};
|
||||
use crate::sources::{ProjectSources, discover, SourceError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ProjectId {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ResolvedNode {
|
||||
pub id: ProjectId,
|
||||
pub path: PathBuf,
|
||||
@ -18,7 +19,7 @@ pub struct ResolvedNode {
|
||||
pub sources: ProjectSources,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ResolvedEdge {
|
||||
pub alias: String,
|
||||
pub to: ProjectId,
|
||||
|
||||
@ -47,6 +47,7 @@ pub mod compiler;
|
||||
pub mod manifest;
|
||||
pub mod deps;
|
||||
pub mod sources;
|
||||
pub mod building;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::manifest::{load_manifest, ManifestKind};
|
||||
use crate::frontends::pbs::{Symbol, Visibility, parser::Parser, collector::SymbolCollector};
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProjectSources {
|
||||
pub main: Option<PathBuf>,
|
||||
pub files: Vec<PathBuf>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user