feat: implement multi target packages

This commit is contained in:
daimond113 2024-07-22 23:16:04 +02:00
parent 14463b7205
commit 2898b02e1c
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
10 changed files with 148 additions and 117 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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);

View file

@ -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,

View file

@ -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<String>>>::new();
let mut package_types = BTreeMap::<&PackageNames, BTreeMap<&VersionId, Vec<String>>>::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(),
);

View file

@ -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<Node> = BTreeMap<PackageNames, BTreeMap<Version, Node>>;
pub type Graph<Node> = BTreeMap<PackageNames, BTreeMap<VersionId, Node>>;
#[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<PackageNames, (Version, String)>,
pub dependencies: BTreeMap<PackageNames, (VersionId, String)>,
pub ty: DependencyType,
pub pkg_ref: PackageRefs,
}
impl DependencyGraphNode {
@ -49,7 +49,7 @@ pub type DependencyGraph = Graph<DependencyGraphNode>;
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<DownloadedDependencyGraphNode>;
@ -95,6 +96,7 @@ pub type DownloadedGraph = Graph<DownloadedDependencyGraphNode>;
pub struct Lockfile {
pub name: PackageName,
pub version: Version,
pub target: TargetKind,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,

View file

@ -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::<VecDeque<_>>();
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),
}
}

View file

@ -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<Ref> = (PackageNames, BTreeMap<Version, Ref>);
#[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<Self, Self::Err> {
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<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
#[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<ResolveResult<Self::Ref>, Self::ResolveError>;
fn download(
@ -115,10 +153,11 @@ impl PackageSource for PackageSources {
&self,
specifier: &Self::Specifier,
project: &Project,
project_target: TargetKind,
) -> Result<ResolveResult<Self::Ref>, 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),
}
}

View file

@ -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<ResolveResult<Self::Ref>, 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<String, (DependencySpecifiers, DependencyType)>,
}
#[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<Self, Self::Err> {
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<EntryKey, IndexFileEntry>;
pub type IndexFile = BTreeMap<VersionId, IndexFileEntry>;
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),
}
}

View file

@ -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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<TargetKind>,
}
impl DependencySpecifier for PesdeDependencySpecifier {}