diff --git a/Cargo.lock b/Cargo.lock index 3260e9c672..285010d7b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,6 +684,21 @@ dependencies = [ "typenum", ] +[[package]] +name = "curl-sys" +version = "0.4.79+curl-8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a9bbeeb3996717ef1248018db20d1b0b5ba7165bff60e3b5135b4f4c2b37a5" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.52.0", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -961,7 +976,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-tree", - "url", + "url 2.5.4", ] [[package]] @@ -987,7 +1002,9 @@ version = "0.26.0" dependencies = [ "camino", "fe-common", + "git2", "glob", + "petgraph", "semver", "serde", "smol_str 0.1.24", @@ -1010,10 +1027,17 @@ dependencies = [ "fe-driver", "fe-hir", "fe-hir-analysis", + "fe-resolver", "fe-test-utils", "wasm-bindgen-test", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -1026,7 +1050,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "percent-encoding", + "percent-encoding 2.3.1", ] [[package]] @@ -1164,6 +1188,21 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "git2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7339329bfa14a00223244311560d11f8f489b453fb90092af97f267a6090ab0" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url 1.7.2", +] + [[package]] name = "glob" version = "0.3.2" @@ -1347,6 +1386,17 @@ dependencies = [ "syn", ] +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "1.0.3" @@ -1486,6 +1536,47 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libgit2-sys" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48441cb35dc255da8ae72825689a95368bf510659ae1ad55dc4aa88cb1789bf1" +dependencies = [ + "cc", + "curl-sys", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1567,9 +1658,15 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "url", + "url 2.5.4", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "memchr" version = "2.7.4" @@ -1674,6 +1771,24 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1724,12 +1839,28 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1753,6 +1884,12 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "plotters" version = "0.3.7" @@ -2288,6 +2425,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.43.0" @@ -2449,12 +2601,27 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.14" @@ -2467,6 +2634,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + [[package]] name = "url" version = "2.5.4" @@ -2474,8 +2652,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna", - "percent-encoding", + "idna 1.0.3", + "percent-encoding 2.3.1", "serde", ] @@ -2509,6 +2687,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/crates/driver/src/db.rs b/crates/driver/src/db.rs index 739e1ce94a..71568c4115 100644 --- a/crates/driver/src/db.rs +++ b/crates/driver/src/db.rs @@ -9,7 +9,7 @@ use codespan_reporting::term::{ use common::{ diagnostics::CompleteDiagnostic, impl_db_traits, - indexmap::IndexSet, + indexmap::{IndexMap, IndexSet}, input::{IngotDependency, IngotKind, Version}, InputDb, InputFile, InputIngot, }; @@ -29,6 +29,7 @@ use hir_analysis::{ HirAnalysisDb, }; use include_dir::{include_dir, Dir}; +use resolver::ingot::graph::Graph; use crate::diagnostics::ToCsDiag; @@ -150,27 +151,63 @@ impl DriverDataBase { pub fn local_ingot( &mut self, - path: &Utf8Path, - version: &Version, - source_root: &Utf8Path, - source_files: Vec<(Utf8PathBuf, String)>, + dependency_graph: &Graph, core_ingot: InputIngot, - ) -> (InputIngot, IndexSet) { - let core_dependency = IngotDependency::new("core", core_ingot); - let mut external_ingots = IndexSet::default(); - external_ingots.insert(core_dependency); - let input_ingot = InputIngot::new( - self, - path.as_str(), - IngotKind::Local, - version.clone(), - external_ingots, - ); + ) -> (InputIngot, IndexMap) { + let mut all_ingots = IndexMap::new(); + + for path in dependency_graph.reverse_toposort() { + let external_ingots = dependency_graph + .dependencies(&path) + .into_iter() + .map(|dependency| IngotDependency { + name: dependency.0.to_string().into(), + ingot: all_ingots[&dependency.1], + }) + .chain(vec![IngotDependency { + name: "core".into(), + ingot: core_ingot, + }]) + .collect(); + + all_ingots.insert( + path.clone(), + InputIngot::new( + self, + &path.to_string(), + IngotKind::External, + Version::new(0, 0, 0), + external_ingots, + ), + ); + } - let input_files = self.set_ingot_source_files(input_ingot, source_root, source_files); - (input_ingot, input_files) + let local_ingot = all_ingots + .shift_remove(&dependency_graph.local_paths[0]) + .expect("local is missing from input ingots"); + (local_ingot, all_ingots) } + // pub fn local_ingot( + // &mut self, + // path: &Utf8Path, + // version: &Version, + // ) -> (InputIngot, IndexSet) { + // let core_dependency = IngotDependency::new("core", core_ingot); + // let mut external_ingots = IndexSet::default(); + // external_ingots.insert(core_dependency); + // let input_ingot = InputIngot::new( + // self, + // path.as_str(), + // IngotKind::Local, + // version.clone(), + // external_ingots, + // ); + // + // let input_files = self.set_ingot_source_files(input_ingot, source_root, source_files); + // (input_ingot, input_files) + // } + pub fn core_ingot( &mut self, path: &Utf8Path, @@ -215,7 +252,7 @@ impl DriverDataBase { (input_ingot, input_files) } - fn set_ingot_source_files( + pub fn set_ingot_source_files( &mut self, ingot: InputIngot, root: &Utf8Path, diff --git a/crates/driver/src/lib.rs b/crates/driver/src/lib.rs index 778253941f..bfc2e0b64f 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -2,12 +2,17 @@ pub mod db; pub mod diagnostics; pub mod files; use camino::Utf8PathBuf; +use common::indexmap::IndexMap; pub use db::{DriverDataBase, DriverDb}; use clap::{Parser, Subcommand}; use hir::hir_def::TopLevelMod; use resolver::{ - ingot::{config::Config, source_files::SourceFiles, Ingot, IngotResolver}, + ingot::{ + config::Config, + source_files::{SourceFiles, SourceFilesResolver}, + Ingot, IngotResolver, + }, Resolver, }; @@ -31,6 +36,7 @@ pub fn run(opts: &Options) { root: Some(root), files, }), + .. }) => { let diagnostics = ingot_resolver.take_diagnostics(); if !diagnostics.is_empty() { @@ -63,7 +69,7 @@ pub fn run(opts: &Options) { db.static_core_ingot().0 }; - let local_ingot = match ingot_resolver.resolve(path) { + let (local_ingot, external_ingots) = match ingot_resolver.resolve(path) { Ok(Ingot::Folder { config: Some(Config { @@ -75,6 +81,7 @@ pub fn run(opts: &Options) { root: Some(root), files, }), + dependency_graph: Some(dependency_graph), }) => { let diagnostics = ingot_resolver.take_diagnostics(); if !diagnostics.is_empty() { @@ -84,11 +91,29 @@ pub fn run(opts: &Options) { } std::process::exit(2) } - db.local_ingot(path, &version, &root, files, core_ingot).0 - } - Ok(Ingot::SingleFile { path, content }) => { - db.standalone(&path, &content, core_ingot).0 + std::fs::write("dependencies.dot", dependency_graph.dot()).expect("failed"); + let (local_ingot, external_ingots) = + db.local_ingot(&dependency_graph, core_ingot); + db.set_ingot_source_files(local_ingot, &root, files); + let mut source_files_resolver = SourceFilesResolver::default(); + for (external_path, external_ingot) in external_ingots.iter() { + match source_files_resolver.resolve(&external_path) { + Ok(SourceFiles { + root: Some(root), + files, + }) => { + db.set_ingot_source_files(*external_ingot, &root, files); + } + Ok(_) => todo!(), + Err(_) => todo!(), + } + } + (local_ingot, external_ingots) } + Ok(Ingot::SingleFile { path, content }) => ( + db.standalone(&path, &content, core_ingot).0, + IndexMap::new(), + ), Ok(_) => { eprintln!("an error was encountered while resolving `{path}`"); for diagnostic in ingot_resolver.take_diagnostics() { @@ -107,6 +132,10 @@ pub fn run(opts: &Options) { let local_diags = db.run_on_ingot(local_ingot); core_diags.emit(&db); local_diags.emit(&db); + for external_ingot in external_ingots.values() { + let diags = db.run_on_ingot(*external_ingot); + diags.emit(&db); + } } Command::New => eprintln!("`fe new` doesn't work at the moment"), } diff --git a/crates/language-server/src/backend/workspace.rs b/crates/language-server/src/backend/workspace.rs index 8d1b6dd9d4..f9944e3322 100644 --- a/crates/language-server/src/backend/workspace.rs +++ b/crates/language-server/src/backend/workspace.rs @@ -319,7 +319,7 @@ impl Workspace { } fn sync_local_ingots(&mut self, db: &mut LanguageServerDatabase, path: &str) { - let config_paths = glob::glob(&format!("{path}/**/{FE_CONFIG_SUFFIX}")) + let config_paths = glob::glob(&format!("{path}/**/fe.toml")) .unwrap() .filter_map(Result::ok) .map(|p| p.to_str().unwrap().to_string()) diff --git a/crates/resolver/Cargo.toml b/crates/resolver/Cargo.toml index 8c5e3b9b9f..8cefb36fbf 100644 --- a/crates/resolver/Cargo.toml +++ b/crates/resolver/Cargo.toml @@ -16,3 +16,5 @@ semver.workspace = true smol_str = "0.1" toml = "0.8" serde = { version = "1", features = ["derive"] } +petgraph = "0.6" +git2 = "0.8" diff --git a/crates/resolver/src/ingot.rs b/crates/resolver/src/ingot.rs index f1bbd43783..ecd56b1148 100644 --- a/crates/resolver/src/ingot.rs +++ b/crates/resolver/src/ingot.rs @@ -2,11 +2,13 @@ use std::{fmt, fs}; use camino::Utf8PathBuf; use config::{Config, ConfigResolver}; +use graph::{Graph, GraphResolver}; use source_files::{SourceFiles, SourceFilesResolver}; use crate::Resolver; pub mod config; +pub mod graph; pub mod source_files; #[derive(Debug)] @@ -18,6 +20,7 @@ pub enum Ingot { Folder { config: Option, source_files: Option, + dependency_graph: Option, }, } @@ -51,6 +54,7 @@ impl Resolver for IngotResolver { if ingot_path.is_dir() { let mut config_resolver = ConfigResolver::default(); let mut source_files_resolver = SourceFilesResolver::default(); + let mut dep_graph_resolver = GraphResolver::default(); let config = match config_resolver.resolve(ingot_path) { Ok(config) => Some(config), @@ -81,9 +85,12 @@ impl Resolver for IngotResolver { .push(Diagnostic::SourceFilesDiagnostics(source_files_diags)); } + let dependency_graph = Some(dep_graph_resolver.resolve(ingot_path).unwrap()); + Ok(Ingot::Folder { config, source_files, + dependency_graph, }) } else { match fs::read_to_string(ingot_path) { diff --git a/crates/resolver/src/ingot/config.rs b/crates/resolver/src/ingot/config.rs index 74912af548..c729c326cd 100644 --- a/crates/resolver/src/ingot/config.rs +++ b/crates/resolver/src/ingot/config.rs @@ -1,5 +1,5 @@ use camino::Utf8PathBuf; -use common::input::Version; +use common::{indexmap::IndexMap, input::Version}; use serde::Deserialize; use smol_str::SmolStr; use std::{fmt, fs, mem}; @@ -13,6 +13,7 @@ const FE_CONFIG_SUFFIX: &str = "fe.toml"; pub struct Config { pub name: Option, pub version: Option, + pub dependencies: Option>, } #[derive(Debug)] @@ -42,16 +43,21 @@ impl Resolver for ConfigResolver { type Diagnostic = Diagnostic; fn resolve(&mut self, ingot_path: &Utf8PathBuf) -> Result { - let config_path = ingot_path.join(FE_CONFIG_SUFFIX); + let config_path = if ingot_path.ends_with(FE_CONFIG_SUFFIX) { + ingot_path.clone() + } else { + ingot_path.join(FE_CONFIG_SUFFIX) + }; if config_path.exists() { let file_content = fs::read_to_string(&config_path).map_err(Error::FileReadError)?; - let raw_config = toml::from_str::(&file_content) - .map_err(Error::TomlParseError)? - .ingot; + let raw_config = + toml::from_str::(&file_content).map_err(Error::TomlParseError)?; + + let ingot = raw_config.ingot.unwrap(); - let version = match raw_config.version { + let version = match ingot.version { Some(version) => match Version::parse(&version) { Ok(version) => Some(version), Err(error) => { @@ -65,7 +71,7 @@ impl Resolver for ConfigResolver { } }; - let name = match raw_config.name { + let name = match ingot.name { Some(name) => { if is_valid_name(&name) { Some(name) @@ -80,11 +86,37 @@ impl Resolver for ConfigResolver { } }; - if raw_config.dependencies.is_some() { - eprintln!("ingot dependencies are not yet supported") - } - - Ok(Config { name, version }) + let dependencies = raw_config.dependencies.map(|dependencies| { + dependencies + .iter() + .map(|(name, description)| { + ( + SmolStr::from(name), + DependencyDescription { + ingot: IngotDescription { + version: Version::new(0, 0, 0), + }, + files: FilesDescription::Path( + description + .as_table() + .expect(&config_path.to_string()) + .get("path") + .unwrap() + .as_str() + .unwrap() + .to_string(), + ), + }, + ) + }) + .collect() + }); + + Ok(Config { + name, + version, + dependencies, + }) } else { Err(Error::ConfigFileDoesNotExist) } @@ -95,18 +127,6 @@ impl Resolver for ConfigResolver { } } -#[derive(Deserialize, Debug, Clone)] -struct IngotConfig { - pub ingot: RawConfig, -} - -#[derive(Deserialize, Debug, Clone)] -struct RawConfig { - pub name: Option, - pub version: Option, - pub dependencies: Option, -} - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -138,3 +158,56 @@ fn is_valid_name_char(c: char) -> bool { fn is_valid_name(s: &SmolStr) -> bool { s.chars().all(is_valid_name_char) } + +#[derive(Debug, Clone)] +pub enum FilesDescription { + Path(String), +} + +#[derive(Debug, Clone)] +pub struct IngotDescription { + pub version: Version, +} + +#[derive(Debug, Clone)] +pub struct DependencyDescription { + pub ingot: IngotDescription, + pub files: FilesDescription, +} + +#[derive(Deserialize, Debug, Clone)] +struct RawConfig { + pub ingot: Option, + pub dependencies: Option
, +} + +#[derive(Deserialize, Debug, Clone)] +struct RawIngotConfig { + pub name: Option, + pub version: Option, +} + +// #[derive(Deserialize, Debug, Clone)] +// pub struct RawPathDescription { +// path: String, +// } +// +// #[derive(Deserialize, Debug, Clone)] +// #[serde(untagged)] +// pub(crate) enum RawFilesDescription { +// Path(RawPathDescription), +// Remote(GitDescription), +// } +// +// #[derive(Deserialize, Debug, Clone)] +// pub(crate) struct RawIngotDescription { +// pub version: SmolStr, +// } +// +// #[derive(Deserialize, Debug, Clone)] +// pub(crate) struct RawDependencyDescription { +// #[serde(flatten)] +// pub ingot: RawIngotDescription, +// #[serde(flatten)] +// pub files: RawFilesDescription, +// } diff --git a/crates/resolver/src/ingot/graph.rs b/crates/resolver/src/ingot/graph.rs new file mode 100644 index 0000000000..6b3acb8d51 --- /dev/null +++ b/crates/resolver/src/ingot/graph.rs @@ -0,0 +1,202 @@ +use core::panic; +use std::mem::take; + +use camino::{Utf8Path, Utf8PathBuf}; +use common::indexmap::{IndexMap, IndexSet}; +use glob::glob; +use petgraph::dot; +use petgraph::visit::EdgeRef; +use petgraph::{ + dot::Dot, + graph::{DiGraph, NodeIndex}, + Direction, +}; +use smol_str::SmolStr; + +use crate::Resolver; + +use super::config::{self, Config, ConfigResolver, FilesDescription}; + +#[derive(Debug, Default)] +pub struct Graph { + pub local_paths: IndexSet, + pub configs: IndexMap, + graph: DiGraph, + nodes: IndexMap, +} + +impl Graph { + pub fn reverse_toposort(&self) -> Vec { + petgraph::algo::toposort(&self.graph, None) + .unwrap() + .into_iter() + .map(|node| self.graph[node].clone()) + .rev() + .collect() + } + + pub fn dependencies(&self, path: &Utf8Path) -> IndexMap { + let node = self.nodes[path]; + + self.graph + .edges_directed(node, Direction::Outgoing) + .map(|edge| { + ( + edge.weight().clone(), + self.graph.node_weight(edge.target()).unwrap().clone(), + ) + }) + .collect() + } + + pub fn dependents(&self, path: &Utf8Path) -> IndexMap { + let node = self.nodes[path]; + self.graph + .edges_directed(node, Direction::Incoming) + .map(|edge| { + ( + edge.weight().clone(), + self.graph.node_weight(edge.target()).unwrap().clone(), + ) + }) + .collect() + } + + pub fn contains(&self, path: &Utf8PathBuf) -> bool { + self.nodes.contains_key(path) + } + + pub fn add_dependency(&mut self, name: SmolStr, source: &Utf8Path, target: &Utf8Path) { + let source_node = self.node_index(source); + let target_node = self.node_index(target); + + self.graph.add_edge(source_node, target_node, name); + } + + pub fn dot(&self) -> String { + format!( + "{:?}", + Dot::with_config(&self.graph, &[dot::Config::EdgeNoLabel]) + ) + } + + fn node_index(&mut self, path: &Utf8Path) -> NodeIndex { + let path = path.canonicalize_utf8().unwrap(); + if !self.contains(&path) { + let node = self.graph.add_node(path.clone()); + self.nodes.insert(path, node); + node + } else { + self.nodes[&path] + } + } +} + +#[derive(Default)] +pub struct GraphResolver { + config_resolver: ConfigResolver, + diagnostics: Vec, +} + +#[derive(Debug)] +pub enum Error { + PathDoesNotExist, +} + +#[derive(Debug)] +pub enum Diagnostic { + TargetPathDoesNotExist { + source_path: Utf8PathBuf, + target_path: Utf8PathBuf, + }, + TargetConfigResolutionError(Utf8PathBuf, config::Error), + LocalConfigResolutionError(Utf8PathBuf, config::Error), +} + +impl Resolver for GraphResolver { + type Description = Utf8PathBuf; + type Resource = Graph; + type Error = Error; + type Diagnostic = Diagnostic; + + fn resolve(&mut self, path: &Utf8PathBuf) -> Result { + let path = path.canonicalize_utf8().unwrap(); + + let mut graph = Graph::default(); + + let glob_path = path.join("**/fe.toml"); + + let local_configs: IndexMap<_, _> = glob(glob_path.as_str()) + .expect("failed to read glob pattern") + .filter_map(|entry| { + let path = Utf8PathBuf::from_path_buf(entry.unwrap().to_path_buf()) + .unwrap() + .canonicalize_utf8() + .unwrap(); + match self.config_resolver.resolve(&path) { + Ok(config) => Some((path.parent().unwrap().to_path_buf(), config)), + Err(error) => { + self.diagnostics + .push(Diagnostic::LocalConfigResolutionError(path, error)); + None + } + } + }) + .collect(); + + let mut unresolved_dependencies = vec![]; + + for (ingot_path, config) in local_configs.iter() { + graph.local_paths.insert(ingot_path.clone()); + if let Some(dependencies) = &config.dependencies { + unresolved_dependencies.push((ingot_path.clone(), dependencies.clone())); + } + } + + graph.configs = local_configs; + + while let Some((source_path, targets)) = unresolved_dependencies.pop() { + for (target_name, target_description) in targets { + let target_path = match target_description.files { + FilesDescription::Path(path) => { + source_path.join(path).canonicalize_utf8().unwrap() + } + }; + + if !target_path.exists() { + self.diagnostics.push(Diagnostic::TargetPathDoesNotExist { + source_path: source_path.clone(), + target_path: target_path.clone(), + }); + continue; + } + + if !graph.contains(&target_path) { + match self.config_resolver.resolve(&target_path) { + Ok(config) => { + if let Some(dependencies) = config.dependencies.clone() { + unresolved_dependencies.push((target_path.clone(), dependencies)) + } + graph.configs.insert(source_path.clone(), config); + } + Err(error) => { + self.diagnostics + .push(Diagnostic::TargetConfigResolutionError( + target_path.clone(), + error, + )) + } + } + } + + graph.add_dependency(target_name, &source_path, &target_path); + } + } + + Ok(graph) + } + + fn take_diagnostics(&mut self) -> Vec { + take(&mut self.diagnostics) + } +} diff --git a/crates/uitest/Cargo.toml b/crates/uitest/Cargo.toml index 242f13e3d5..8b065fc845 100644 --- a/crates/uitest/Cargo.toml +++ b/crates/uitest/Cargo.toml @@ -15,3 +15,4 @@ driver.workspace = true test-utils.workspace = true hir.workspace = true hir-analysis.workspace = true +resolver.workspace = true diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/A/fe.toml b/crates/uitest/fixtures/ingot_resolution/bad_workspace/A/fe.toml new file mode 100644 index 0000000000..4d5d038bde --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/bad_workspace/A/fe.toml @@ -0,0 +1,6 @@ +[ingot] +name = "A" +version = "2.0.0" + +[dependencies] +B = { path = "../B" } diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/A/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/bad_workspace/A/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/B/fe.toml b/crates/uitest/fixtures/ingot_resolution/bad_workspace/B/fe.toml new file mode 100644 index 0000000000..a71b8ee6a7 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/bad_workspace/B/fe.toml @@ -0,0 +1,9 @@ +[ingot] +name = "B" +version = "1.0.0" + +[dependencies] +# cycle +A = { path = "A" } +C = { path = "C" } + diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/B/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/bad_workspace/B/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/C/fe.toml b/crates/uitest/fixtures/ingot_resolution/bad_workspace/C/fe.toml new file mode 100644 index 0000000000..a86e0f6dec --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/bad_workspace/C/fe.toml @@ -0,0 +1,6 @@ +[ingot] +version = "1.O.0" + +[dependencies] +# D = { path = "../D" } +# E = { path = "../E" } diff --git a/crates/uitest/fixtures/ingot_resolution/bad_workspace/C/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/bad_workspace/C/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/external/C/fe.toml b/crates/uitest/fixtures/ingot_resolution/external/C/fe.toml new file mode 100644 index 0000000000..3a5fb438d3 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/external/C/fe.toml @@ -0,0 +1,8 @@ +[ingot] +name = "C" +version = "1.0.0" + +[dependencies] +D = { path = "../D" } +E = { path = "../E" } +F = { path = "../F" } diff --git a/crates/uitest/fixtures/ingot_resolution/external/C/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/external/C/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/external/D/fe.toml b/crates/uitest/fixtures/ingot_resolution/external/D/fe.toml new file mode 100644 index 0000000000..1cca4a70c9 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/external/D/fe.toml @@ -0,0 +1,8 @@ +[ingot] +name = "D" +version = "1.0.0" + +[dependencies] +E = { path = "../E" } +F = { path = "../F" } +G = { path = "../G" } diff --git a/crates/uitest/fixtures/ingot_resolution/external/D/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/external/D/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/external/E/fe.toml b/crates/uitest/fixtures/ingot_resolution/external/E/fe.toml new file mode 100644 index 0000000000..4b6d10f137 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/external/E/fe.toml @@ -0,0 +1,8 @@ +[ingot] +name = "E" +version = "1.0.0" + +[dependencies] +G = { path = "../G" } +# cycle +C = { path = "../C" } diff --git a/crates/uitest/fixtures/ingot_resolution/external/E/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/external/E/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/external/F/fe.toml b/crates/uitest/fixtures/ingot_resolution/external/F/fe.toml new file mode 100644 index 0000000000..dfaab6ab0d --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/external/F/fe.toml @@ -0,0 +1,6 @@ +[ingot] +name = "F" +version = "1.0.0" + +[dependencies] +G = { path = "../G" } diff --git a/crates/uitest/fixtures/ingot_resolution/external/F/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/external/F/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/external/G/fe.toml b/crates/uitest/fixtures/ingot_resolution/external/G/fe.toml new file mode 100644 index 0000000000..8e194d9cf0 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/external/G/fe.toml @@ -0,0 +1,3 @@ +[ingot] +name = "G" +version = "1.0.0" diff --git a/crates/uitest/fixtures/ingot_resolution/external/G/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/external/G/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/ingot_a_graph.snap b/crates/uitest/fixtures/ingot_resolution/ingot_a_graph.snap new file mode 100644 index 0000000000..3afd8667c6 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/ingot_a_graph.snap @@ -0,0 +1,24 @@ +--- +source: crates/uitest/tests/ingot_resolution.rs +expression: graph.dot() +--- +digraph { + 0 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/A\"" ] + 1 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/C\"" ] + 2 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/E\"" ] + 3 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/G\"" ] + 4 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/D\"" ] + 5 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/F\"" ] + 0 -> 1 [ ] + 0 -> 2 [ ] + 0 -> 3 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] + 1 -> 4 [ ] + 1 -> 2 [ ] + 1 -> 5 [ ] + 5 -> 3 [ ] + 4 -> 2 [ ] + 4 -> 5 [ ] + 4 -> 3 [ ] +} diff --git a/crates/uitest/fixtures/ingot_resolution/workspace/A/fe.toml b/crates/uitest/fixtures/ingot_resolution/workspace/A/fe.toml new file mode 100644 index 0000000000..fd27848c2a --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/workspace/A/fe.toml @@ -0,0 +1,8 @@ +[ingot] +name = "A" +version = "1.0.0" + +[dependencies] +C = { path = "../../external/C" } +E = { path = "../../external/E" } +G = { path = "../../external/G" } diff --git a/crates/uitest/fixtures/ingot_resolution/workspace/A/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/workspace/A/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/workspace/B/fe.toml b/crates/uitest/fixtures/ingot_resolution/workspace/B/fe.toml new file mode 100644 index 0000000000..60a6737a4f --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/workspace/B/fe.toml @@ -0,0 +1,6 @@ +[ingot] +name = "B" +version = "1.0.0" + +[dependencies] +G = { path = "../../external/G" } diff --git a/crates/uitest/fixtures/ingot_resolution/workspace/B/src/lib.fe b/crates/uitest/fixtures/ingot_resolution/workspace/B/src/lib.fe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/uitest/fixtures/ingot_resolution/workspace_graph.snap b/crates/uitest/fixtures/ingot_resolution/workspace_graph.snap new file mode 100644 index 0000000000..d39d800624 --- /dev/null +++ b/crates/uitest/fixtures/ingot_resolution/workspace_graph.snap @@ -0,0 +1,26 @@ +--- +source: crates/uitest/tests/ingot_resolution.rs +expression: graph.dot() +--- +digraph { + 0 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/B\"" ] + 1 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/G\"" ] + 2 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/A\"" ] + 3 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/C\"" ] + 4 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/E\"" ] + 5 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/D\"" ] + 6 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/F\"" ] + 0 -> 1 [ ] + 2 -> 3 [ ] + 2 -> 4 [ ] + 2 -> 1 [ ] + 4 -> 3 [ ] + 4 -> 1 [ ] + 3 -> 5 [ ] + 3 -> 4 [ ] + 3 -> 6 [ ] + 6 -> 1 [ ] + 5 -> 4 [ ] + 5 -> 6 [ ] + 5 -> 1 [ ] +} diff --git a/crates/uitest/ingot_a.dot b/crates/uitest/ingot_a.dot new file mode 100644 index 0000000000..9fc5e2f5dc --- /dev/null +++ b/crates/uitest/ingot_a.dot @@ -0,0 +1,20 @@ +digraph { + 0 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/A\"" ] + 1 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/C\"" ] + 2 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/E\"" ] + 3 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/G\"" ] + 4 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/D\"" ] + 5 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/F\"" ] + 0 -> 1 [ ] + 0 -> 2 [ ] + 0 -> 3 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] + 1 -> 4 [ ] + 1 -> 2 [ ] + 1 -> 5 [ ] + 5 -> 3 [ ] + 4 -> 2 [ ] + 4 -> 5 [ ] + 4 -> 3 [ ] +} diff --git a/crates/uitest/ingot_a.png b/crates/uitest/ingot_a.png new file mode 100644 index 0000000000..45cb93fb15 Binary files /dev/null and b/crates/uitest/ingot_a.png differ diff --git a/crates/uitest/tests/ingot_resolution.rs b/crates/uitest/tests/ingot_resolution.rs new file mode 100644 index 0000000000..8627ff9625 --- /dev/null +++ b/crates/uitest/tests/ingot_resolution.rs @@ -0,0 +1,79 @@ +use core::panic; +use std::fs; + +use camino::Utf8PathBuf; +use resolver::{ingot::graph::GraphResolver, Resolver}; +use test_utils::snap_test; + +#[test] +fn workspace_graph_resolution() { + let mut resolver = GraphResolver::default(); + let graph = resolver + .resolve( + &Utf8PathBuf::from("fixtures/ingot_resolution/workspace") + .canonicalize_utf8() + .unwrap(), + ) + .expect("dep error"); + let diags = resolver.take_diagnostics(); + if !diags.is_empty() { + panic!("{:#?}", diags) + } + fs::write("workspace.dot", graph.dot()).unwrap(); + let snap_path = format!( + "{}/fixtures/ingot_resolution/workspace_graph", + env!("CARGO_MANIFEST_DIR") + ); + snap_test!(graph.dot(), &snap_path); +} + +#[test] +fn ingot_a_resolution() { + let mut resolver = GraphResolver::default(); + let graph = resolver + .resolve( + &Utf8PathBuf::from("fixtures/ingot_resolution/workspace/A") + .canonicalize_utf8() + .unwrap(), + ) + .expect("dep error"); + let diags = resolver.take_diagnostics(); + if !diags.is_empty() { + panic!("{:#?}", diags) + } + fs::write("ingot_a.dot", graph.dot()).unwrap(); + let snap_path = format!( + "{}/fixtures/ingot_resolution/ingot_a_graph", + env!("CARGO_MANIFEST_DIR") + ); + snap_test!(graph.dot(), &snap_path); +} + +#[test] +fn bad_workspace_resolution() { + let mut resolver = GraphResolver::default(); + let graph = resolver + .resolve( + &Utf8PathBuf::from("fixtures/ingot_resolution/bad_workspace") + .canonicalize_utf8() + .unwrap(), + ) + .expect("dep error"); + let diags = resolver.take_diagnostics(); + + let snap_path = format!( + "{}/fixtures/ingot_resolution/bad_workspace_diags", + env!("CARGO_MANIFEST_DIR") + ); + + let diags = format!("{:?}", diags); + + snap_test!(&diags, &snap_path); + + // fs::write("ingot_a.dot", graph.dot()).unwrap(); + let snap_path = format!( + "{}/fixtures/ingot_resolution/bad_workspace_graph", + env!("CARGO_MANIFEST_DIR") + ); + snap_test!(graph.dot(), &snap_path); +} diff --git a/crates/uitest/workspace.dot b/crates/uitest/workspace.dot new file mode 100644 index 0000000000..8d1dc3e84c --- /dev/null +++ b/crates/uitest/workspace.dot @@ -0,0 +1,22 @@ +digraph { + 0 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/B\"" ] + 1 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/G\"" ] + 2 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/workspace/A\"" ] + 3 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/C\"" ] + 4 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/E\"" ] + 5 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/D\"" ] + 6 [ label = "\"/home/grantwuerker/workshop/fe/crates/uitest/fixtures/ingot_resolution/external/F\"" ] + 0 -> 1 [ ] + 2 -> 3 [ ] + 2 -> 4 [ ] + 2 -> 1 [ ] + 4 -> 3 [ ] + 4 -> 1 [ ] + 3 -> 5 [ ] + 3 -> 4 [ ] + 3 -> 6 [ ] + 6 -> 1 [ ] + 5 -> 4 [ ] + 5 -> 6 [ ] + 5 -> 1 [ ] +} diff --git a/crates/uitest/workspace.png b/crates/uitest/workspace.png new file mode 100644 index 0000000000..2ef5aa6521 Binary files /dev/null and b/crates/uitest/workspace.png differ