feat: implement dependency overrides

This commit is contained in:
daimond113 2024-07-22 22:00:09 +02:00
parent 64ed3931cf
commit 14463b7205
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
13 changed files with 186 additions and 178 deletions

41
Cargo.lock generated
View file

@ -304,9 +304,6 @@ name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
[[package]]
name = "block-buffer"
@ -1109,7 +1106,6 @@ dependencies = [
"gix-index",
"gix-lock",
"gix-macros",
"gix-mailmap",
"gix-negotiate",
"gix-object",
"gix-odb",
@ -1136,7 +1132,6 @@ dependencies = [
"once_cell",
"parking_lot",
"regex",
"serde",
"smallvec",
"thiserror",
]
@ -1151,7 +1146,6 @@ dependencies = [
"gix-date",
"gix-utils",
"itoa",
"serde",
"thiserror",
"winnow 0.6.14",
]
@ -1168,7 +1162,6 @@ dependencies = [
"gix-quote",
"gix-trace",
"kstring",
"serde",
"smallvec",
"thiserror",
"unicode-bom",
@ -1215,7 +1208,6 @@ dependencies = [
"gix-features",
"gix-hash",
"memmap2",
"serde",
"thiserror",
]
@ -1267,7 +1259,6 @@ dependencies = [
"gix-sec",
"gix-trace",
"gix-url",
"serde",
"thiserror",
]
@ -1279,7 +1270,6 @@ checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0"
dependencies = [
"bstr",
"itoa",
"serde",
"thiserror",
"time",
]
@ -1374,7 +1364,6 @@ dependencies = [
"bstr",
"gix-features",
"gix-path",
"serde",
]
[[package]]
@ -1384,7 +1373,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e"
dependencies = [
"faster-hex",
"serde",
"thiserror",
]
@ -1409,7 +1397,6 @@ dependencies = [
"gix-glob",
"gix-path",
"gix-trace",
"serde",
"unicode-bom",
]
@ -1437,7 +1424,6 @@ dependencies = [
"libc",
"memmap2",
"rustix",
"serde",
"smallvec",
"thiserror",
]
@ -1464,19 +1450,6 @@ dependencies = [
"syn 2.0.71",
]
[[package]]
name = "gix-mailmap"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb2da346958252cbc8656529f5479830a3bc6046f3d86405c9e77f71dfdf7b2"
dependencies = [
"bstr",
"gix-actor",
"gix-date",
"serde",
"thiserror",
]
[[package]]
name = "gix-negotiate"
version = "0.13.1"
@ -1507,7 +1480,6 @@ dependencies = [
"gix-utils",
"gix-validate",
"itoa",
"serde",
"smallvec",
"thiserror",
"winnow 0.6.14",
@ -1529,7 +1501,6 @@ dependencies = [
"gix-path",
"gix-quote",
"parking_lot",
"serde",
"tempfile",
"thiserror",
]
@ -1550,7 +1521,6 @@ dependencies = [
"gix-tempfile",
"memmap2",
"parking_lot",
"serde",
"smallvec",
"thiserror",
]
@ -1634,7 +1604,6 @@ dependencies = [
"gix-transport",
"gix-utils",
"maybe-async",
"serde",
"thiserror",
"winnow 0.6.14",
]
@ -1668,7 +1637,6 @@ dependencies = [
"gix-utils",
"gix-validate",
"memmap2",
"serde",
"thiserror",
"winnow 0.6.14",
]
@ -1700,7 +1668,6 @@ dependencies = [
"gix-object",
"gix-revwalk",
"gix-trace",
"serde",
"thiserror",
]
@ -1728,7 +1695,6 @@ dependencies = [
"bitflags 2.6.0",
"gix-path",
"libc",
"serde",
"windows-sys 0.52.0",
]
@ -1782,7 +1748,6 @@ dependencies = [
"gix-sec",
"gix-url",
"reqwest",
"serde",
"thiserror",
]
@ -1813,7 +1778,6 @@ dependencies = [
"gix-features",
"gix-path",
"home",
"serde",
"thiserror",
"url",
]
@ -1855,7 +1819,6 @@ dependencies = [
"gix-object",
"gix-path",
"gix-validate",
"serde",
]
[[package]]
@ -2284,7 +2247,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
dependencies = [
"serde",
"static_assertions",
]
@ -3356,9 +3318,6 @@ name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]]
name = "smol_str"

View file

@ -29,7 +29,7 @@ serde = { version = "1.0.204", features = ["derive"] }
toml = "0.8.15"
serde_json = "1.0.120"
serde_with = "3.9.0"
gix = { version = "0.63.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "revparse-regex", "credentials", "serde"] }
gix = { version = "0.63.0", default-features = false, features = ["blocking-http-transport-reqwest-rust-tls", "revparse-regex", "credentials"] }
semver = { version = "1.0.23", features = ["serde"] }
reqwest = { version = "0.12.5", default-features = false, features = ["rustls-tls", "blocking"] }
tar = "0.4.41"

View file

@ -32,143 +32,141 @@ impl InitCommand {
match project.read_manifest() {
Ok(_) => {
println!("{}", "project already initialized".red());
Ok(())
return Ok(());
}
Err(ManifestReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {
let mut manifest = toml_edit::DocumentMut::new();
Err(ManifestReadError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
};
manifest["name"] = toml_edit::value(
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(),
);
manifest["version"] = toml_edit::value("0.1.0");
let mut manifest = toml_edit::DocumentMut::new();
let description = inquire::Text::new(
"What is the description of the project? (leave empty for none)",
)
.prompt()
.unwrap();
if !description.is_empty() {
manifest["description"] = toml_edit::value(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())
.map(|s| s.into())
.collect::<Vec<toml_edit::Value>>();
if !authors.is_empty() {
let mut authors_arr = toml_edit::Array::new();
authors_arr.extend(authors);
manifest["authors"] = toml_edit::value(authors_arr);
}
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) {
manifest["name"] = toml_edit::value(
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();
if !repo.is_empty() {
manifest["repository"] = toml_edit::value(repo);
}
.unwrap(),
);
manifest["version"] = toml_edit::value("0.1.0");
let license = inquire::Text::new(
"What is the license of this project? (leave empty for none)",
)
let description =
inquire::Text::new("What is the description of the project? (leave empty for none)")
.prompt()
.unwrap();
if !description.is_empty() {
manifest["description"] = toml_edit::value(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())
.map(|s| s.into())
.collect::<Vec<toml_edit::Value>>();
if !authors.is_empty() {
let mut authors_arr = toml_edit::Array::new();
authors_arr.extend(authors);
manifest["authors"] = toml_edit::value(authors_arr);
}
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() {
manifest["repository"] = toml_edit::value(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() {
manifest["license"] = toml_edit::value(license);
}
if !license.is_empty() {
manifest["license"] = toml_edit::value(license);
}
let target_env = inquire::Select::new(
"What environment are you targeting for your package?",
vec![
#[cfg(feature = "roblox")]
"roblox",
#[cfg(feature = "lune")]
"lune",
#[cfg(feature = "luau")]
"luau",
],
)
.prompt()
let target_env = inquire::Select::new(
"What environment are you targeting for your package?",
vec![
#[cfg(feature = "roblox")]
"roblox",
#[cfg(feature = "lune")]
"lune",
#[cfg(feature = "luau")]
"luau",
],
)
.prompt()
.unwrap();
let mut target = toml_edit::Table::new();
target["environment"] = toml_edit::value(target_env);
if target_env == "roblox"
|| inquire::Confirm::new(&format!(
"Would you like to setup a default {} script?",
ScriptName::RobloxSyncConfigGenerator
))
.prompt()
.unwrap()
{
let folder = project.path().join(concat!(".", env!("CARGO_PKG_NAME")));
std::fs::create_dir_all(&folder).context("failed to create scripts folder")?;
std::fs::write(
folder.join(format!("{}.luau", ScriptName::RobloxSyncConfigGenerator)),
script_contents(Path::new(&format!(
"lune/rojo/{}.luau",
ScriptName::RobloxSyncConfigGenerator
))),
)
.context("failed to write script file")?;
let scripts = manifest
.entry("scripts")
.or_insert(toml_edit::Item::Table(toml_edit::Table::new()))
.as_table_mut()
.unwrap();
let mut target = toml_edit::Table::new();
target["environment"] = toml_edit::value(target_env);
if target_env == "roblox"
|| inquire::Confirm::new(&format!(
"Would you like to setup a default {} script?",
ScriptName::RobloxSyncConfigGenerator
))
.prompt()
.unwrap()
{
let folder = project.path().join(concat!(".", env!("CARGO_PKG_NAME")));
std::fs::create_dir_all(&folder).context("failed to create scripts folder")?;
std::fs::write(
folder.join(format!("{}.luau", ScriptName::RobloxSyncConfigGenerator)),
script_contents(Path::new(&format!(
"lune/rojo/{}.luau",
ScriptName::RobloxSyncConfigGenerator
))),
)
.context("failed to write script file")?;
let scripts = manifest
.entry("scripts")
.or_insert(toml_edit::Item::Table(toml_edit::Table::new()))
.as_table_mut()
.unwrap();
scripts[&ScriptName::RobloxSyncConfigGenerator.to_string()] =
toml_edit::value(format!(
concat!(".", env!("CARGO_PKG_NAME"), "/{}.luau"),
ScriptName::RobloxSyncConfigGenerator
));
}
let mut indices = toml_edit::Table::new();
indices[DEFAULT_INDEX_NAME] =
toml_edit::value(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()),
scripts[&ScriptName::RobloxSyncConfigGenerator.to_string()] =
toml_edit::value(format!(
concat!(".", env!("CARGO_PKG_NAME"), "/{}.luau"),
ScriptName::RobloxSyncConfigGenerator
));
}
let mut indices = toml_edit::Table::new();
indices[DEFAULT_INDEX_NAME] =
toml_edit::value(read_config(project.data_dir())?.default_index.as_str());
project.write_manifest(manifest.to_string())?;
println!("{}", "initialized project".green());
Ok(())
}
}

View file

@ -16,7 +16,7 @@ impl InstallCommand {
.context("failed to read manifest")?;
let lockfile = if project
.is_up_to_date()
.is_up_to_date(false)
.context("failed to check if project is up to date")?
{
match project.deser_lockfile() {

View file

@ -1,4 +1,4 @@
use crate::git::authenticate_conn;
use crate::util::authenticate_conn;
use anyhow::Context;
use gix::remote::Direction;
use keyring::Entry;
@ -229,11 +229,11 @@ pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> {
}
pub trait IsUpToDate {
fn is_up_to_date(&self) -> anyhow::Result<bool>;
fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool>;
}
impl IsUpToDate for Project {
fn is_up_to_date(&self) -> anyhow::Result<bool> {
fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool> {
let manifest = self.deser_manifest()?;
let lockfile = match self.deser_lockfile() {
Ok(lockfile) => lockfile,
@ -245,6 +245,11 @@ impl IsUpToDate for Project {
Err(e) => return Err(e.into()),
};
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);
}
if manifest.name != lockfile.name || manifest.version != lockfile.version {
return Ok(false);
}

View file

@ -22,7 +22,7 @@ pub struct RunCommand {
impl RunCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
if let Ok(pkg_name) = self.package_or_script.parse::<PackageName>() {
let graph = if project.is_up_to_date()? {
let graph = if project.is_up_to_date(true)? {
project.deser_lockfile()?.graph
} else {
anyhow::bail!("outdated lockfile, please run the install command first")

View file

@ -8,7 +8,6 @@ use once_cell::sync::Lazy;
use std::path::{Path, PathBuf};
pub mod download;
mod git;
pub mod linking;
pub mod lockfile;
pub mod manifest;
@ -16,6 +15,7 @@ pub mod names;
pub mod resolver;
pub mod scripts;
pub mod source;
pub(crate) mod util;
pub const MANIFEST_FILE_NAME: &str = "pesde.toml";
pub const LOCKFILE_FILE_NAME: &str = "pesde.lock";

View file

@ -17,6 +17,7 @@ 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 ty: DependencyType,
}

View file

@ -5,7 +5,7 @@ use pesde::{AuthConfig, Project};
use std::fs::create_dir_all;
mod cli;
pub mod git;
pub mod util;
#[derive(Parser, Debug)]
#[clap(version, about = "pesde is a feature-rich package manager for Luau")]

View file

@ -11,7 +11,7 @@ use semver::Version;
use std::collections::{HashMap, HashSet, VecDeque};
impl Project {
// TODO: implement dependency overrides, account for targets using the is_compatible_with method
// TODO: account for targets using the is_compatible_with method
pub fn dependency_graph(
&self,
previous_graph: Option<&DependencyGraph>,
@ -101,17 +101,28 @@ impl Project {
let mut queue = all_specifiers
.into_iter()
.map(|((spec, ty), alias)| (alias, spec, ty, None::<(PackageNames, Version)>, 0usize))
.map(|((spec, ty), alias)| {
(
alias.to_string(),
spec,
ty,
None::<(PackageNames, Version)>,
vec![alias.to_string()],
false,
)
})
.collect::<VecDeque<_>>();
while let Some((alias, specifier, ty, dependant, depth)) = queue.pop_front() {
while let Some((alias, specifier, ty, dependant, path, overridden)) = queue.pop_front() {
let depth = path.len() - 1;
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_url = if depth == 0 || overridden {
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()),
@ -235,12 +246,28 @@ impl Project {
continue;
}
let overridden = manifest.overrides.iter().find_map(|(key, spec)| {
key.0.iter().find_map(|override_path| {
// if the path up until the last element is the same as the current path,
// and the last element in the path is the dependency alias,
// then the specifier is to be overridden
(path.len() == override_path.len() - 1
&& path == override_path[..override_path.len() - 1]
&& override_path.last() == Some(&dependency_alias))
.then_some(spec)
})
});
queue.push_back((
dependency_alias,
dependency_spec,
overridden.cloned().unwrap_or(dependency_spec),
dependency_ty,
Some((name.clone(), target_version.clone())),
depth + 1,
path.iter()
.cloned()
.chain(std::iter::once(alias.to_string()))
.collect(),
overridden.is_some(),
));
}
}

View file

@ -15,10 +15,10 @@ use pkg_ref::PesdePackageRef;
use specifier::PesdeDependencySpecifier;
use crate::{
git::authenticate_conn,
manifest::{DependencyType, Target, TargetKind},
names::{PackageName, PackageNames},
source::{hash, DependencySpecifiers, PackageSource, ResolveResult},
util::authenticate_conn,
Project, REQWEST_CLIENT,
};

View file

@ -13,7 +13,12 @@ use crate::{
pub struct PesdePackageRef {
pub name: PackageName,
pub version: Version,
#[serde(
serialize_with = "crate::util::serialize_gix_url",
deserialize_with = "crate::util::deserialize_gix_url"
)]
pub index_url: gix::Url,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
pub target: Target,
}

View file

@ -1,4 +1,6 @@
use crate::AuthConfig;
use gix::bstr::BStr;
use serde::{Deserialize, Deserializer, Serializer};
pub fn authenticate_conn(
conn: &mut gix::remote::Connection<
@ -21,3 +23,14 @@ pub fn authenticate_conn(
});
}
}
pub fn serialize_gix_url<S: Serializer>(url: &gix::Url, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&url.to_bstring().to_string())
}
pub fn deserialize_gix_url<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<gix::Url, D::Error> {
let s = String::deserialize(deserializer)?;
gix::Url::from_bytes(BStr::new(&s)).map_err(serde::de::Error::custom)
}