diff --git a/src/cli/install.rs b/src/cli/install.rs index b28521e..f788fd6 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -63,6 +63,7 @@ impl InstallCommand { .write_lockfile(Lockfile { name: manifest.name, version: manifest.version, + target: manifest.target.kind(), overrides: manifest.overrides, graph: downloaded_graph, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 5797143..3304187 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -245,9 +245,16 @@ impl IsUpToDate for Project { Err(e) => return Err(e.into()), }; + if manifest.overrides != lockfile.overrides { + return Ok(false); + } + + if manifest.target.kind() != lockfile.target { + return Ok(false); + } + if !strict { - // the resolver will use the old lockfile and update it itself. it can't handle overrides only - return Ok(manifest.overrides == lockfile.overrides); + return Ok(true); } if manifest.name != lockfile.name || manifest.version != lockfile.version { diff --git a/src/cli/run.rs b/src/cli/run.rs index 967e065..8da89ff 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -30,7 +30,7 @@ impl RunCommand { let pkg_name = PackageNames::Pesde(pkg_name); - for (version, node) in graph.get(&pkg_name).context("package not found in graph")? { + for (version_id, node) in graph.get(&pkg_name).context("package not found in graph")? { if node.node.direct.is_none() { continue; } @@ -48,7 +48,7 @@ impl RunCommand { .join(base_folder) .join(PACKAGES_CONTAINER_NAME), &pkg_name, - version, + version_id.version(), ); let path = bin_path.to_path(&container_folder); diff --git a/src/download.rs b/src/download.rs index 9542d4f..943e474 100644 --- a/src/download.rs +++ b/src/download.rs @@ -21,7 +21,7 @@ impl Project { let mut downloaded_graph: DownloadedGraph = BTreeMap::new(); for (name, versions) in graph { - for (version, node) in versions { + for (version_id, node) in versions { let source = match &node.pkg_ref { PackageRefs::Pesde(pkg_ref) => { PackageSources::Pesde(PesdePackageSource::new(pkg_ref.index_url.clone())) @@ -38,7 +38,7 @@ impl Project { .join(node.base_folder(manifest.target.kind(), true)) .join(PACKAGES_CONTAINER_NAME), name, - version, + version_id.version(), ); create_dir_all(&container_folder)?; @@ -46,7 +46,7 @@ impl Project { let target = source.download(&node.pkg_ref, &container_folder, self)?; downloaded_graph.entry(name.clone()).or_default().insert( - version.clone(), + version_id.clone(), DownloadedDependencyGraphNode { node: node.clone(), target, diff --git a/src/linking/mod.rs b/src/linking/mod.rs index ada63b5..6a45f3b 100644 --- a/src/linking/mod.rs +++ b/src/linking/mod.rs @@ -4,10 +4,9 @@ use crate::{ manifest::{ScriptName, Target}, names::PackageNames, scripts::execute_script, - source::PackageRef, + source::{PackageRef, VersionId}, Project, PACKAGES_CONTAINER_NAME, }; -use semver::Version; use std::{collections::BTreeMap, fs::create_dir_all}; pub mod generator; @@ -16,10 +15,10 @@ 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(); + let mut package_types = BTreeMap::<&PackageNames, BTreeMap<&VersionId, Vec>>::new(); for (name, versions) in graph { - for (version, node) in versions { + for (version_id, node) in versions { let Some(lib_file) = node.target.lib_path() else { continue; }; @@ -30,7 +29,7 @@ impl Project { .join(node.node.base_folder(manifest.target.kind(), true)) .join(PACKAGES_CONTAINER_NAME), name, - version, + version_id.version(), ); let lib_file = lib_file.to_path(&container_folder); @@ -58,7 +57,7 @@ impl Project { package_types .entry(name) .or_default() - .insert(version, types); + .insert(version_id, types); #[cfg(feature = "roblox")] if let Target::Roblox { build_files, .. } = &node.target { @@ -87,7 +86,7 @@ impl Project { } for (name, versions) in graph { - for (version, node) in versions { + for (version_id, node) in versions { let base_folder = self.path().join( self.path() .join(node.node.base_folder(manifest.target.kind(), true)), @@ -96,13 +95,15 @@ impl Project { 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 container_folder = node.node.container_folder( + &packages_container_folder, + name, + version_id.version(), + ); if let Some((alias, types)) = package_types .get(name) - .and_then(|v| v.get(version)) + .and_then(|v| v.get(version_id)) .and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types))) { let module = generator::generate_linking_module( @@ -118,23 +119,23 @@ impl Project { std::fs::write(base_folder.join(format!("{alias}.luau")), module)?; } - for (dependency_name, (dependency_version, dependency_alias)) in + for (dependency_name, (dependency_version_id, dependency_alias)) in &node.node.dependencies { let Some(dependency_node) = graph .get(dependency_name) - .and_then(|v| v.get(dependency_version)) + .and_then(|v| v.get(dependency_version_id)) else { return Err(errors::LinkingError::DependencyNotFound( dependency_name.to_string(), - dependency_version.to_string(), + dependency_version_id.to_string(), )); }; let dependency_container_folder = dependency_node.node.container_folder( &packages_container_folder, dependency_name, - dependency_version, + dependency_version_id.version(), ); let linker_folder = container_folder @@ -153,7 +154,7 @@ impl Project { )?, package_types .get(dependency_name) - .and_then(|v| v.get(dependency_version)) + .and_then(|v| v.get(dependency_version_id)) .unwrap(), ); diff --git a/src/lockfile.rs b/src/lockfile.rs index 6d6187a..03ad395 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -1,7 +1,7 @@ use crate::{ manifest::{DependencyType, OverrideKey, Target, TargetKind}, names::{PackageName, PackageNames}, - source::{DependencySpecifiers, PackageRef, PackageRefs}, + source::{DependencySpecifiers, PackageRef, PackageRefs, VersionId}, }; use semver::Version; use serde::{Deserialize, Serialize}; @@ -10,16 +10,16 @@ use std::{ path::{Path, PathBuf}, }; -pub type Graph = BTreeMap>; +pub type Graph = 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, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub dependencies: BTreeMap, + pub dependencies: BTreeMap, pub ty: DependencyType, + pub pkg_ref: PackageRefs, } impl DependencyGraphNode { @@ -49,7 +49,7 @@ pub type DependencyGraph = Graph; pub fn insert_node( graph: &mut DependencyGraph, name: PackageNames, - version: Version, + version: VersionId, mut node: DependencyGraphNode, is_top_level: bool, ) { @@ -85,8 +85,9 @@ pub fn insert_node( #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DownloadedDependencyGraphNode { - pub node: DependencyGraphNode, pub target: Target, + #[serde(flatten)] + pub node: DependencyGraphNode, } pub type DownloadedGraph = Graph; @@ -95,6 +96,7 @@ pub type DownloadedGraph = Graph; pub struct Lockfile { pub name: PackageName, pub version: Version, + pub target: TargetKind, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub overrides: BTreeMap, diff --git a/src/resolver.rs b/src/resolver.rs index 5e23dd0..935c6e1 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -4,14 +4,13 @@ use crate::{ names::PackageNames, source::{ pesde::PesdePackageSource, DependencySpecifiers, PackageRef, PackageSource, PackageSources, + VersionId, }, Project, DEFAULT_INDEX_NAME, }; -use semver::Version; use std::collections::{HashMap, HashSet, VecDeque}; impl Project { - // TODO: account for targets using the is_compatible_with method pub fn dependency_graph( &self, previous_graph: Option<&DependencyGraph>, @@ -106,14 +105,17 @@ impl Project { alias.to_string(), spec, ty, - None::<(PackageNames, Version)>, + None::<(PackageNames, VersionId)>, vec![alias.to_string()], false, + manifest.target.kind(), ) }) .collect::>(); - while let Some((alias, specifier, ty, dependant, path, overridden)) = queue.pop_front() { + while let Some((alias, specifier, ty, dependant, path, overridden, target)) = + queue.pop_front() + { let depth = path.len() - 1; log::debug!( @@ -155,10 +157,10 @@ impl Project { } let (name, resolved) = source - .resolve(&specifier, self) + .resolve(&specifier, self, target) .map_err(|e| Box::new(e.into()))?; - let Some(target_version) = graph + let Some(target_version_id) = graph .get(&name) .and_then(|versions| { versions @@ -170,11 +172,9 @@ impl Project { .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; + return Err(Box::new(errors::DependencyGraphError::NoMatchingVersion( + format!("{specifier} ({})", manifest.target.kind()), + ))); }; let ty = if depth == 0 && ty == DependencyType::Peer { @@ -183,25 +183,25 @@ impl Project { ty }; - if let Some((dependant_name, dependant_version)) = dependant { + if let Some((dependant_name, dependant_version_id)) = dependant { graph .get_mut(&dependant_name) - .and_then(|versions| versions.get_mut(&dependant_version)) + .and_then(|versions| versions.get_mut(&dependant_version_id)) .and_then(|node| { node.dependencies - .insert(name.clone(), (target_version.clone(), alias.clone())) + .insert(name.clone(), (target_version_id.clone(), alias.clone())) }); } if let Some(already_resolved) = graph .get_mut(&name) - .and_then(|versions| versions.get_mut(&target_version)) + .and_then(|versions| versions.get_mut(&target_version_id)) { log::debug!( "{}{}@{} already resolved", "\t".repeat(depth), name, - target_version + target_version_id ); if already_resolved.ty == DependencyType::Peer && ty == DependencyType::Standard { @@ -211,7 +211,7 @@ impl Project { continue; } - let pkg_ref = &resolved[&target_version]; + let pkg_ref = &resolved[&target_version_id]; let node = DependencyGraphNode { direct: if depth == 0 { Some((alias.clone(), specifier.clone())) @@ -225,7 +225,7 @@ impl Project { insert_node( &mut graph, name.clone(), - target_version.clone(), + target_version_id.clone(), node.clone(), depth == 0, ); @@ -234,7 +234,7 @@ impl Project { "{}resolved {}@{} from new dependency graph", "\t".repeat(depth), name, - target_version + target_version_id ); for (dependency_alias, (dependency_spec, dependency_ty)) in @@ -262,20 +262,21 @@ impl Project { dependency_alias, overridden.cloned().unwrap_or(dependency_spec), dependency_ty, - Some((name.clone(), target_version.clone())), + Some((name.clone(), target_version_id.clone())), path.iter() .cloned() .chain(std::iter::once(alias.to_string())) .collect(), overridden.is_some(), + pkg_ref.target_kind(), )); } } for (name, versions) in &graph { - for (version, node) in versions { + for (version_id, node) in versions { if node.ty == DependencyType::Peer { - log::warn!("peer dependency {name}@{version} was not resolved"); + log::warn!("peer dependency {name}@{version_id} was not resolved"); } } } @@ -310,5 +311,8 @@ pub mod errors { #[error("error resolving package")] Resolve(#[from] crate::source::errors::ResolveError), + + #[error("no matching version found for {0}")] + NoMatchingVersion(String), } } diff --git a/src/source/mod.rs b/src/source/mod.rs index b584856..63e8063 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -1,17 +1,17 @@ -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, }; +use semver::Version; +use serde::{Deserialize, Serialize}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::{ + collections::BTreeMap, + fmt::{Debug, Display}, + path::Path, + str::FromStr, +}; pub mod pesde; @@ -40,6 +40,7 @@ impl Display for DependencySpecifiers { } #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case", tag = "ref_ty")] pub enum PackageRefs { Pesde(pesde::pkg_ref::PesdePackageRef), } @@ -68,7 +69,43 @@ impl PackageRef for PackageRefs { } } -pub type ResolveResult = (PackageNames, BTreeMap); +#[derive( + Debug, SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +pub struct VersionId(Version, TargetKind); + +impl VersionId { + pub fn version(&self) -> &Version { + &self.0 + } + + pub fn target(&self) -> &TargetKind { + &self.1 + } +} + +impl Display for VersionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.0, self.1) + } +} + +impl FromStr for VersionId { + type Err = errors::VersionIdParseError; + + fn from_str(s: &str) -> Result { + let Some((version, target)) = s.split_once(' ') else { + return Err(errors::VersionIdParseError::Malformed(s.to_string())); + }; + + let version = version.parse()?; + let target = target.parse()?; + + Ok(VersionId(version, target)) + } +} + +pub type ResolveResult = (PackageNames, BTreeMap); #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum PackageSources { @@ -89,6 +126,7 @@ pub trait PackageSource: Debug { &self, specifier: &Self::Specifier, project: &Project, + project_target: TargetKind, ) -> Result, Self::ResolveError>; fn download( @@ -115,10 +153,11 @@ impl PackageSource for PackageSources { &self, specifier: &Self::Specifier, project: &Project, + project_target: TargetKind, ) -> Result, Self::ResolveError> { match (self, specifier) { (PackageSources::Pesde(source), DependencySpecifiers::Pesde(specifier)) => source - .resolve(specifier, project) + .resolve(specifier, project, project_target) .map(|(name, results)| { ( name, @@ -179,4 +218,17 @@ pub mod errors { #[error("error downloading pesde package")] Pesde(#[from] crate::source::pesde::errors::DownloadError), } + + #[derive(Debug, Error)] + #[non_exhaustive] + pub enum VersionIdParseError { + #[error("malformed entry key {0}")] + Malformed(String), + + #[error("malformed version")] + Version(#[from] semver::Error), + + #[error("malformed target")] + Target(#[from] crate::manifest::errors::TargetKindFromStr), + } } diff --git a/src/source/pesde/mod.rs b/src/source/pesde/mod.rs index 31ce726..df851b0 100644 --- a/src/source/pesde/mod.rs +++ b/src/source/pesde/mod.rs @@ -1,23 +1,16 @@ -use std::{ - collections::BTreeMap, - fmt::{Debug, Display}, - hash::Hash, - path::Path, - str::FromStr, -}; +use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::Path}; use gix::remote::Direction; -use semver::Version; use serde::{Deserialize, Serialize}; -use serde_with::{DeserializeFromStr, SerializeDisplay}; use pkg_ref::PesdePackageRef; use specifier::PesdeDependencySpecifier; +use crate::manifest::TargetKind; use crate::{ - manifest::{DependencyType, Target, TargetKind}, + manifest::{DependencyType, Target}, names::{PackageName, PackageNames}, - source::{hash, DependencySpecifiers, PackageSource, ResolveResult}, + source::{hash, DependencySpecifiers, PackageSource, ResolveResult, VersionId}, util::authenticate_conn, Project, REQWEST_CLIENT, }; @@ -307,6 +300,7 @@ impl PackageSource for PesdePackageSource { &self, specifier: &Self::Specifier, project: &Project, + project_target: TargetKind, ) -> Result, Self::ResolveError> { let (scope, name) = specifier.name.as_str(); let string = match self.read_file([scope, name], project) { @@ -322,10 +316,17 @@ impl PackageSource for PesdePackageSource { PackageNames::Pesde(specifier.name.clone()), entries .into_iter() - .filter(|(EntryKey(version, _), _)| specifier.version.matches(version)) - .map(|(EntryKey(version, _), entry)| { + .filter(|(VersionId(version, target), _)| { + specifier.version.matches(version) + && specifier + .target + .map_or(project_target.is_compatible_with(target), |t| t == *target) + }) + .map(|(id, entry)| { + let version = id.version().clone(); + ( - version.clone(), + id, PesdePackageRef { name: specifier.name.clone(), version, @@ -413,33 +414,7 @@ pub struct IndexFileEntry { pub dependencies: BTreeMap, } -#[derive( - Debug, SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, -)] -pub struct EntryKey(pub Version, pub TargetKind); - -impl Display for EntryKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {}", self.0, self.1) - } -} - -impl FromStr for EntryKey { - type Err = errors::EntryKeyParseError; - - fn from_str(s: &str) -> Result { - let Some((version, target)) = s.split_once(' ') else { - return Err(errors::EntryKeyParseError::Malformed(s.to_string())); - }; - - let version = version.parse()?; - let target = target.parse()?; - - Ok(EntryKey(version, target)) - } -} - -pub type IndexFile = BTreeMap; +pub type IndexFile = BTreeMap; pub mod errors { use std::path::PathBuf; @@ -593,17 +568,4 @@ pub mod errors { #[error("error unpacking package")] Unpack(#[from] std::io::Error), } - - #[derive(Debug, Error)] - #[non_exhaustive] - pub enum EntryKeyParseError { - #[error("malformed entry key {0}")] - Malformed(String), - - #[error("malformed version")] - Version(#[from] semver::Error), - - #[error("malformed target")] - Target(#[from] crate::manifest::errors::TargetKindFromStr), - } } diff --git a/src/source/pesde/specifier.rs b/src/source/pesde/specifier.rs index 7fa9d15..1940b55 100644 --- a/src/source/pesde/specifier.rs +++ b/src/source/pesde/specifier.rs @@ -1,4 +1,4 @@ -use crate::{names::PackageName, source::DependencySpecifier}; +use crate::{manifest::TargetKind, names::PackageName, source::DependencySpecifier}; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -9,6 +9,8 @@ pub struct PesdeDependencySpecifier { pub version: VersionReq, #[serde(default, skip_serializing_if = "Option::is_none")] pub index: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub target: Option, } impl DependencySpecifier for PesdeDependencySpecifier {}