From 6bed48716eb157221e5fe9b8440acda5093b1734 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Thu, 11 Jun 2026 21:30:43 -0500 Subject: [PATCH] feat: package source loading --- crates/cli/src/commands/builder.rs | 38 ++++++++++++++++++- crates/cli/src/echo.rs | 4 ++ crates/core/src/loader/dependency.rs | 31 ++++++++++++++- crates/core/src/project.rs | 34 ++++++++++++++++- ...14_project_compilation_and_dependencies.md | 4 +- 5 files changed, 105 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/commands/builder.rs b/crates/cli/src/commands/builder.rs index db1cd01..9279419 100644 --- a/crates/cli/src/commands/builder.rs +++ b/crates/cli/src/commands/builder.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; use std::process::ExitCode; +use compiler_core::project::{ProjectLoadOptions, ProjectLoadProgress}; + use crate::args::{Emit, Target}; use crate::echo; @@ -15,13 +17,47 @@ pub struct Builder<'a> { pub(super) json: bool, } +fn print_project_load_progress(event: ProjectLoadProgress, verbose: bool) { + match event { + ProjectLoadProgress::ResolvingDependencies => echo::progress("Resolving dependencies"), + ProjectLoadProgress::UsingCachedPackage { name, version, path } => { + let package = package_label(&name, version.as_deref()); + if verbose { + echo::progress(format!("Using cached {package} ({})", path.display())); + } else { + echo::progress(format!("Using cached {package}")); + } + } + ProjectLoadProgress::UsingPathPackage { name, version, path } => { + let package = package_label(&name, version.as_deref()); + if verbose { + echo::progress(format!("Using path {package} ({})", path.display())); + } else { + echo::progress(format!("Using path {package}")); + } + } + } +} + +fn package_label(name: &str, version: Option<&str>) -> String { + match version { + Some(version) => format!("{name} {version}"), + None => name.to_string(), + } +} + impl Builder<'_> { pub fn build(&mut self) -> ExitCode { if self.json { return echo::fail("build", "--json", "machine-readable output is not implemented yet"); } let input = self.input.unwrap_or_else(|| Path::new(".")); - let project = match compiler_core::project::load_project(input) { + let verbose = self.verbose; + let mut progress = move |event| print_project_load_progress(event, verbose); + let project = match compiler_core::project::load_project_with_options( + input, + ProjectLoadOptions { progress: Some(&mut progress) }, + ) { Ok(project) => project, Err(diagnostics) => return echo::fail_with_diagnostics("load project", input.display(), &diagnostics), }; diff --git a/crates/cli/src/echo.rs b/crates/cli/src/echo.rs index b10c57f..e7c9359 100644 --- a/crates/cli/src/echo.rs +++ b/crates/cli/src/echo.rs @@ -7,6 +7,10 @@ pub fn status(label: &str, message: impl AsRef) { eprintln!("{} {}", label.bright_magenta().bold(), message.as_ref()); } +pub fn progress(message: impl AsRef) { + eprintln!("{}", message.as_ref()); +} + pub fn error(message: impl AsRef) { eprintln!("{} {}", "error".bright_red().bold(), message.as_ref()); } diff --git a/crates/core/src/loader/dependency.rs b/crates/core/src/loader/dependency.rs index e4a3fe1..56b1999 100644 --- a/crates/core/src/loader/dependency.rs +++ b/crates/core/src/loader/dependency.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, fs}; use serde::Deserialize; use crate::diagnostic::{Diagnostic, DiagnosticCode, Diagnostics}; -use crate::project::{Dependency, DependencySource, DependencyToml}; +use crate::project::{Dependency, DependencySource, DependencyToml, ProjectLoadProgress}; use crate::source::{SourceFile, SourceFileId}; use crate::types::ModuleInterface; use crate::{ast, parse, target}; @@ -31,6 +31,14 @@ struct PackagesToml { pub fn load_dependency_interfaces( root: &Path, dependencies: &[(String, DependencyToml, bool)], compile_target: target::CompileTarget, +) -> Result { + let mut progress = None; + load_dependency_interfaces_with_progress(root, dependencies, compile_target, &mut progress) +} + +pub fn load_dependency_interfaces_with_progress( + root: &Path, dependencies: &[(String, DependencyToml, bool)], compile_target: target::CompileTarget, + progress: &mut Option<&mut dyn FnMut(ProjectLoadProgress)>, ) -> Result { let package_versions = match fs::read_to_string(root.join("build").join("packages").join("packages.toml")) { Ok(text) => toml::from_str::(&text) @@ -41,11 +49,32 @@ pub fn load_dependency_interfaces( let mut output = DependencyInterfaces::default(); let mut diagnostics = Vec::new(); + if !dependencies.is_empty() + && let Some(progress) = progress.as_deref_mut() + { + progress(ProjectLoadProgress::ResolvingDependencies); + } + for (name, dep, _dev) in dependencies { match dependency_root(root, name, dep) { Some(pkg_root) => { let version = dep.get_dep_ver(name, &package_versions, &pkg_root); let source = DependencySource::from_toml(dep); + if let Some(progress) = progress.as_deref_mut() { + let event = match source { + DependencySource::Path | DependencySource::Git => ProjectLoadProgress::UsingPathPackage { + name: name.clone(), + version: version.clone(), + path: pkg_root.clone(), + }, + DependencySource::Hex => ProjectLoadProgress::UsingCachedPackage { + name: name.clone(), + version: version.clone(), + path: pkg_root.clone(), + }, + }; + progress(event); + } let pkg = DependencyPackage { name: name.clone(), version, root: pkg_root.clone(), source }; output.packages.push(pkg); match load_pkg_interfaces(&pkg_root, compile_target) { diff --git a/crates/core/src/project.rs b/crates/core/src/project.rs index c68b553..ad8574e 100644 --- a/crates/core/src/project.rs +++ b/crates/core/src/project.rs @@ -171,14 +171,44 @@ impl DependencyToml { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProjectLoadProgress { + ResolvingDependencies, + UsingCachedPackage { + name: String, + version: Option, + path: PathBuf, + }, + UsingPathPackage { + name: String, + version: Option, + path: PathBuf, + }, +} + +#[derive(Default)] +pub struct ProjectLoadOptions<'a> { + pub progress: Option<&'a mut dyn FnMut(ProjectLoadProgress)>, +} + pub fn load_project(path: impl AsRef) -> Result { + load_project_with_options(path, ProjectLoadOptions::default()) +} + +pub fn load_project_with_options( + path: impl AsRef, mut options: ProjectLoadOptions<'_>, +) -> Result { let root = project_root(path.as_ref()); let config = read_config(&root)?; let (sources, modules) = discover_modules(&root)?; let configured_dependencies = configured_dependencies(&config); let compile_target = target::project_compile_target(config.target.as_ref()); - let dependency_interfaces = - dependency::load_dependency_interfaces(&root, &configured_dependencies, compile_target)?; + let dependency_interfaces = dependency::load_dependency_interfaces_with_progress( + &root, + &configured_dependencies, + compile_target, + &mut options.progress, + )?; let dependencies = dependency::dependency_nodes(&dependency_interfaces.packages, dependencies(&config)); Ok(Project { diff --git a/docs/internal/tasks/14_project_compilation_and_dependencies.md b/docs/internal/tasks/14_project_compilation_and_dependencies.md index ab0ed75..f0b9bea 100644 --- a/docs/internal/tasks/14_project_compilation_and_dependencies.md +++ b/docs/internal/tasks/14_project_compilation_and_dependencies.md @@ -99,9 +99,9 @@ The goal here is to make linked project names deterministic and collision-free. cache schema version. - [x] Replace stale extracted package directories when stamps do not match. - [x] Load path dependency source without network access. -- [ ] Add CLI progress output: `Resolving dependencies`, `Downloading ...`, +- [x] Add CLI progress output: `Resolving dependencies`, `Downloading ...`, `Using cached ...`, and `Extracting ...`. -- [ ] Keep quiet output deterministic and put URLs, checksums, cache paths, and +- [x] Keep quiet output deterministic and put URLs, checksums, cache paths, and source paths behind `--verbose`. - [ ] Add a dependency source loader for selected packages and paths. - [ ] Compile one package module from source through the normal pipeline. -- 2.51.2