feat: implement dependency resolver and lune scripts

This commit is contained in:
daimond113 2024-07-14 15:19:15 +02:00
parent b73bf418c5
commit fdad8995a4
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
15 changed files with 994 additions and 173 deletions

View file

@ -18,7 +18,7 @@ jobs:
components: rustfmt, clippy components: rustfmt, clippy
- name: Run tests - name: Run tests
run: cargo test --all run: cargo test --all --all-features
- name: Check formatting - name: Check formatting
run: cargo fmt --all -- --check run: cargo fmt --all -- --check

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
imports_granularity = "Crate"

113
src/cli/init.rs Normal file
View file

@ -0,0 +1,113 @@
use crate::cli::read_config;
use clap::Args;
use colored::Colorize;
use inquire::validator::Validation;
use pesde::{errors::ManifestReadError, names::PackageName, Project, DEFAULT_INDEX_NAME};
use std::str::FromStr;
#[derive(Debug, Args)]
pub struct InitCommand {}
impl InitCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
match project.read_manifest() {
Ok(_) => {
println!("{}", "project already initialized".red());
Ok(())
}
Err(ManifestReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {
let mut manifest = nondestructive::yaml::from_slice(b"").unwrap();
let mut mapping = manifest.as_mut().make_mapping();
mapping.insert_str(
"name",
inquire::Text::new("What is the name of the project?")
.with_validator(|name: &str| {
Ok(match PackageName::from_str(name) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.to_string().into()),
})
})
.prompt()
.unwrap(),
);
mapping.insert_str("version", "0.1.0");
let description = inquire::Text::new(
"What is the description of the project? (leave empty for none)",
)
.prompt()
.unwrap();
if !description.is_empty() {
mapping.insert_str("description", description);
}
let authors = inquire::Text::new(
"Who are the authors of this project? (leave empty for none, comma separated)",
)
.prompt()
.unwrap();
let authors = authors
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>();
if !authors.is_empty() {
let mut authors_field = mapping
.insert("authors", nondestructive::yaml::Separator::Auto)
.make_sequence();
for author in authors {
authors_field.push_string(author);
}
}
let repo = inquire::Text::new(
"What is the repository URL of this project? (leave empty for none)",
)
.with_validator(|repo: &str| {
if repo.is_empty() {
return Ok(Validation::Valid);
}
Ok(match url::Url::parse(repo) {
Ok(_) => Validation::Valid,
Err(e) => Validation::Invalid(e.to_string().into()),
})
})
.prompt()
.unwrap();
if !repo.is_empty() {
mapping.insert_str("repository", repo);
}
let license = inquire::Text::new(
"What is the license of this project? (leave empty for none)",
)
.with_initial_value("MIT")
.prompt()
.unwrap();
if !license.is_empty() {
mapping.insert_str("license", license);
}
let mut indices = mapping
.insert("indices", nondestructive::yaml::Separator::Auto)
.make_mapping();
indices.insert_str(
DEFAULT_INDEX_NAME,
read_config(project.data_dir())?.default_index.as_str(),
);
project.write_manifest(manifest.to_string())?;
println!("{}", "initialized project".green());
Ok(())
}
Err(e) => Err(e.into()),
}
}
}

13
src/cli/install.rs Normal file
View file

@ -0,0 +1,13 @@
use clap::Args;
use pesde::Project;
#[derive(Debug, Args)]
pub struct InstallCommand {}
impl InstallCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
dbg!(project.dependency_graph(None)?);
Ok(())
}
}

View file

@ -1,5 +1,3 @@
use clap::Subcommand;
use anyhow::Context; use anyhow::Context;
use keyring::Entry; use keyring::Entry;
use pesde::Project; use pesde::Project;
@ -8,6 +6,9 @@ use std::path::Path;
mod auth; mod auth;
mod config; mod config;
mod init;
mod install;
mod run;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliConfig { pub struct CliConfig {
@ -124,8 +125,8 @@ pub fn reqwest_client(data_dir: &Path) -> anyhow::Result<reqwest::blocking::Clie
.build()?) .build()?)
} }
#[derive(Debug, Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum SubCommand { pub enum Subcommand {
/// Authentication-related commands /// Authentication-related commands
#[command(subcommand)] #[command(subcommand)]
Auth(auth::AuthCommands), Auth(auth::AuthCommands),
@ -133,13 +134,25 @@ pub enum SubCommand {
/// Configuration-related commands /// Configuration-related commands
#[command(subcommand)] #[command(subcommand)]
Config(config::ConfigCommands), Config(config::ConfigCommands),
/// Initializes a manifest file in the current directory
Init(init::InitCommand),
/// Runs a script, an executable package, or a file with Lune
Run(run::RunCommand),
/// Installs all dependencies for the project
Install(install::InstallCommand),
} }
impl SubCommand { impl Subcommand {
pub fn run(self, project: Project) -> anyhow::Result<()> { pub fn run(self, project: Project) -> anyhow::Result<()> {
match self { match self {
SubCommand::Auth(auth) => auth.run(project), Subcommand::Auth(auth) => auth.run(project),
SubCommand::Config(config) => config.run(project), Subcommand::Config(config) => config.run(project),
Subcommand::Init(init) => init.run(project),
Subcommand::Run(run) => run.run(project),
Subcommand::Install(install) => install.run(project),
} }
} }
} }

48
src/cli/run.rs Normal file
View file

@ -0,0 +1,48 @@
use anyhow::Context;
use clap::Args;
use pesde::{
names::PackageName,
scripts::{execute_lune_script, execute_script},
Project,
};
use relative_path::RelativePathBuf;
#[derive(Debug, Args)]
pub struct RunCommand {
/// The package name, script name, or path to a script to run
#[arg(index = 1)]
package_or_script: String,
/// Arguments to pass to the script
#[arg(index = 2, last = true)]
args: Vec<String>,
}
impl RunCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
if let Ok(_pkg_name) = self.package_or_script.parse::<PackageName>() {
todo!("implement binary package execution")
}
if let Ok(manifest) = project.deser_manifest() {
if manifest.scripts.contains_key(&self.package_or_script) {
execute_script(&manifest, &self.package_or_script, &self.args)
.context("failed to execute script")?;
return Ok(());
}
};
let relative_path = RelativePathBuf::from(self.package_or_script);
let path = relative_path.to_path(project.path());
if !path.exists() {
anyhow::bail!("path does not exist: {}", path.display());
}
execute_lune_script(None, &relative_path, &self.args)
.context("failed to execute script")?;
Ok(())
}
}

View file

@ -3,16 +3,20 @@
#[cfg(not(any(feature = "roblox", feature = "lune", feature = "luau")))] #[cfg(not(any(feature = "roblox", feature = "lune", feature = "luau")))]
compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled"); compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled");
use crate::lockfile::Lockfile;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub mod lockfile; pub mod lockfile;
pub mod manifest; pub mod manifest;
pub mod names; pub mod names;
pub mod resolver;
pub mod scripts;
pub mod source; pub mod source;
pub const MANIFEST_FILE_NAME: &str = "pesde.yaml"; pub const MANIFEST_FILE_NAME: &str = "pesde.yaml";
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock"; pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";
pub const DEFAULT_INDEX_NAME: &str = "default";
pub(crate) static REQWEST_CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| { pub(crate) static REQWEST_CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| {
reqwest::blocking::Client::builder() reqwest::blocking::Client::builder()
@ -143,6 +147,17 @@ impl Project {
pub fn write_manifest<S: AsRef<[u8]>>(&self, manifest: S) -> Result<(), std::io::Error> { pub fn write_manifest<S: AsRef<[u8]>>(&self, manifest: S) -> Result<(), std::io::Error> {
std::fs::write(self.path.join(MANIFEST_FILE_NAME), manifest.as_ref()) std::fs::write(self.path.join(MANIFEST_FILE_NAME), manifest.as_ref())
} }
pub fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
let bytes = std::fs::read(self.path.join(LOCKFILE_FILE_NAME))?;
Ok(serde_yaml::from_slice(&bytes)?)
}
pub fn write_lockfile(&self, lockfile: Lockfile) -> Result<(), errors::LockfileWriteError> {
let writer = std::fs::File::create(self.path.join(LOCKFILE_FILE_NAME))?;
serde_yaml::to_writer(writer, &lockfile)?;
Ok(())
}
} }
pub mod errors { pub mod errors {
@ -157,4 +172,24 @@ pub mod errors {
#[error("error deserializing manifest file")] #[error("error deserializing manifest file")]
Serde(#[from] serde_yaml::Error), Serde(#[from] serde_yaml::Error),
} }
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LockfileReadError {
#[error("io error reading lockfile file")]
Io(#[from] std::io::Error),
#[error("error deserializing lockfile file")]
Serde(#[from] serde_yaml::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LockfileWriteError {
#[error("io error writing lockfile file")]
Io(#[from] std::io::Error),
#[error("error serializing lockfile file")]
Serde(#[from] serde_yaml::Error),
}
} }

View file

@ -1,20 +1,60 @@
use crate::{ use crate::{
manifest::{DependencyType, OverrideKey},
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::{DependencySpecifiers, PackageRefs}, source::{DependencySpecifiers, PackageRefs},
}; };
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::{btree_map::Entry, BTreeMap};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DependencyGraphNode {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direct: Option<(String, DependencySpecifiers)>,
pub pkg_ref: PackageRefs,
pub dependencies: BTreeMap<PackageNames, (Version, String)>,
pub ty: DependencyType,
}
pub type DependencyGraph = BTreeMap<PackageNames, BTreeMap<Version, DependencyGraphNode>>;
pub fn insert_node(
graph: &mut DependencyGraph,
name: PackageNames,
version: Version,
node: DependencyGraphNode,
) {
match graph
.entry(name.clone())
.or_default()
.entry(version.clone())
{
Entry::Vacant(entry) => {
entry.insert(node);
}
Entry::Occupied(existing) => {
let current_node = existing.into_mut();
match (&current_node.direct, &node.direct) {
(Some(_), Some(_)) => {
log::warn!("duplicate direct dependency for {name}@{version}",);
}
(None, Some(_)) => {
current_node.direct = node.direct;
}
(_, _) => {}
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lockfile { pub struct Lockfile {
pub name: PackageName, pub name: PackageName,
pub version: Version,
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
pub specifiers: BTreeMap<PackageNames, BTreeMap<Version, DependencySpecifiers>>, pub graph: DependencyGraph,
pub dependencies: BTreeMap<PackageNames, BTreeMap<Version, LockfileNode>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LockfileNode {
pub pkg_ref: PackageRefs,
} }

View file

@ -14,10 +14,12 @@ struct Cli {
version: (), version: (),
#[command(subcommand)] #[command(subcommand)]
subcommand: cli::SubCommand, subcommand: cli::Subcommand,
} }
fn main() { fn main() {
pretty_env_logger::init();
let project_dirs = let project_dirs =
directories::ProjectDirs::from("com", env!("CARGO_PKG_NAME"), env!("CARGO_BIN_NAME")) directories::ProjectDirs::from("com", env!("CARGO_PKG_NAME"), env!("CARGO_BIN_NAME"))
.expect("couldn't get home directory"); .expect("couldn't get home directory");

View file

@ -1,7 +1,6 @@
use crate::{names::PackageName, source::DependencySpecifiers};
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use semver::Version; use semver::Version;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay}; use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
@ -9,19 +8,11 @@ use std::{
str::FromStr, str::FromStr,
}; };
#[derive(Serialize, Deserialize, Debug, Clone, Default)] use crate::{names::PackageName, source::DependencySpecifiers};
#[serde(deny_unknown_fields)]
pub struct Exports {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lib: Option<RelativePathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bin: Option<RelativePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case", deny_unknown_fields)] #[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum Target { pub enum TargetKind {
#[cfg(feature = "roblox")] #[cfg(feature = "roblox")]
Roblox, Roblox,
#[cfg(feature = "lune")] #[cfg(feature = "lune")]
@ -30,33 +21,111 @@ pub enum Target {
Luau, Luau,
} }
impl Display for Target { impl Display for TargetKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
#[cfg(feature = "roblox")] #[cfg(feature = "roblox")]
Target::Roblox => write!(f, "roblox"), TargetKind::Roblox => write!(f, "roblox"),
#[cfg(feature = "lune")] #[cfg(feature = "lune")]
Target::Lune => write!(f, "lune"), TargetKind::Lune => write!(f, "lune"),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Target::Luau => write!(f, "luau"), TargetKind::Luau => write!(f, "luau"),
} }
} }
} }
impl Target { impl TargetKind {
// self is the project's target, dependency is the target of the dependency // self is the project's target, dependency is the target of the dependency
fn is_compatible_with(&self, dependency: &Self) -> bool { pub fn is_compatible_with(&self, dependency: &Self) -> bool {
if self == dependency { if self == dependency {
return true; return true;
} }
match (self, dependency) { match (self, dependency) {
#[cfg(all(feature = "lune", feature = "luau"))] #[cfg(all(feature = "lune", feature = "luau"))]
(Target::Lune, Target::Luau) => true, (TargetKind::Lune, TargetKind::Luau) => true,
_ => false, _ => false,
} }
} }
pub fn packages_folder(&self, dependency: &Self) -> String {
if self == dependency {
return "packages".to_string();
}
format!("{}_packages", dependency)
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[serde(rename_all = "snake_case", tag = "environment", remote = "Self")]
pub enum Target {
#[cfg(feature = "roblox")]
Roblox { lib: RelativePathBuf },
#[cfg(feature = "lune")]
Lune {
lib: Option<RelativePathBuf>,
bin: Option<RelativePathBuf>,
},
#[cfg(feature = "luau")]
Luau {
lib: Option<RelativePathBuf>,
bin: Option<RelativePathBuf>,
},
}
impl<'de> Deserialize<'de> for Target {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let target = Self::deserialize(deserializer)?;
match &target {
#[cfg(feature = "lune")]
Target::Lune { lib, bin } => {
if lib.is_none() && bin.is_none() {
return Err(serde::de::Error::custom(
"one of `lib` or `bin` exports must be defined",
));
}
}
#[cfg(feature = "luau")]
Target::Luau { lib, bin } => {
if lib.is_none() && bin.is_none() {
return Err(serde::de::Error::custom(
"one of `lib` or `bin` exports must be defined",
));
}
}
#[allow(unreachable_patterns)]
_ => {}
};
Ok(target)
}
}
impl Display for Target {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.kind())
}
}
impl Target {
pub fn kind(&self) -> TargetKind {
match self {
#[cfg(feature = "roblox")]
Target::Roblox { .. } => TargetKind::Roblox,
#[cfg(feature = "lune")]
Target::Lune { .. } => TargetKind::Lune,
#[cfg(feature = "luau")]
Target::Luau { .. } => TargetKind::Luau,
}
}
} }
#[derive( #[derive(
@ -68,11 +137,16 @@ impl FromStr for OverrideKey {
type Err = errors::OverrideKeyFromStr; type Err = errors::OverrideKeyFromStr;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self( let overrides = s
s.split(',') .split(',')
.map(|overrides| overrides.split('>').map(|s| s.to_string()).collect()) .map(|overrides| overrides.split('>').map(|s| s.to_string()).collect())
.collect(), .collect::<Vec<Vec<String>>>();
))
if overrides.is_empty() {
return Err(errors::OverrideKeyFromStr::Empty);
}
Ok(Self(overrides))
} }
} }
@ -96,40 +170,7 @@ impl Display for OverrideKey {
} }
} }
fn deserialize_dep_specs<'de, D>( #[derive(Deserialize, Debug, Clone)]
deserializer: D,
) -> Result<BTreeMap<String, DependencySpecifiers>, D::Error>
where
D: Deserializer<'de>,
{
struct SpecsVisitor;
impl<'de> Visitor<'de> for SpecsVisitor {
type Value = BTreeMap<String, DependencySpecifiers>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a map of dependency specifiers")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut specs = BTreeMap::new();
while let Some((key, mut value)) = map.next_entry::<String, DependencySpecifiers>()? {
value.set_alias(key.to_string());
specs.insert(key, value);
}
Ok(specs)
}
}
deserializer.deserialize_map(SpecsVisitor)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Manifest { pub struct Manifest {
pub name: PackageName, pub name: PackageName,
pub version: Version, pub version: Version,
@ -141,34 +182,77 @@ pub struct Manifest {
pub authors: Option<Vec<String>>, pub authors: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
pub repository: Option<String>, pub repository: Option<String>,
#[serde(default)]
pub exports: Exports,
pub target: Target, pub target: Target,
#[serde(default)] #[serde(default)]
pub private: bool, pub private: bool,
#[serde(default)] #[serde(default)]
pub scripts: BTreeMap<String, RelativePathBuf>,
#[serde(default)]
pub indices: BTreeMap<String, url::Url>, pub indices: BTreeMap<String, url::Url>,
#[cfg(feature = "wally")] #[cfg(feature = "wally")]
#[serde(default)] #[serde(default)]
pub wally_indices: BTreeMap<String, url::Url>, pub wally_indices: BTreeMap<String, url::Url>,
#[cfg(feature = "wally")] #[cfg(all(feature = "wally", feature = "roblox"))]
#[serde(default)] #[serde(default)]
pub sourcemap_generator: Option<String>, pub sourcemap_generator: Option<String>,
#[serde(default)] #[serde(default)]
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>, pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
#[serde(default, deserialize_with = "deserialize_dep_specs")] #[serde(default)]
pub dependencies: BTreeMap<String, DependencySpecifiers>, pub dependencies: BTreeMap<String, DependencySpecifiers>,
#[serde(default, deserialize_with = "deserialize_dep_specs")] #[serde(default)]
pub peer_dependencies: BTreeMap<String, DependencySpecifiers>, pub peer_dependencies: BTreeMap<String, DependencySpecifiers>,
#[serde(default, deserialize_with = "deserialize_dep_specs")] #[serde(default)]
pub dev_dependencies: BTreeMap<String, DependencySpecifiers>, pub dev_dependencies: BTreeMap<String, DependencySpecifiers>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum DependencyType {
Standard,
Dev,
Peer,
}
impl Manifest {
pub fn all_dependencies(
&self,
) -> Result<
BTreeMap<String, (DependencySpecifiers, DependencyType)>,
errors::AllDependenciesError,
> {
let mut all_deps = BTreeMap::new();
for (deps, ty) in [
(&self.dependencies, DependencyType::Standard),
(&self.peer_dependencies, DependencyType::Peer),
(&self.dev_dependencies, DependencyType::Dev),
] {
for (alias, spec) in deps {
if all_deps.insert(alias.clone(), (spec.clone(), ty)).is_some() {
return Err(errors::AllDependenciesError::AliasConflict(alias.clone()));
}
}
}
Ok(all_deps)
}
}
pub mod errors { pub mod errors {
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[non_exhaustive] #[non_exhaustive]
pub enum OverrideKeyFromStr {} pub enum OverrideKeyFromStr {
#[error("empty override key")]
Empty,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AllDependenciesError {
#[error("another specifier is already using the alias {0}")]
AliasConflict(String),
}
} }

View file

@ -70,6 +70,14 @@ pub enum PackageNames {
Pesde(PackageName), Pesde(PackageName),
} }
impl Display for PackageNames {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PackageNames::Pesde(name) => write!(f, "{}", name),
}
}
}
pub mod errors { pub mod errors {
use thiserror::Error; use thiserror::Error;

284
src/resolver.rs Normal file
View file

@ -0,0 +1,284 @@
use crate::{
lockfile::{insert_node, DependencyGraph, DependencyGraphNode},
manifest::DependencyType,
names::PackageNames,
source::{
pesde::PesdePackageSource, DependencySpecifiers, PackageRef, PackageSource, PackageSources,
},
Project, DEFAULT_INDEX_NAME,
};
use semver::Version;
use std::collections::{HashMap, HashSet, VecDeque};
impl Project {
// TODO: implement dependency overrides
pub fn dependency_graph(
&self,
previous_graph: Option<&DependencyGraph>,
) -> Result<DependencyGraph, Box<errors::DependencyGraphError>> {
let manifest = self.deser_manifest().map_err(|e| Box::new(e.into()))?;
let mut all_dependencies = manifest
.all_dependencies()
.map_err(|e| Box::new(e.into()))?;
let mut all_specifiers = all_dependencies
.clone()
.into_iter()
.map(|(alias, (spec, ty))| ((spec, ty), alias))
.collect::<HashMap<_, _>>();
let mut graph = DependencyGraph::default();
if let Some(previous_graph) = previous_graph {
for (name, versions) in previous_graph {
for (version, node) in versions {
let Some((_, specifier)) = &node.direct else {
// this is not a direct dependency, will be added if it's still being used later
continue;
};
match all_specifiers.remove(&(specifier.clone(), node.ty)) {
Some(alias) => {
all_dependencies.remove(&alias);
}
None => {
// this dependency is no longer in the manifest, or it's type has changed
continue;
}
}
log::debug!("resolved {}@{} from old dependency graph", name, version);
insert_node(&mut graph, name.clone(), version.clone(), node.clone());
let mut queue = node
.dependencies
.iter()
.map(|(name, (version, _))| (name, version, 0usize))
.collect::<VecDeque<_>>();
while let Some((dep_name, dep_version, depth)) = queue.pop_front() {
if let Some(dep_node) = previous_graph
.get(dep_name)
.and_then(|v| v.get(dep_version))
{
log::debug!(
"{}resolved dependency {}@{} from {}@{}",
"\t".repeat(depth),
dep_name,
dep_version,
name,
version
);
insert_node(
&mut graph,
dep_name.clone(),
dep_version.clone(),
dep_node.clone(),
);
dep_node
.dependencies
.iter()
.map(|(name, (version, _))| (name, version, depth + 1))
.for_each(|dep| queue.push_back(dep));
} else {
log::warn!(
"dependency {}@{} from {}@{} not found in previous graph",
dep_name,
dep_version,
name,
version
);
}
}
}
}
}
let mut refreshed_sources = HashSet::new();
let mut queue = all_dependencies
.into_iter()
.map(|(alias, (spec, ty))| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize))
.collect::<VecDeque<_>>();
while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() {
log::debug!(
"{}resolving {specifier} ({alias}) from {dependant:?}",
"\t".repeat(depth)
);
let source = match &specifier {
DependencySpecifiers::Pesde(specifier) => {
let index_url = if depth == 0 {
let index_name = specifier.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
let index_url = manifest.indices.get(index_name).ok_or(
errors::DependencyGraphError::IndexNotFound(index_name.to_string()),
)?;
match index_url.as_str().try_into() {
Ok(url) => url,
Err(e) => {
return Err(Box::new(errors::DependencyGraphError::UrlParse(
index_url.clone(),
e,
)))
}
}
} else {
let index_url = specifier.index.clone().unwrap();
index_url
.clone()
.try_into()
.map_err(|e| errors::DependencyGraphError::InvalidIndex(index_url, e))?
};
PackageSources::Pesde(PesdePackageSource::new(index_url))
}
};
if refreshed_sources.insert(source.clone()) {
source.refresh(self).map_err(|e| Box::new(e.into()))?;
}
let (name, resolved) = source
.resolve(&specifier, self)
.map_err(|e| Box::new(e.into()))?;
let Some(target_version) = graph
.get(&name)
.and_then(|versions| {
versions
.keys()
// only consider versions that are compatible with the specifier
.filter(|ver| resolved.contains_key(ver))
.max()
})
.or_else(|| resolved.last_key_value().map(|(ver, _)| ver))
.cloned()
else {
log::warn!(
"{}could not find any version for {specifier} ({alias})",
"\t".repeat(depth)
);
continue;
};
let ty = if depth == 0 && ty == DependencyType::Peer {
DependencyType::Standard
} else {
ty
};
if let Some((dependant_name, dependant_version)) = dependant {
graph
.get_mut(&dependant_name)
.and_then(|versions| versions.get_mut(&dependant_version))
.and_then(|node| {
node.dependencies
.insert(name.clone(), (target_version.clone(), alias.clone()))
});
}
if let Some(already_resolved) = graph
.get_mut(&name)
.and_then(|versions| versions.get_mut(&target_version))
{
log::debug!(
"{}{}@{} already resolved",
"\t".repeat(depth),
name,
target_version
);
if already_resolved.ty == DependencyType::Peer && ty == DependencyType::Standard {
already_resolved.ty = ty;
}
continue;
}
let pkg_ref = &resolved[&target_version];
let node = DependencyGraphNode {
direct: if depth == 0 {
Some((alias.clone(), specifier.clone()))
} else {
None
},
pkg_ref: pkg_ref.clone(),
dependencies: Default::default(),
ty,
};
insert_node(
&mut graph,
name.clone(),
target_version.clone(),
node.clone(),
);
log::debug!(
"{}resolved {}@{} from new dependency graph",
"\t".repeat(depth),
name,
target_version
);
for (dependency_alias, (dependency_spec, dependency_ty)) in
pkg_ref.dependencies().clone()
{
if dependency_ty == DependencyType::Dev {
// dev dependencies of dependencies are not included in the graph
// they should not even be stored in the index, so this is just a check to avoid potential issues
continue;
}
queue.push_back((
dependency_alias,
dependency_spec,
dependency_ty,
Some((name.clone(), target_version.clone())),
depth + 1,
));
}
}
for (name, versions) in &graph {
for (version, node) in versions {
if node.ty == DependencyType::Peer {
log::warn!("peer dependency {name}@{version} was not resolved");
}
}
}
Ok(graph)
}
}
pub mod errors {
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DependencyGraphError {
#[error("failed to deserialize manifest")]
ManifestRead(#[from] crate::errors::ManifestReadError),
#[error("error getting all project dependencies")]
AllDependencies(#[from] crate::manifest::errors::AllDependenciesError),
#[error("index named {0} not found in manifest")]
IndexNotFound(String),
#[error("error parsing url {0} into git url")]
UrlParse(url::Url, #[source] gix::url::parse::Error),
#[error("index {0} cannot be parsed as a git url")]
InvalidIndex(String, #[source] gix::url::parse::Error),
#[error("error refreshing package source")]
Refresh(#[from] crate::source::errors::RefreshError),
#[error("error resolving package")]
Resolve(#[from] crate::source::errors::ResolveError),
}
}

80
src/scripts.rs Normal file
View file

@ -0,0 +1,80 @@
use crate::manifest::Manifest;
use relative_path::RelativePathBuf;
use std::{
ffi::OsStr,
io::{BufRead, BufReader},
process::{Command, Stdio},
thread::spawn,
};
pub fn execute_lune_script<A: IntoIterator<Item = S>, S: AsRef<OsStr>>(
script_name: Option<&str>,
script_path: &RelativePathBuf,
args: A,
) -> Result<(), std::io::Error> {
match Command::new("lune")
.arg("run")
.arg(script_path.as_str())
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(mut child) => {
let stdout = BufReader::new(child.stdout.take().unwrap());
let stderr = BufReader::new(child.stderr.take().unwrap());
let script = match script_name {
Some(script) => script.to_string(),
None => script_path.to_string(),
};
let script_2 = script.to_string();
spawn(move || {
for line in stderr.lines() {
match line {
Ok(line) => {
log::error!("[{script}]: {line}");
}
Err(e) => {
log::error!("ERROR IN READING STDERR OF {script}: {e}");
break;
}
}
}
});
for line in stdout.lines() {
match line {
Ok(line) => {
log::info!("[{script_2}]: {line}");
}
Err(e) => {
log::error!("ERROR IN READING STDOUT OF {script_2}: {e}");
break;
}
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
log::warn!("Lune could not be found in PATH: {e}")
}
Err(e) => return Err(e),
};
Ok(())
}
pub fn execute_script<A: IntoIterator<Item = S>, S: AsRef<OsStr>>(
manifest: &Manifest,
script: &str,
args: A,
) -> Result<(), std::io::Error> {
if let Some(script_path) = manifest.scripts.get(script) {
return execute_lune_script(Some(script), script_path, args);
}
Ok(())
}

View file

@ -1,28 +1,59 @@
use std::{collections::BTreeMap, fmt::Debug, path::Path}; use crate::{manifest::DependencyType, names::PackageNames, Project};
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{
use crate::Project; collections::BTreeMap,
fmt::{Debug, Display},
path::Path,
};
pub mod pesde; pub mod pesde;
pub trait DependencySpecifier: Debug {
fn alias(&self) -> &str;
fn set_alias(&mut self, alias: String);
}
pub trait PackageRef: Debug {}
pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String { pub(crate) fn hash<S: std::hash::Hash>(struc: &S) -> String {
use std::collections::hash_map::DefaultHasher; use std::{collections::hash_map::DefaultHasher, hash::Hasher};
use std::hash::Hasher;
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
struc.hash(&mut hasher); struc.hash(&mut hasher);
hasher.finish().to_string() hasher.finish().to_string()
} }
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum DependencySpecifiers {
Pesde(pesde::PesdeDependencySpecifier),
}
pub trait DependencySpecifier: Debug + Display {}
impl DependencySpecifier for DependencySpecifiers {}
impl Display for DependencySpecifiers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DependencySpecifiers::Pesde(specifier) => write!(f, "{}", specifier),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum PackageRefs {
Pesde(pesde::PesdePackageRef),
}
pub trait PackageRef: Debug {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)>;
}
impl PackageRef for PackageRefs {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
match self {
PackageRefs::Pesde(pkg_ref) => pkg_ref.dependencies(),
}
}
}
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<Version, Ref>);
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum PackageSources {
Pesde(pesde::PesdePackageSource),
}
pub trait PackageSource: Debug { pub trait PackageSource: Debug {
type Ref: PackageRef; type Ref: PackageRef;
type Specifier: DependencySpecifier; type Specifier: DependencySpecifier;
@ -38,7 +69,7 @@ pub trait PackageSource: Debug {
&self, &self,
specifier: &Self::Specifier, specifier: &Self::Specifier,
project: &Project, project: &Project,
) -> Result<BTreeMap<Version, Self::Ref>, Self::ResolveError>; ) -> Result<ResolveResult<Self::Ref>, Self::ResolveError>;
fn download( fn download(
&self, &self,
@ -47,33 +78,85 @@ pub trait PackageSource: Debug {
project: &Project, project: &Project,
) -> Result<(), Self::DownloadError>; ) -> Result<(), Self::DownloadError>;
} }
impl PackageSource for PackageSources {
type Ref = PackageRefs;
type Specifier = DependencySpecifiers;
type RefreshError = errors::RefreshError;
type ResolveError = errors::ResolveError;
type DownloadError = errors::DownloadError;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] fn refresh(&self, project: &Project) -> Result<(), Self::RefreshError> {
#[serde(untagged)]
pub enum DependencySpecifiers {
Pesde(pesde::PesdeDependencySpecifier),
}
impl DependencySpecifiers {
pub fn alias(&self) -> &str {
match self { match self {
DependencySpecifiers::Pesde(spec) => spec.alias(), PackageSources::Pesde(source) => source.refresh(project).map_err(Into::into),
} }
} }
pub fn set_alias(&mut self, alias: String) { fn resolve(
match self { &self,
DependencySpecifiers::Pesde(spec) => spec.set_alias(alias), specifier: &Self::Specifier,
project: &Project,
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
match (self, specifier) {
(PackageSources::Pesde(source), DependencySpecifiers::Pesde(specifier)) => source
.resolve(specifier, project)
.map(|(name, results)| {
(
name,
results
.into_iter()
.map(|(version, pkg_ref)| (version, PackageRefs::Pesde(pkg_ref)))
.collect(),
)
})
.map_err(Into::into),
_ => Err(errors::ResolveError::Mismatch),
}
}
fn download(
&self,
pkg_ref: &Self::Ref,
destination: &Path,
project: &Project,
) -> Result<(), Self::DownloadError> {
match (self, pkg_ref) {
(PackageSources::Pesde(source), PackageRefs::Pesde(pkg_ref)) => source
.download(pkg_ref, destination, project)
.map_err(Into::into),
_ => Err(errors::DownloadError::Mismatch),
} }
} }
} }
#[derive(Debug, Serialize, Deserialize, Clone)] pub mod errors {
pub enum PackageRefs { use thiserror::Error;
Pesde(pesde::PesdePackageRef),
}
#[derive(Debug, Eq, PartialEq, Hash)] #[derive(Debug, Error)]
pub enum PackageSources { #[non_exhaustive]
Pesde(pesde::PesdePackageSource), pub enum RefreshError {
#[error("error refreshing pesde package source")]
Pesde(#[from] crate::source::pesde::errors::RefreshError),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ResolveError {
#[error("mismatched dependency specifier for source")]
Mismatch,
#[error("error resolving pesde package")]
Pesde(#[from] crate::source::pesde::errors::ResolveError),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DownloadError {
#[error("mismatched package ref for source")]
Mismatch,
#[error("error downloading pesde package")]
Pesde(#[from] crate::source::pesde::errors::DownloadError),
}
} }

View file

@ -1,4 +1,9 @@
use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::Path}; use std::{
collections::BTreeMap,
fmt::{Debug, Display},
hash::Hash,
path::Path,
};
use gix::remote::Direction; use gix::remote::Direction;
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
@ -6,9 +11,11 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
authenticate_conn, authenticate_conn,
manifest::Target, manifest::{DependencyType, TargetKind},
names::PackageName, names::{PackageName, PackageNames},
source::{hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource}, source::{
hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource, ResolveResult,
},
Project, REQWEST_CLIENT, Project, REQWEST_CLIENT,
}; };
@ -16,17 +23,14 @@ use crate::{
pub struct PesdeDependencySpecifier { pub struct PesdeDependencySpecifier {
pub name: PackageName, pub name: PackageName,
pub version: VersionReq, pub version: VersionReq,
#[serde(default, skip_serializing_if = "String::is_empty")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: String, pub index: Option<String>,
} }
impl DependencySpecifier for PesdeDependencySpecifier {}
impl DependencySpecifier for PesdeDependencySpecifier { impl Display for PesdeDependencySpecifier {
fn alias(&self) -> &str { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.alias.as_str() write!(f, "{}@{}", self.name, self.version)
}
fn set_alias(&mut self, alias: String) {
self.alias = alias;
} }
} }
@ -34,9 +38,14 @@ impl DependencySpecifier for PesdeDependencySpecifier {
pub struct PesdePackageRef { pub struct PesdePackageRef {
name: PackageName, name: PackageName,
version: Version, version: Version,
index_url: gix::Url,
dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
}
impl PackageRef for PesdePackageRef {
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
&self.dependencies
}
} }
impl PackageRef for PesdePackageRef {}
impl Ord for PesdePackageRef { impl Ord for PesdePackageRef {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
@ -50,7 +59,7 @@ impl PartialOrd for PesdePackageRef {
} }
} }
#[derive(Debug, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct PesdePackageSource { pub struct PesdePackageSource {
repo_url: gix::Url, repo_url: gix::Url,
} }
@ -326,30 +335,35 @@ impl PackageSource for PesdePackageSource {
&self, &self,
specifier: &Self::Specifier, specifier: &Self::Specifier,
project: &Project, project: &Project,
) -> Result<BTreeMap<Version, Self::Ref>, Self::ResolveError> { ) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
let (scope, name) = specifier.name.as_str(); let (scope, name) = specifier.name.as_str();
let bytes = match self.read_file([scope, name], project) { let bytes = match self.read_file([scope, name], project) {
Ok(Some(bytes)) => bytes, Ok(Some(bytes)) => bytes,
Ok(None) => return Ok(BTreeMap::new()), Ok(None) => return Err(Self::ResolveError::NotFound(specifier.name.to_string())),
Err(e) => return Err(Self::ResolveError::Read(specifier.name.to_string(), e)), Err(e) => return Err(Self::ResolveError::Read(specifier.name.to_string(), e)),
}; };
let entries: Vec<IndexFileEntry> = serde_yaml::from_slice(&bytes) let entries: Vec<IndexFileEntry> = serde_yaml::from_slice(&bytes)
.map_err(|e| Self::ResolveError::Parse(specifier.name.to_string(), e))?; .map_err(|e| Self::ResolveError::Parse(specifier.name.to_string(), e))?;
Ok(entries Ok((
.into_iter() PackageNames::Pesde(specifier.name.clone()),
.filter(|entry| specifier.version.matches(&entry.version)) entries
.map(|entry| { .into_iter()
( .filter(|entry| specifier.version.matches(&entry.version))
entry.version.clone(), .map(|entry| {
PesdePackageRef { (
name: specifier.name.clone(), entry.version.clone(),
version: entry.version, PesdePackageRef {
}, name: specifier.name.clone(),
) version: entry.version,
}) index_url: self.repo_url.clone(),
.collect()) dependencies: entry.dependencies,
},
)
})
.collect(),
))
} }
fn download( fn download(
@ -418,15 +432,15 @@ impl IndexConfig {
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct IndexFileEntry { pub struct IndexFileEntry {
pub version: Version, pub version: Version,
pub target: Target, pub target: TargetKind,
#[serde(default = "chrono::Utc::now")] #[serde(default = "chrono::Utc::now")]
pub published_at: chrono::DateTime<chrono::Utc>, pub published_at: chrono::DateTime<chrono::Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: Vec<DependencySpecifiers>, pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
} }
impl Ord for IndexFileEntry { impl Ord for IndexFileEntry {
@ -457,28 +471,28 @@ pub mod errors {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("error opening repository at {0}")] #[error("error opening repository at {0}")]
Open(PathBuf, gix::open::Error), Open(PathBuf, #[source] gix::open::Error),
#[error("no default remote found in repository at {0}")] #[error("no default remote found in repository at {0}")]
NoDefaultRemote(PathBuf), NoDefaultRemote(PathBuf),
#[error("error getting default remote from repository at {0}")] #[error("error getting default remote from repository at {0}")]
GetDefaultRemote(PathBuf, gix::remote::find::existing::Error), GetDefaultRemote(PathBuf, #[source] gix::remote::find::existing::Error),
#[error("error connecting to remote repository at {0}")] #[error("error connecting to remote repository at {0}")]
Connect(gix::Url, gix::remote::connect::Error), Connect(gix::Url, #[source] gix::remote::connect::Error),
#[error("error preparing fetch from remote repository at {0}")] #[error("error preparing fetch from remote repository at {0}")]
PrepareFetch(gix::Url, gix::remote::fetch::prepare::Error), PrepareFetch(gix::Url, #[source] gix::remote::fetch::prepare::Error),
#[error("error reading from remote repository at {0}")] #[error("error reading from remote repository at {0}")]
Read(gix::Url, gix::remote::fetch::Error), Read(gix::Url, #[source] gix::remote::fetch::Error),
#[error("error cloning repository from {0}")] #[error("error cloning repository from {0}")]
Clone(gix::Url, gix::clone::Error), Clone(gix::Url, #[source] gix::clone::Error),
#[error("error fetching repository from {0}")] #[error("error fetching repository from {0}")]
Fetch(gix::Url, gix::clone::fetch::Error), Fetch(gix::Url, #[source] gix::clone::fetch::Error),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -488,13 +502,13 @@ pub mod errors {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("error opening repository at {0}")] #[error("error opening repository at {0}")]
Open(PathBuf, gix::open::Error), Open(PathBuf, #[source] gix::open::Error),
#[error("no default remote found in repository at {0}")] #[error("no default remote found in repository at {0}")]
NoDefaultRemote(PathBuf), NoDefaultRemote(PathBuf),
#[error("error getting default remote from repository at {0}")] #[error("error getting default remote from repository at {0}")]
GetDefaultRemote(PathBuf, gix::remote::find::existing::Error), GetDefaultRemote(PathBuf, #[source] gix::remote::find::existing::Error),
#[error("no refspecs found in repository at {0}")] #[error("no refspecs found in repository at {0}")]
NoRefSpecs(PathBuf), NoRefSpecs(PathBuf),
@ -503,29 +517,29 @@ pub mod errors {
NoLocalRefSpec(PathBuf), NoLocalRefSpec(PathBuf),
#[error("no reference found for local refspec {0}")] #[error("no reference found for local refspec {0}")]
NoReference(String, gix::reference::find::existing::Error), NoReference(String, #[source] gix::reference::find::existing::Error),
#[error("cannot peel reference {0}")] #[error("cannot peel reference {0}")]
CannotPeel(String, gix::reference::peel::Error), CannotPeel(String, #[source] gix::reference::peel::Error),
#[error("error converting id {0} to object")] #[error("error converting id {0} to object")]
CannotConvertToObject(String, gix::object::find::existing::Error), CannotConvertToObject(String, #[source] gix::object::find::existing::Error),
#[error("error peeling object {0} to tree")] #[error("error peeling object {0} to tree")]
CannotPeelToTree(String, gix::object::peel::to_kind::Error), CannotPeelToTree(String, #[source] gix::object::peel::to_kind::Error),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[non_exhaustive] #[non_exhaustive]
pub enum ReadFile { pub enum ReadFile {
#[error("error opening repository at {0}")] #[error("error opening repository at {0}")]
Open(PathBuf, gix::open::Error), Open(PathBuf, #[source] gix::open::Error),
#[error("error getting tree from repository at {0}")] #[error("error getting tree from repository at {0}")]
Tree(PathBuf, Box<TreeError>), Tree(PathBuf, #[source] Box<TreeError>),
#[error("error looking up entry {0} in tree")] #[error("error looking up entry {0} in tree")]
Lookup(String, gix::object::find::existing::Error), Lookup(String, #[source] gix::object::find::existing::Error),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -534,11 +548,14 @@ pub mod errors {
#[error("error interacting with the filesystem")] #[error("error interacting with the filesystem")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("package {0} not found")]
NotFound(String),
#[error("error reading file for {0}")] #[error("error reading file for {0}")]
Read(String, Box<ReadFile>), Read(String, #[source] Box<ReadFile>),
#[error("error parsing file for {0}")] #[error("error parsing file for {0}")]
Parse(String, serde_yaml::Error), Parse(String, #[source] serde_yaml::Error),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -558,19 +575,19 @@ pub mod errors {
#[non_exhaustive] #[non_exhaustive]
pub enum AllPackagesError { pub enum AllPackagesError {
#[error("error opening repository at {0}")] #[error("error opening repository at {0}")]
Open(PathBuf, gix::open::Error), Open(PathBuf, #[source] gix::open::Error),
#[error("error getting tree from repository at {0}")] #[error("error getting tree from repository at {0}")]
Tree(PathBuf, Box<TreeError>), Tree(PathBuf, #[source] Box<TreeError>),
#[error("error decoding entry in repository at {0}")] #[error("error decoding entry in repository at {0}")]
Decode(PathBuf, gix::objs::decode::Error), Decode(PathBuf, #[source] gix::objs::decode::Error),
#[error("error converting entry in repository at {0}")] #[error("error converting entry in repository at {0}")]
Convert(PathBuf, gix::object::find::existing::Error), Convert(PathBuf, #[source] gix::object::find::existing::Error),
#[error("error deserializing file {0} in repository at {1}")] #[error("error deserializing file {0} in repository at {1}")]
Deserialize(String, PathBuf, serde_yaml::Error), Deserialize(String, PathBuf, #[source] serde_yaml::Error),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]