Skip to content

Commit

Permalink
First cut at package management
Browse files Browse the repository at this point in the history
  • Loading branch information
jneem committed Nov 5, 2024
1 parent 9b9c9f1 commit e67a1bd
Show file tree
Hide file tree
Showing 34 changed files with 4,874 additions and 554 deletions.
2,767 changes: 2,242 additions & 525 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"vector",
"lsp/nls",
"lsp/lsp-harness",
"package",
"utils",
"wasm-repl",
"pyckel",
Expand All @@ -23,6 +24,7 @@ readme = "README.md"

[workspace.dependencies]
nickel-lang-core = { version = "0.9.0", path = "./core", default-features = false }
nickel-lang-package = { version = "0.1.0", path = "./package" }
nickel-lang-vector = { version = "0.1.0", path = "./vector" }
nickel-lang-utils = { version = "0.1.0", path = "./utils" }
lsp-harness = { version = "0.1.0", path = "./lsp/lsp-harness" }
Expand All @@ -39,6 +41,7 @@ ansi_term = "0.12"
anyhow = "1.0"
assert_cmd = "2.0.11"
assert_matches = "1.5.0"
base16 = "0.2.1"
bincode = "1.3.3"
clap = "4.3"
clap_complete = "4.3.2"
Expand Down
2 changes: 2 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ metrics = ["dep:metrics", "dep:metrics-util", "nickel-lang-core/metrics"]

[dependencies]
nickel-lang-core = { workspace = true, features = [ "markdown" ], default-features = false }
nickel-lang-package.workspace = true

clap = { workspace = true, features = ["derive", "string"] }
serde = { workspace = true, features = ["derive"] }
Expand All @@ -37,6 +38,7 @@ clap_complete = { workspace = true }

metrics = { workspace = true, optional = true }
metrics-util = { workspace = true, optional = true }
semver = { version = "1.0.23", features = ["serde"] }

comrak = { workspace = true, optional = true }
once_cell.workspace = true
Expand Down
5 changes: 4 additions & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use git_version::git_version;

use crate::{
completions::GenCompletionsCommand, eval::EvalCommand, export::ExportCommand,
pprint_ast::PprintAstCommand, query::QueryCommand, typecheck::TypecheckCommand,
package::PackageCommand, pprint_ast::PprintAstCommand, query::QueryCommand,
typecheck::TypecheckCommand,
};

use nickel_lang_core::error::report::ErrorFormat;
Expand Down Expand Up @@ -74,6 +75,8 @@ pub enum Command {
Query(QueryCommand),
/// Typechecks the program but does not run it
Typecheck(TypecheckCommand),
/// Handle nickel packages
Package(PackageCommand),
/// Starts a REPL session
#[cfg(feature = "repl")]
Repl(ReplCommand),
Expand Down
40 changes: 40 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ pub enum Error {
program: Program<CBNCache>,
error: CliUsageError,
},
NoManifest,
/// The provided manifest path doesn't have a parent directory.
NoPackageRoot {
manifest_path: std::path::PathBuf,
},
Package {
error: nickel_lang_package::error::Error,
},
/// Not an actual failure but a special early return to indicate that information was printed
/// during the usage of the customize mode, because a subcommand such as `list`, `show`, etc.
/// was used, and thus no customized program can be returned.
Expand Down Expand Up @@ -187,6 +195,12 @@ impl From<std::io::Error> for Error {
}
}

impl From<nickel_lang_package::error::Error> for Error {
fn from(error: nickel_lang_package::error::Error) -> Self {
Error::Package { error }
}
}

#[cfg(feature = "format")]
impl From<crate::format::FormatError> for Error {
fn from(error: crate::format::FormatError) -> Self {
Expand Down Expand Up @@ -256,6 +270,32 @@ impl Error {
Error::CustomizeInfoPrinted => {
// Nothing to do, the caller should simply exit.
}
Error::NoManifest => report_standalone("failed to find a manifest file", None),
Error::Package { error } => {
if let nickel_lang_package::error::Error::ManifestEval {
package,
mut program,
error,
} = error
{
let msg = if let Some(package) = package {
format!("failed to evaluate manifest file for package {package}")
} else {
"failed to evaluate package manifest".to_owned()
};
report_standalone(&msg, None);
program.report(error, format)
} else {
report_standalone("failed to read manifest file", Some(error.to_string()))
}
}
Error::NoPackageRoot { manifest_path } => report_standalone(
&format!(
"invalid manifest path `{}` has no parent",
manifest_path.display()
),
None,
),
}
}
}
19 changes: 19 additions & 0 deletions cli/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::PathBuf;

use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program};
use nickel_lang_package::ManifestFile;

use crate::{cli::GlobalOptions, customize::Customize, error::CliResult};

Expand All @@ -24,6 +25,9 @@ pub struct InputOptions<Customize: clap::Args> {
#[arg(long, short = 'I', global = true)]
pub import_path: Vec<PathBuf>,

#[arg(long, global = true)]
pub manifest_path: Option<PathBuf>,

#[command(flatten)]
pub customize_mode: Customize,
}
Expand All @@ -48,6 +52,21 @@ impl<C: clap::Args + Customize> Prepare for InputOptions<C> {
program.add_import_paths(nickel_path.split(':'));
}

if let Some(manifest_path) = self.manifest_path.as_ref() {
let manifest = ManifestFile::from_path(manifest_path)?;
let resolution = manifest.resolve()?;
for (pkg_id, versions) in &resolution.package_map {
for v in versions {
resolution
.index
.ensure_downloaded(pkg_id, v.clone())
.unwrap();
}
}
let package_map = resolution.package_map(&manifest)?;
program.set_package_map(package_map);
}

#[cfg(debug_assertions)]
if self.nostdlib {
program.set_skip_stdlib();
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod error;
mod eval;
mod export;
mod input;
mod package;
mod pprint_ast;
mod query;
mod typecheck;
Expand All @@ -44,6 +45,7 @@ fn main() -> ExitCode {
Command::Query(query) => query.run(opts.global),
Command::Typecheck(typecheck) => typecheck.run(opts.global),
Command::GenCompletions(completions) => completions.run(opts.global),
Command::Package(package) => package.run(opts.global),

#[cfg(feature = "repl")]
Command::Repl(repl) => repl.run(opts.global),
Expand Down
132 changes: 132 additions & 0 deletions cli/src/package.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::{
collections::HashMap,
env::current_dir,
path::{Path, PathBuf},
};

use nickel_lang_core::{identifier::Ident, package::PackageMap};
use nickel_lang_package::{index::PackageIndex, ManifestFile};

use crate::{
cli::GlobalOptions,
error::{CliResult, Error},
};

#[derive(clap::Subcommand, Debug)]
pub enum Command {
GenerateLockfile,
DebugResolution,
RefreshIndex,
Publish {
#[arg(long)]
index: PathBuf,

#[arg(long)]
// TODO: make this an index::Id and have clap use FromStr somehow
package_id: String,
},
}

#[derive(clap::Parser, Debug)]
pub struct PackageCommand {
#[command(subcommand)]
pub command: Command,

#[arg(long, global = true)]
pub manifest_path: Option<PathBuf>,
}

impl PackageCommand {
fn find_manifest(&self) -> CliResult<PathBuf> {
match &self.manifest_path {
Some(p) => Ok(p.clone()),
None => {
let mut dir = current_dir()?;

loop {
let path = dir.join("package.ncl");
if path.is_file() {
return Ok(path);
}

if !dir.pop() {
return Err(Error::NoManifest);
}
}
}
}
}

fn load_manifest(&self) -> CliResult<ManifestFile> {
Ok(ManifestFile::from_path(self.find_manifest()?)?)
}

pub fn run(self, _global: GlobalOptions) -> CliResult<()> {
match &self.command {
Command::GenerateLockfile => {
self.load_manifest()?.regenerate_lock()?;
}
Command::DebugResolution => {
let path = self.find_manifest()?;
let manifest = ManifestFile::from_path(path.clone())?;
let resolution = manifest.resolve()?;
let package_map = resolution.package_map(&manifest)?;
print_package_map(&package_map);
}
Command::Publish { index, package_id } => {
let id = package_id.parse().unwrap();
let package_file = nickel_lang_package::index::scrape::scrape(&id).unwrap();
dbg!(&package_file);
let mut package_index = PackageIndex::new_with_root(index.clone());
// TODO: check for conflicts between the new thing and what's already in the index
for pkg in package_file.packages.into_values() {
package_index.save(pkg);
}
}
Command::RefreshIndex => {
let index = PackageIndex::new();
index.refresh_from_github();
}
}

Ok(())
}
}

fn print_package_map(map: &PackageMap) {
let mut by_parent: HashMap<&Path, Vec<(Ident, &Path)>> = HashMap::new();
for ((parent, name), child) in &map.packages {
by_parent
.entry(parent.as_path())
.or_default()
.push((*name, child));
}

if map.top_level.is_empty() {
eprintln!("No top-level dependencies");
} else {
eprintln!("Top-level dependencies:");
let mut top_level = map.top_level.iter().collect::<Vec<_>>();
top_level.sort();
for (name, path) in top_level {
eprintln!(" {} -> {}", name, path.display());
}
}

let mut by_parent = by_parent.into_iter().collect::<Vec<_>>();
by_parent.sort();
if by_parent.is_empty() {
eprintln!("No transitive dependencies");
} else {
eprintln!("Transitive dependencies:");

for (parent, mut deps) in by_parent {
deps.sort();
eprintln!(" {}", parent.display());

for (name, path) in deps {
eprintln!(" {} -> {}", name, path.display());
}
}
}
}
2 changes: 2 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cxx-build = { workspace = true, optional = true }
pkg-config = { workspace = true, optional = true }

[dependencies]
base16.workspace = true
lalrpop-util.workspace = true
regex.workspace = true
simple-counter.workspace = true
Expand Down Expand Up @@ -84,6 +85,7 @@ tree-sitter-nickel = { workspace = true, optional = true }

metrics = { workspace = true, optional = true }
strsim = "0.10.0"
directories.workspace = true

bumpalo = { workspace = true, optional = true }

Expand Down
Loading

0 comments on commit e67a1bd

Please sign in to comment.