mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: implement dependency resolver and lune scripts
This commit is contained in:
parent
b73bf418c5
commit
fdad8995a4
15 changed files with 994 additions and 173 deletions
2
.github/workflows/test-and-lint.yaml
vendored
2
.github/workflows/test-and-lint.yaml
vendored
|
@ -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
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
imports_granularity = "Crate"
|
113
src/cli/init.rs
Normal file
113
src/cli/init.rs
Normal 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
13
src/cli/install.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
48
src/cli/run.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
35
src/lib.rs
35
src/lib.rs
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (¤t_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,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
216
src/manifest.rs
216
src/manifest.rs
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
284
src/resolver.rs
Normal 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
80
src/scripts.rs
Normal 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(())
|
||||||
|
}
|
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in a new issue