mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 02:50:37 +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
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --all
|
||||
run: cargo test --all --all-features
|
||||
|
||||
- name: Check formatting
|
||||
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 keyring::Entry;
|
||||
use pesde::Project;
|
||||
|
@ -8,6 +6,9 @@ use std::path::Path;
|
|||
|
||||
mod auth;
|
||||
mod config;
|
||||
mod init;
|
||||
mod install;
|
||||
mod run;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CliConfig {
|
||||
|
@ -124,8 +125,8 @@ pub fn reqwest_client(data_dir: &Path) -> anyhow::Result<reqwest::blocking::Clie
|
|||
.build()?)
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum SubCommand {
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Subcommand {
|
||||
/// Authentication-related commands
|
||||
#[command(subcommand)]
|
||||
Auth(auth::AuthCommands),
|
||||
|
@ -133,13 +134,25 @@ pub enum SubCommand {
|
|||
/// Configuration-related commands
|
||||
#[command(subcommand)]
|
||||
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<()> {
|
||||
match self {
|
||||
SubCommand::Auth(auth) => auth.run(project),
|
||||
SubCommand::Config(config) => config.run(project),
|
||||
Subcommand::Auth(auth) => auth.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")))]
|
||||
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 std::path::{Path, PathBuf};
|
||||
|
||||
pub mod lockfile;
|
||||
pub mod manifest;
|
||||
pub mod names;
|
||||
pub mod resolver;
|
||||
pub mod scripts;
|
||||
pub mod source;
|
||||
|
||||
pub const MANIFEST_FILE_NAME: &str = "pesde.yaml";
|
||||
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(|| {
|
||||
reqwest::blocking::Client::builder()
|
||||
|
@ -143,6 +147,17 @@ impl Project {
|
|||
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())
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -157,4 +172,24 @@ pub mod errors {
|
|||
#[error("error deserializing manifest file")]
|
||||
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::{
|
||||
manifest::{DependencyType, OverrideKey},
|
||||
names::{PackageName, PackageNames},
|
||||
source::{DependencySpecifiers, PackageRefs},
|
||||
};
|
||||
use semver::Version;
|
||||
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)]
|
||||
pub struct Lockfile {
|
||||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
|
||||
|
||||
pub specifiers: BTreeMap<PackageNames, BTreeMap<Version, DependencySpecifiers>>,
|
||||
pub dependencies: BTreeMap<PackageNames, BTreeMap<Version, LockfileNode>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct LockfileNode {
|
||||
pub pkg_ref: PackageRefs,
|
||||
pub graph: DependencyGraph,
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@ struct Cli {
|
|||
version: (),
|
||||
|
||||
#[command(subcommand)]
|
||||
subcommand: cli::SubCommand,
|
||||
subcommand: cli::Subcommand,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let project_dirs =
|
||||
directories::ProjectDirs::from("com", env!("CARGO_PKG_NAME"), env!("CARGO_BIN_NAME"))
|
||||
.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 semver::Version;
|
||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
|
@ -9,19 +8,11 @@ use std::{
|
|||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[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>,
|
||||
}
|
||||
use crate::{names::PackageName, source::DependencySpecifiers};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Target {
|
||||
pub enum TargetKind {
|
||||
#[cfg(feature = "roblox")]
|
||||
Roblox,
|
||||
#[cfg(feature = "lune")]
|
||||
|
@ -30,33 +21,111 @@ pub enum Target {
|
|||
Luau,
|
||||
}
|
||||
|
||||
impl Display for Target {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl Display for TargetKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
#[cfg(feature = "roblox")]
|
||||
Target::Roblox => write!(f, "roblox"),
|
||||
TargetKind::Roblox => write!(f, "roblox"),
|
||||
#[cfg(feature = "lune")]
|
||||
Target::Lune => write!(f, "lune"),
|
||||
TargetKind::Lune => write!(f, "lune"),
|
||||
#[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
|
||||
fn is_compatible_with(&self, dependency: &Self) -> bool {
|
||||
pub fn is_compatible_with(&self, dependency: &Self) -> bool {
|
||||
if self == dependency {
|
||||
return true;
|
||||
}
|
||||
|
||||
match (self, dependency) {
|
||||
#[cfg(all(feature = "lune", feature = "luau"))]
|
||||
(Target::Lune, Target::Luau) => true,
|
||||
(TargetKind::Lune, TargetKind::Luau) => true,
|
||||
|
||||
_ => 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(
|
||||
|
@ -68,11 +137,16 @@ impl FromStr for OverrideKey {
|
|||
type Err = errors::OverrideKeyFromStr;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(
|
||||
s.split(',')
|
||||
.map(|overrides| overrides.split('>').map(|s| s.to_string()).collect())
|
||||
.collect(),
|
||||
))
|
||||
let overrides = s
|
||||
.split(',')
|
||||
.map(|overrides| overrides.split('>').map(|s| s.to_string()).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>(
|
||||
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)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Manifest {
|
||||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
|
@ -141,34 +182,77 @@ pub struct Manifest {
|
|||
pub authors: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub repository: Option<String>,
|
||||
#[serde(default)]
|
||||
pub exports: Exports,
|
||||
pub target: Target,
|
||||
#[serde(default)]
|
||||
pub private: bool,
|
||||
#[serde(default)]
|
||||
pub scripts: BTreeMap<String, RelativePathBuf>,
|
||||
#[serde(default)]
|
||||
pub indices: BTreeMap<String, url::Url>,
|
||||
#[cfg(feature = "wally")]
|
||||
#[serde(default)]
|
||||
pub wally_indices: BTreeMap<String, url::Url>,
|
||||
#[cfg(feature = "wally")]
|
||||
#[cfg(all(feature = "wally", feature = "roblox"))]
|
||||
#[serde(default)]
|
||||
pub sourcemap_generator: Option<String>,
|
||||
#[serde(default)]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
|
||||
|
||||
#[serde(default, deserialize_with = "deserialize_dep_specs")]
|
||||
#[serde(default)]
|
||||
pub dependencies: BTreeMap<String, DependencySpecifiers>,
|
||||
#[serde(default, deserialize_with = "deserialize_dep_specs")]
|
||||
#[serde(default)]
|
||||
pub peer_dependencies: BTreeMap<String, DependencySpecifiers>,
|
||||
#[serde(default, deserialize_with = "deserialize_dep_specs")]
|
||||
#[serde(default)]
|
||||
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 {
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[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),
|
||||
}
|
||||
|
||||
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 {
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Project;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::{Debug, Display},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
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 {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
struc.hash(&mut hasher);
|
||||
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 {
|
||||
type Ref: PackageRef;
|
||||
type Specifier: DependencySpecifier;
|
||||
|
@ -38,7 +69,7 @@ pub trait PackageSource: Debug {
|
|||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
) -> Result<BTreeMap<Version, Self::Ref>, Self::ResolveError>;
|
||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError>;
|
||||
|
||||
fn download(
|
||||
&self,
|
||||
|
@ -47,33 +78,85 @@ pub trait PackageSource: Debug {
|
|||
project: &Project,
|
||||
) -> 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)]
|
||||
#[serde(untagged)]
|
||||
pub enum DependencySpecifiers {
|
||||
Pesde(pesde::PesdeDependencySpecifier),
|
||||
}
|
||||
|
||||
impl DependencySpecifiers {
|
||||
pub fn alias(&self) -> &str {
|
||||
fn refresh(&self, project: &Project) -> Result<(), Self::RefreshError> {
|
||||
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) {
|
||||
match self {
|
||||
DependencySpecifiers::Pesde(spec) => spec.set_alias(alias),
|
||||
fn resolve(
|
||||
&self,
|
||||
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 enum PackageRefs {
|
||||
Pesde(pesde::PesdePackageRef),
|
||||
}
|
||||
pub mod errors {
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
pub enum PackageSources {
|
||||
Pesde(pesde::PesdePackageSource),
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
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 semver::{Version, VersionReq};
|
||||
|
@ -6,9 +11,11 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{
|
||||
authenticate_conn,
|
||||
manifest::Target,
|
||||
names::PackageName,
|
||||
source::{hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource},
|
||||
manifest::{DependencyType, TargetKind},
|
||||
names::{PackageName, PackageNames},
|
||||
source::{
|
||||
hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource, ResolveResult,
|
||||
},
|
||||
Project, REQWEST_CLIENT,
|
||||
};
|
||||
|
||||
|
@ -16,17 +23,14 @@ use crate::{
|
|||
pub struct PesdeDependencySpecifier {
|
||||
pub name: PackageName,
|
||||
pub version: VersionReq,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub alias: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub index: Option<String>,
|
||||
}
|
||||
impl DependencySpecifier for PesdeDependencySpecifier {}
|
||||
|
||||
impl DependencySpecifier for PesdeDependencySpecifier {
|
||||
fn alias(&self) -> &str {
|
||||
self.alias.as_str()
|
||||
}
|
||||
|
||||
fn set_alias(&mut self, alias: String) {
|
||||
self.alias = alias;
|
||||
impl Display for PesdeDependencySpecifier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}@{}", self.name, self.version)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,9 +38,14 @@ impl DependencySpecifier for PesdeDependencySpecifier {
|
|||
pub struct PesdePackageRef {
|
||||
name: PackageName,
|
||||
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 {
|
||||
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 {
|
||||
repo_url: gix::Url,
|
||||
}
|
||||
|
@ -326,30 +335,35 @@ impl PackageSource for PesdePackageSource {
|
|||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
) -> Result<BTreeMap<Version, Self::Ref>, Self::ResolveError> {
|
||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
|
||||
let (scope, name) = specifier.name.as_str();
|
||||
let bytes = match self.read_file([scope, name], project) {
|
||||
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)),
|
||||
};
|
||||
|
||||
let entries: Vec<IndexFileEntry> = serde_yaml::from_slice(&bytes)
|
||||
.map_err(|e| Self::ResolveError::Parse(specifier.name.to_string(), e))?;
|
||||
|
||||
Ok(entries
|
||||
.into_iter()
|
||||
.filter(|entry| specifier.version.matches(&entry.version))
|
||||
.map(|entry| {
|
||||
(
|
||||
entry.version.clone(),
|
||||
PesdePackageRef {
|
||||
name: specifier.name.clone(),
|
||||
version: entry.version,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
Ok((
|
||||
PackageNames::Pesde(specifier.name.clone()),
|
||||
entries
|
||||
.into_iter()
|
||||
.filter(|entry| specifier.version.matches(&entry.version))
|
||||
.map(|entry| {
|
||||
(
|
||||
entry.version.clone(),
|
||||
PesdePackageRef {
|
||||
name: specifier.name.clone(),
|
||||
version: entry.version,
|
||||
index_url: self.repo_url.clone(),
|
||||
dependencies: entry.dependencies,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
fn download(
|
||||
|
@ -418,15 +432,15 @@ impl IndexConfig {
|
|||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct IndexFileEntry {
|
||||
pub version: Version,
|
||||
pub target: Target,
|
||||
pub target: TargetKind,
|
||||
#[serde(default = "chrono::Utc::now")]
|
||||
pub published_at: chrono::DateTime<chrono::Utc>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub dependencies: Vec<DependencySpecifiers>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
|
||||
}
|
||||
|
||||
impl Ord for IndexFileEntry {
|
||||
|
@ -457,28 +471,28 @@ pub mod errors {
|
|||
Io(#[from] std::io::Error),
|
||||
|
||||
#[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}")]
|
||||
NoDefaultRemote(PathBuf),
|
||||
|
||||
#[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}")]
|
||||
Connect(gix::Url, gix::remote::connect::Error),
|
||||
Connect(gix::Url, #[source] gix::remote::connect::Error),
|
||||
|
||||
#[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}")]
|
||||
Read(gix::Url, gix::remote::fetch::Error),
|
||||
Read(gix::Url, #[source] gix::remote::fetch::Error),
|
||||
|
||||
#[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}")]
|
||||
Fetch(gix::Url, gix::clone::fetch::Error),
|
||||
Fetch(gix::Url, #[source] gix::clone::fetch::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -488,13 +502,13 @@ pub mod errors {
|
|||
Io(#[from] std::io::Error),
|
||||
|
||||
#[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}")]
|
||||
NoDefaultRemote(PathBuf),
|
||||
|
||||
#[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}")]
|
||||
NoRefSpecs(PathBuf),
|
||||
|
@ -503,29 +517,29 @@ pub mod errors {
|
|||
NoLocalRefSpec(PathBuf),
|
||||
|
||||
#[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}")]
|
||||
CannotPeel(String, gix::reference::peel::Error),
|
||||
CannotPeel(String, #[source] gix::reference::peel::Error),
|
||||
|
||||
#[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")]
|
||||
CannotPeelToTree(String, gix::object::peel::to_kind::Error),
|
||||
CannotPeelToTree(String, #[source] gix::object::peel::to_kind::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum ReadFile {
|
||||
#[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}")]
|
||||
Tree(PathBuf, Box<TreeError>),
|
||||
Tree(PathBuf, #[source] Box<TreeError>),
|
||||
|
||||
#[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)]
|
||||
|
@ -534,11 +548,14 @@ pub mod errors {
|
|||
#[error("error interacting with the filesystem")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("package {0} not found")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("error reading file for {0}")]
|
||||
Read(String, Box<ReadFile>),
|
||||
Read(String, #[source] Box<ReadFile>),
|
||||
|
||||
#[error("error parsing file for {0}")]
|
||||
Parse(String, serde_yaml::Error),
|
||||
Parse(String, #[source] serde_yaml::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -558,19 +575,19 @@ pub mod errors {
|
|||
#[non_exhaustive]
|
||||
pub enum AllPackagesError {
|
||||
#[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}")]
|
||||
Tree(PathBuf, Box<TreeError>),
|
||||
Tree(PathBuf, #[source] Box<TreeError>),
|
||||
|
||||
#[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}")]
|
||||
Convert(PathBuf, gix::object::find::existing::Error),
|
||||
Convert(PathBuf, #[source] gix::object::find::existing::Error),
|
||||
|
||||
#[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)]
|
||||
|
|
Loading…
Reference in a new issue