From 10ca24a0ccd376c736c53cdd41b7f2d35bebca77 Mon Sep 17 00:00:00 2001 From: daimond113 <72147841+daimond113@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:38:01 +0200 Subject: [PATCH] feat: implement linking --- src/cli/install.rs | 14 +- src/download.rs | 79 ++++++++++++ src/lib.rs | 3 + src/linking/generator.rs | 128 +++++++++++++++++++ src/linking/mod.rs | 177 ++++++++++++++++++++++++++ src/lockfile.rs | 43 ++++++- src/main.rs | 23 +++- src/manifest.rs | 54 +++++--- src/names.rs | 18 +++ src/resolver.rs | 29 ++--- src/source/mod.rs | 34 ++++- src/source/{pesde.rs => pesde/mod.rs} | 66 ++-------- src/source/pesde/pkg_ref.rs | 44 +++++++ src/source/pesde/specifier.rs | 19 +++ 14 files changed, 630 insertions(+), 101 deletions(-) create mode 100644 src/download.rs create mode 100644 src/linking/generator.rs create mode 100644 src/linking/mod.rs rename src/source/{pesde.rs => pesde/mod.rs} (92%) create mode 100644 src/source/pesde/pkg_ref.rs create mode 100644 src/source/pesde/specifier.rs diff --git a/src/cli/install.rs b/src/cli/install.rs index 833b2f6..4a4a3e0 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -1,12 +1,24 @@ +use anyhow::Context; use clap::Args; use pesde::Project; +use std::collections::HashSet; #[derive(Debug, Args)] pub struct InstallCommand {} impl InstallCommand { pub fn run(self, project: Project) -> anyhow::Result<()> { - dbg!(project.dependency_graph(None)?); + let mut refreshed_sources = HashSet::new(); + let graph = project + .dependency_graph(None, &mut refreshed_sources) + .context("failed to build dependency graph")?; + let downloaded_graph = project + .download_graph(&graph, &mut refreshed_sources) + .context("failed to download dependencies")?; + + project + .link_dependencies(&downloaded_graph) + .context("failed to link dependencies")?; Ok(()) } diff --git a/src/download.rs b/src/download.rs new file mode 100644 index 0000000..d9db138 --- /dev/null +++ b/src/download.rs @@ -0,0 +1,79 @@ +use std::{ + collections::{BTreeMap, HashSet}, + fs::create_dir_all, +}; + +use crate::{ + lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph}, + source::{pesde::PesdePackageSource, PackageRefs, PackageSource, PackageSources}, + Project, PACKAGES_CONTAINER_NAME, +}; + +impl Project { + pub fn download_graph( + &self, + graph: &DependencyGraph, + refreshed_sources: &mut HashSet, + ) -> Result { + let manifest = self.deser_manifest()?; + + let mut downloaded_graph: DownloadedGraph = BTreeMap::new(); + + for (name, versions) in graph { + for (version, node) in versions { + let source = match &node.pkg_ref { + PackageRefs::Pesde(pkg_ref) => { + PackageSources::Pesde(PesdePackageSource::new(pkg_ref.index_url.clone())) + } + }; + + if refreshed_sources.insert(source.clone()) { + source.refresh(self).map_err(Box::new)?; + } + + let container_folder = node.container_folder( + &self + .path() + .join(node.base_folder(manifest.target.kind(), true)) + .join(PACKAGES_CONTAINER_NAME), + name, + version, + ); + + create_dir_all(&container_folder)?; + + let target = source.download(&node.pkg_ref, &container_folder, self)?; + + downloaded_graph.entry(name.clone()).or_default().insert( + version.clone(), + DownloadedDependencyGraphNode { + node: node.clone(), + target, + }, + ); + } + } + + Ok(downloaded_graph) + } +} + +pub mod errors { + use thiserror::Error; + + #[derive(Debug, Error)] + #[non_exhaustive] + pub enum DownloadGraphError { + #[error("error deserializing project manifest")] + ManifestDeserializationFailed(#[from] crate::errors::ManifestReadError), + + #[error("failed to refresh package source")] + RefreshFailed(#[from] Box), + + #[error("error interacting with filesystem")] + Io(#[from] std::io::Error), + + #[error("failed to download package")] + DownloadFailed(#[from] crate::source::errors::DownloadError), + } +} diff --git a/src/lib.rs b/src/lib.rs index e02a1ae..e5860f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ use crate::lockfile::Lockfile; use once_cell::sync::Lazy; use std::path::{Path, PathBuf}; +pub mod download; +pub mod linking; pub mod lockfile; pub mod manifest; pub mod names; @@ -17,6 +19,7 @@ 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 const PACKAGES_CONTAINER_NAME: &str = ".pesde"; pub(crate) static REQWEST_CLIENT: Lazy = Lazy::new(|| { reqwest::blocking::Client::builder() diff --git a/src/linking/generator.rs b/src/linking/generator.rs new file mode 100644 index 0000000..1d24531 --- /dev/null +++ b/src/linking/generator.rs @@ -0,0 +1,128 @@ +use std::path::{Component, Path}; + +use full_moon::{ast::luau::ExportedTypeDeclaration, visitors::Visitor}; + +use crate::manifest::Target; + +struct TypeVisitor { + types: Vec, +} + +impl Visitor for TypeVisitor { + fn visit_exported_type_declaration(&mut self, node: &ExportedTypeDeclaration) { + let name = node.type_declaration().type_name().to_string(); + + let (declaration_generics, generics) = + if let Some(declaration) = node.type_declaration().generics() { + let mut declaration_generics = vec![]; + let mut generics = vec![]; + + for generic in declaration.generics().iter() { + declaration_generics.push(generic.to_string()); + + if generic.default_type().is_some() { + generics.push(generic.parameter().to_string()) + } else { + generics.push(generic.to_string()) + } + } + + ( + format!("<{}>", declaration_generics.join(", ")), + format!("<{}>", generics.join(", ")), + ) + } else { + ("".to_string(), "".to_string()) + }; + + self.types.push(format!( + "export type {name}{declaration_generics} = module.{name}{generics}\n" + )); + } +} + +pub fn get_file_types(file: &str) -> Result, Vec> { + let ast = full_moon::parse(file)?; + let mut visitor = TypeVisitor { types: vec![] }; + visitor.visit_ast(&ast); + + Ok(visitor.types) +} + +pub fn generate_linking_module, S: AsRef>( + path: &str, + types: I, +) -> String { + let mut output = format!("local module = require({path})\n"); + + for ty in types { + output.push_str(ty.as_ref()); + } + + output.push_str("return module"); + + output +} + +pub fn get_require_path( + target: &Target, + base_dir: &Path, + destination_dir: &Path, + use_new_structure: bool, +) -> Result { + let Some(lib_file) = target.lib_path() else { + return Err(errors::GetRequirePathError::NoLibPath); + }; + + let path = pathdiff::diff_paths(destination_dir, base_dir).unwrap(); + let path = if !use_new_structure { + lib_file.to_path(path) + } else { + path + }; + + #[cfg(feature = "roblox")] + if matches!(target, Target::Roblox { .. }) { + let path = path + .components() + .filter_map(|component| match component { + Component::ParentDir => Some(".Parent".to_string()), + Component::Normal(part) if part != "init.lua" && part != "init.luau" => { + Some(format!( + "[{:?}]", + part.to_string_lossy() + .trim_end_matches(".lua") + .trim_end_matches(".luau") + )) + } + _ => None, + }) + .collect::>() + .join(""); + + return Ok(format!("script{path}")); + }; + + let path = path + .components() + .filter_map(|ct| match ct { + Component::ParentDir => Some("..".to_string()), + Component::Normal(part) => Some(format!("{}", part.to_string_lossy())), + _ => None, + }) + .collect::>() + .join("/"); + + Ok(format!("./{path}")) +} + +pub mod errors { + use thiserror::Error; + + #[derive(Debug, Error)] + #[non_exhaustive] + pub enum GetRequirePathError { + #[error("get require path called for target without a lib path")] + NoLibPath, + } +} diff --git a/src/linking/mod.rs b/src/linking/mod.rs new file mode 100644 index 0000000..6022507 --- /dev/null +++ b/src/linking/mod.rs @@ -0,0 +1,177 @@ +use crate::{ + linking::generator::get_file_types, lockfile::DownloadedGraph, manifest::Manifest, + names::PackageNames, source::PackageRef, Project, MANIFEST_FILE_NAME, PACKAGES_CONTAINER_NAME, +}; +use semver::Version; +use std::{collections::BTreeMap, fs::create_dir_all}; + +pub mod generator; + +fn read_manifest(path: &std::path::Path) -> Result { + let manifest = std::fs::read_to_string(path.join(MANIFEST_FILE_NAME))?; + serde_yaml::from_str(&manifest) + .map_err(|e| errors::LinkingError::DependencyManifest(path.display().to_string(), e)) +} + +impl Project { + pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> { + let manifest = self.deser_manifest()?; + + let mut package_types = BTreeMap::<&PackageNames, BTreeMap<&Version, Vec>>::new(); + + for (name, versions) in graph { + for (version, node) in versions { + let Some(lib_file) = node.target.lib_path() else { + continue; + }; + + let container_folder = node.node.container_folder( + &self + .path() + .join(node.node.base_folder(manifest.target.kind(), true)) + .join(PACKAGES_CONTAINER_NAME), + name, + version, + ); + + let lib_file = lib_file.to_path(container_folder); + + let contents = match std::fs::read_to_string(&lib_file) { + Ok(contents) => contents, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return Err(errors::LinkingError::LibFileNotFound( + lib_file.display().to_string(), + )); + } + Err(e) => return Err(e.into()), + }; + + let types = match get_file_types(&contents) { + Ok(types) => types, + Err(e) => { + return Err(errors::LinkingError::FullMoon( + lib_file.display().to_string(), + e, + )) + } + }; + + package_types + .entry(name) + .or_default() + .insert(version, types); + } + } + + for (name, versions) in graph { + for (version, node) in versions { + let base_folder = self.path().join( + self.path() + .join(node.node.base_folder(manifest.target.kind(), true)), + ); + create_dir_all(&base_folder)?; + let base_folder = base_folder.canonicalize()?; + let packages_container_folder = base_folder.join(PACKAGES_CONTAINER_NAME); + + let container_folder = + node.node + .container_folder(&packages_container_folder, name, version); + + let node_manifest = read_manifest(&container_folder)?; + + if let Some((alias, types)) = package_types + .get(name) + .and_then(|v| v.get(version)) + .and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types))) + { + let module = generator::generate_linking_module( + &generator::get_require_path( + &node_manifest.target, + &base_folder, + &container_folder, + node.node.pkg_ref.use_new_structure(), + )?, + types, + ); + + std::fs::write(base_folder.join(format!("{alias}.luau")), module)?; + } + + for (dependency_name, (dependency_version, dependency_alias)) in + &node.node.dependencies + { + let Some(dependency_node) = graph + .get(dependency_name) + .and_then(|v| v.get(dependency_version)) + else { + return Err(errors::LinkingError::DependencyNotFound( + dependency_name.to_string(), + dependency_version.to_string(), + )); + }; + + let dependency_container_folder = dependency_node.node.container_folder( + &packages_container_folder, + dependency_name, + dependency_version, + ); + + let dependency_manifest = read_manifest(&dependency_container_folder)?; + + let linker_folder = container_folder + .join(dependency_node.node.base_folder(node.target.kind(), false)); + create_dir_all(&linker_folder)?; + let linker_folder = linker_folder.canonicalize()?; + + let linker_file = linker_folder.join(format!("{dependency_alias}.luau")); + + let module = generator::generate_linking_module( + &generator::get_require_path( + &dependency_manifest.target, + &linker_file, + &dependency_container_folder, + node.node.pkg_ref.use_new_structure(), + )?, + package_types + .get(dependency_name) + .and_then(|v| v.get(dependency_version)) + .unwrap(), + ); + + std::fs::write(linker_file, module)?; + } + } + } + + Ok(()) + } +} + +pub mod errors { + use thiserror::Error; + + #[derive(Debug, Error)] + #[non_exhaustive] + pub enum LinkingError { + #[error("error deserializing project manifest")] + Manifest(#[from] crate::errors::ManifestReadError), + + #[error("error deserializing manifest at {0}")] + DependencyManifest(String, #[source] serde_yaml::Error), + + #[error("error interacting with filesystem")] + Io(#[from] std::io::Error), + + #[error("dependency not found: {0}@{1}")] + DependencyNotFound(String, String), + + #[error("library file at {0} not found")] + LibFileNotFound(String), + + #[error("error parsing Luau script at {0}")] + FullMoon(String, Vec), + + #[error("error generating require path")] + GetRequirePath(#[from] crate::linking::generator::errors::GetRequirePathError), + } +} diff --git a/src/lockfile.rs b/src/lockfile.rs index e12d199..0828763 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -1,11 +1,16 @@ use crate::{ - manifest::{DependencyType, OverrideKey}, + manifest::{DependencyType, OverrideKey, Target, TargetKind}, names::{PackageName, PackageNames}, - source::{DependencySpecifiers, PackageRefs}, + source::{DependencySpecifiers, PackageRef, PackageRefs}, }; use semver::Version; use serde::{Deserialize, Serialize}; -use std::collections::{btree_map::Entry, BTreeMap}; +use std::{ + collections::{btree_map::Entry, BTreeMap}, + path::{Path, PathBuf}, +}; + +pub type Graph = BTreeMap>; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DependencyGraphNode { @@ -16,7 +21,29 @@ pub struct DependencyGraphNode { pub ty: DependencyType, } -pub type DependencyGraph = BTreeMap>; +impl DependencyGraphNode { + pub fn base_folder(&self, project_target: TargetKind, is_top_level: bool) -> String { + if is_top_level || self.pkg_ref.use_new_structure() { + project_target.packages_folder(&self.pkg_ref.target_kind()) + } else { + "..".to_string() + } + } + + pub fn container_folder>( + &self, + path: &P, + name: &PackageNames, + version: &Version, + ) -> PathBuf { + path.as_ref() + .join(name.escaped()) + .join(version.to_string()) + .join(name.as_str().1) + } +} + +pub type DependencyGraph = Graph; pub fn insert_node( graph: &mut DependencyGraph, @@ -50,6 +77,14 @@ pub fn insert_node( } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DownloadedDependencyGraphNode { + pub node: DependencyGraphNode, + pub target: Target, +} + +pub type DownloadedGraph = Graph; + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Lockfile { pub name: PackageName, diff --git a/src/main.rs b/src/main.rs index 4fd0012..66b45ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use crate::cli::get_token; use clap::Parser; use colored::Colorize; use pesde::{AuthConfig, Project}; +use std::fs::create_dir_all; mod cli; @@ -27,6 +28,7 @@ fn main() { let cli = Cli::parse(); let data_dir = project_dirs.data_dir(); + create_dir_all(data_dir).expect("failed to create data directory"); if let Err(err) = get_token(data_dir).and_then(|token| { cli.subcommand.run(Project::new( @@ -35,16 +37,33 @@ fn main() { AuthConfig::new().with_pesde_token(token), )) }) { - eprintln!("{}: {}\n", "error".red().bold(), err.to_string().bold()); + eprintln!("{}: {err}\n", "error".red().bold()); let cause = err.chain().skip(1).collect::>(); if !cause.is_empty() { eprintln!("{}:", "caused by".red().bold()); for err in cause { - eprintln!(" - {}", err.to_string().bold()); + eprintln!(" - {err}"); } } + + let backtrace = err.backtrace(); + match backtrace.status() { + std::backtrace::BacktraceStatus::Disabled => { + eprintln!( + "\n{}: set RUST_BACKTRACE=1 for a backtrace", + "help".yellow().bold() + ); + } + std::backtrace::BacktraceStatus::Captured => { + eprintln!("\n{}:\n{backtrace}", "backtrace".yellow().bold()); + } + _ => { + eprintln!("\n{}: not captured", "backtrace".yellow().bold()); + } + } + std::process::exit(1); } } diff --git a/src/manifest.rs b/src/manifest.rs index 8acb79f..52756f0 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,6 +1,6 @@ use relative_path::RelativePathBuf; use semver::Version; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use std::{ collections::BTreeMap, @@ -58,7 +58,7 @@ impl TargetKind { } } -#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[serde(rename_all = "snake_case", tag = "environment", remote = "Self")] pub enum Target { #[cfg(feature = "roblox")] @@ -75,6 +75,39 @@ pub enum Target { }, } +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, + } + } + + pub fn lib_path(&self) -> Option<&RelativePathBuf> { + match self { + #[cfg(feature = "roblox")] + Target::Roblox { lib } => Some(lib), + #[cfg(feature = "lune")] + Target::Lune { lib, .. } => lib.as_ref(), + #[cfg(feature = "luau")] + Target::Luau { lib, .. } => lib.as_ref(), + } + } +} + +impl Serialize for Target { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Self::serialize(self, serializer) + } +} + impl<'de> Deserialize<'de> for Target { fn deserialize(deserializer: D) -> Result where @@ -115,19 +148,6 @@ impl Display for Target { } } -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( Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, )] @@ -189,10 +209,10 @@ pub struct Manifest { pub scripts: BTreeMap, #[serde(default)] pub indices: BTreeMap, - #[cfg(feature = "wally")] + #[cfg(feature = "wally-compat")] #[serde(default)] pub wally_indices: BTreeMap, - #[cfg(all(feature = "wally", feature = "roblox"))] + #[cfg(all(feature = "wally-compat", feature = "roblox"))] #[serde(default)] pub sourcemap_generator: Option, #[serde(default)] diff --git a/src/names.rs b/src/names.rs index ac1cfa8..b47c202 100644 --- a/src/names.rs +++ b/src/names.rs @@ -63,6 +63,10 @@ impl PackageName { pub fn as_str(&self) -> (&str, &str) { (&self.0, &self.1) } + + pub fn escaped(&self) -> String { + format!("{}+{}", self.0, self.1) + } } #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -70,6 +74,20 @@ pub enum PackageNames { Pesde(PackageName), } +impl PackageNames { + pub fn as_str(&self) -> (&str, &str) { + match self { + PackageNames::Pesde(name) => name.as_str(), + } + } + + pub fn escaped(&self) -> String { + match self { + PackageNames::Pesde(name) => name.escaped(), + } + } +} + impl Display for PackageNames { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/resolver.rs b/src/resolver.rs index 225f690..ca4c93c 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -11,19 +11,17 @@ use semver::Version; use std::collections::{HashMap, HashSet, VecDeque}; impl Project { - // TODO: implement dependency overrides + // TODO: implement dependency overrides, account for targets using the is_compatible_with method pub fn dependency_graph( &self, previous_graph: Option<&DependencyGraph>, + refreshed_sources: &mut HashSet, ) -> Result> { let manifest = self.deser_manifest().map_err(|e| Box::new(e.into()))?; - let mut all_dependencies = manifest + let mut all_specifiers = manifest .all_dependencies() - .map_err(|e| Box::new(e.into()))?; - - let mut all_specifiers = all_dependencies - .clone() + .map_err(|e| Box::new(e.into()))? .into_iter() .map(|(alias, (spec, ty))| ((spec, ty), alias)) .collect::>(); @@ -38,14 +36,12 @@ impl Project { 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; - } + if all_specifiers + .remove(&(specifier.clone(), node.ty)) + .is_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); @@ -96,10 +92,9 @@ impl Project { } } - let mut refreshed_sources = HashSet::new(); - let mut queue = all_dependencies + let mut queue = all_specifiers .into_iter() - .map(|(alias, (spec, ty))| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize)) + .map(|((spec, ty), alias)| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize)) .collect::>(); while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() { diff --git a/src/source/mod.rs b/src/source/mod.rs index c6c1893..9ce172c 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -1,12 +1,18 @@ -use crate::{manifest::DependencyType, names::PackageNames, Project}; -use semver::Version; -use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, fmt::{Debug, Display}, path::Path, }; +use semver::Version; +use serde::{Deserialize, Serialize}; + +use crate::{ + manifest::{DependencyType, Target, TargetKind}, + names::PackageNames, + Project, +}; + pub mod pesde; pub(crate) fn hash(struc: &S) -> String { @@ -20,7 +26,7 @@ pub(crate) fn hash(struc: &S) -> String { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[serde(untagged)] pub enum DependencySpecifiers { - Pesde(pesde::PesdeDependencySpecifier), + Pesde(pesde::specifier::PesdeDependencySpecifier), } pub trait DependencySpecifier: Debug + Display {} impl DependencySpecifier for DependencySpecifiers {} @@ -35,10 +41,12 @@ impl Display for DependencySpecifiers { #[derive(Debug, Serialize, Deserialize, Clone)] pub enum PackageRefs { - Pesde(pesde::PesdePackageRef), + Pesde(pesde::pkg_ref::PesdePackageRef), } pub trait PackageRef: Debug { fn dependencies(&self) -> &BTreeMap; + fn use_new_structure(&self) -> bool; + fn target_kind(&self) -> TargetKind; } impl PackageRef for PackageRefs { fn dependencies(&self) -> &BTreeMap { @@ -46,6 +54,18 @@ impl PackageRef for PackageRefs { PackageRefs::Pesde(pkg_ref) => pkg_ref.dependencies(), } } + + fn use_new_structure(&self) -> bool { + match self { + PackageRefs::Pesde(pkg_ref) => pkg_ref.use_new_structure(), + } + } + + fn target_kind(&self) -> TargetKind { + match self { + PackageRefs::Pesde(pkg_ref) => pkg_ref.target_kind(), + } + } } pub type ResolveResult = (PackageNames, BTreeMap); @@ -76,7 +96,7 @@ pub trait PackageSource: Debug { pkg_ref: &Self::Ref, destination: &Path, project: &Project, - ) -> Result<(), Self::DownloadError>; + ) -> Result; } impl PackageSource for PackageSources { type Ref = PackageRefs; @@ -119,7 +139,7 @@ impl PackageSource for PackageSources { pkg_ref: &Self::Ref, destination: &Path, project: &Project, - ) -> Result<(), Self::DownloadError> { + ) -> Result { match (self, pkg_ref) { (PackageSources::Pesde(source), PackageRefs::Pesde(pkg_ref)) => source .download(pkg_ref, destination, project) diff --git a/src/source/pesde.rs b/src/source/pesde/mod.rs similarity index 92% rename from src/source/pesde.rs rename to src/source/pesde/mod.rs index 5568083..d29fb81 100644 --- a/src/source/pesde.rs +++ b/src/source/pesde/mod.rs @@ -1,63 +1,22 @@ -use std::{ - collections::BTreeMap, - fmt::{Debug, Display}, - hash::Hash, - path::Path, -}; +use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::Path}; use gix::remote::Direction; -use semver::{Version, VersionReq}; +use semver::Version; use serde::{Deserialize, Serialize}; +use pkg_ref::PesdePackageRef; +use specifier::PesdeDependencySpecifier; + use crate::{ authenticate_conn, - manifest::{DependencyType, TargetKind}, + manifest::{DependencyType, Target}, names::{PackageName, PackageNames}, - source::{ - hash, DependencySpecifier, DependencySpecifiers, PackageRef, PackageSource, ResolveResult, - }, + source::{hash, DependencySpecifiers, PackageSource, ResolveResult}, Project, REQWEST_CLIENT, }; -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] -pub struct PesdeDependencySpecifier { - pub name: PackageName, - pub version: VersionReq, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub index: Option, -} -impl DependencySpecifier for PesdeDependencySpecifier {} - -impl Display for PesdeDependencySpecifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}@{}", self.name, self.version) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub struct PesdePackageRef { - name: PackageName, - version: Version, - index_url: gix::Url, - dependencies: BTreeMap, -} -impl PackageRef for PesdePackageRef { - fn dependencies(&self) -> &BTreeMap { - &self.dependencies - } -} - -impl Ord for PesdePackageRef { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.version.cmp(&other.version) - } -} - -impl PartialOrd for PesdePackageRef { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} +pub mod pkg_ref; +pub mod specifier; #[derive(Debug, Hash, PartialEq, Eq, Clone)] pub struct PesdePackageSource { @@ -359,6 +318,7 @@ impl PackageSource for PesdePackageSource { version: entry.version, index_url: self.repo_url.clone(), dependencies: entry.dependencies, + target: entry.target, }, ) }) @@ -371,7 +331,7 @@ impl PackageSource for PesdePackageSource { pkg_ref: &Self::Ref, destination: &Path, project: &Project, - ) -> Result<(), Self::DownloadError> { + ) -> Result { let config = self.config(project)?; let (scope, name) = pkg_ref.name.as_str(); @@ -397,7 +357,7 @@ impl PackageSource for PesdePackageSource { archive.unpack(destination)?; - Ok(()) + Ok(pkg_ref.target.clone()) } } @@ -432,7 +392,7 @@ impl IndexConfig { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub struct IndexFileEntry { pub version: Version, - pub target: TargetKind, + pub target: Target, #[serde(default = "chrono::Utc::now")] pub published_at: chrono::DateTime, diff --git a/src/source/pesde/pkg_ref.rs b/src/source/pesde/pkg_ref.rs new file mode 100644 index 0000000..9889863 --- /dev/null +++ b/src/source/pesde/pkg_ref.rs @@ -0,0 +1,44 @@ +use std::collections::BTreeMap; + +use semver::Version; +use serde::{Deserialize, Serialize}; + +use crate::{ + manifest::{DependencyType, Target, TargetKind}, + names::PackageName, + source::{DependencySpecifiers, PackageRef}, +}; + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct PesdePackageRef { + pub name: PackageName, + pub version: Version, + pub index_url: gix::Url, + pub dependencies: BTreeMap, + pub target: Target, +} +impl PackageRef for PesdePackageRef { + fn dependencies(&self) -> &BTreeMap { + &self.dependencies + } + + fn use_new_structure(&self) -> bool { + true + } + + fn target_kind(&self) -> TargetKind { + self.target.kind() + } +} + +impl Ord for PesdePackageRef { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.version.cmp(&other.version) + } +} + +impl PartialOrd for PesdePackageRef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/src/source/pesde/specifier.rs b/src/source/pesde/specifier.rs new file mode 100644 index 0000000..7fa9d15 --- /dev/null +++ b/src/source/pesde/specifier.rs @@ -0,0 +1,19 @@ +use crate::{names::PackageName, source::DependencySpecifier}; +use semver::VersionReq; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +pub struct PesdeDependencySpecifier { + pub name: PackageName, + pub version: VersionReq, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub index: Option, +} +impl DependencySpecifier for PesdeDependencySpecifier {} + +impl Display for PesdeDependencySpecifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}@{}", self.name, self.version) + } +}