feat: support adding workspace packages

This commit is contained in:
daimond113 2024-09-03 18:19:01 +02:00
parent 5236be53fd
commit 71eacb8673
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
6 changed files with 168 additions and 99 deletions

View file

@ -4,7 +4,7 @@ use anyhow::Context;
use clap::Args;
use semver::VersionReq;
use crate::cli::{config::read_config, NamedVersionable, VersionedPackageName};
use crate::cli::{config::read_config, AnyPackageIdentifier, VersionedPackageName};
use pesde::{
manifest::target::TargetKind,
names::PackageNames,
@ -13,6 +13,7 @@ use pesde::{
pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource},
specifiers::DependencySpecifiers,
traits::PackageSource,
workspace::WorkspacePackageSource,
PackageSources,
},
Project, DEFAULT_INDEX_NAME,
@ -22,7 +23,7 @@ use pesde::{
pub struct AddCommand {
/// The package name to add
#[arg(index = 1)]
name: NamedVersionable<VersionReq>,
name: AnyPackageIdentifier<VersionReq>,
/// The index in which to search for the package
#[arg(short, long)]
@ -52,7 +53,7 @@ impl AddCommand {
.context("failed to read manifest")?;
let (source, specifier) = match &self.name {
NamedVersionable::PackageName(versioned) => match &versioned {
AnyPackageIdentifier::PackageName(versioned) => match &versioned {
VersionedPackageName(PackageNames::Pesde(name), version) => {
let index = manifest
.indices
@ -103,7 +104,7 @@ impl AddCommand {
(source, specifier)
}
},
NamedVersionable::Url((url, rev)) => (
AnyPackageIdentifier::Url((url, rev)) => (
PackageSources::Git(GitPackageSource::new(url.clone())),
DependencySpecifiers::Git(GitDependencySpecifier {
repo: url.clone(),
@ -111,6 +112,15 @@ impl AddCommand {
path: None,
}),
),
AnyPackageIdentifier::Workspace(VersionedPackageName(name, version)) => (
PackageSources::Workspace(WorkspacePackageSource),
DependencySpecifiers::Workspace(
pesde::source::workspace::specifier::WorkspaceDependencySpecifier {
name: name.clone(),
version_type: version.unwrap_or_default(),
},
),
),
};
source
.refresh(&project)
@ -141,15 +151,16 @@ impl AddCommand {
"dependencies"
};
let alias = self.alias.unwrap_or_else(|| match self.name {
NamedVersionable::PackageName(versioned) => versioned.0.as_str().1.to_string(),
NamedVersionable::Url((url, _)) => url
let alias = self.alias.unwrap_or_else(|| match self.name.clone() {
AnyPackageIdentifier::PackageName(versioned) => versioned.0.as_str().1.to_string(),
AnyPackageIdentifier::Url((url, _)) => url
.path
.to_string()
.split('/')
.last()
.map(|s| s.to_string())
.unwrap_or(url.path.to_string()),
AnyPackageIdentifier::Workspace(versioned) => versioned.0.as_str().1.to_string(),
});
let field = &mut manifest[dependency_key]
@ -198,7 +209,19 @@ impl AddCommand {
println!("added git {}#{} to {}", spec.repo, spec.rev, dependency_key);
}
DependencySpecifiers::Workspace(_) => todo!(),
DependencySpecifiers::Workspace(spec) => {
field["workspace"] = toml_edit::value(spec.name.clone().to_string());
if let AnyPackageIdentifier::Workspace(versioned) = self.name {
if let Some(version) = versioned.1 {
field["version"] = toml_edit::value(version.to_string());
}
}
println!(
"added workspace {}@{} to {}",
spec.name, spec.version_type, dependency_key
);
}
}
project

View file

@ -1,6 +1,7 @@
use crate::cli::{bin_dir, files::make_executable, IsUpToDate};
use crate::cli::{bin_dir, files::make_executable};
use anyhow::Context;
use clap::Args;
use colored::{ColoredString, Colorize};
use indicatif::MultiProgress;
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME};
use relative_path::RelativePathBuf;
@ -70,6 +71,15 @@ end
)
}
#[cfg(feature = "patches")]
const JOBS: u8 = 6;
#[cfg(not(feature = "patches"))]
const JOBS: u8 = 5;
fn job(n: u8) -> ColoredString {
format!("[{n}/{JOBS}]").dimmed().bold()
}
impl InstallCommand {
pub fn run(
self,
@ -85,12 +95,19 @@ impl InstallCommand {
let lockfile = if self.unlocked {
None
} else if project
.is_up_to_date(false)
.context("failed to check if project is up to date")?
{
} else {
match project.deser_lockfile() {
Ok(lockfile) => Some(lockfile),
Ok(lockfile) => {
if lockfile.overrides != manifest.overrides {
log::debug!("overrides are different");
None
} else if lockfile.target != manifest.target.kind() {
log::debug!("target kind is different");
None
} else {
Some(lockfile)
}
}
Err(pesde::errors::LockfileReadError::Io(e))
if e.kind() == std::io::ErrorKind::NotFound =>
{
@ -98,10 +115,17 @@ impl InstallCommand {
}
Err(e) => return Err(e.into()),
}
} else {
None
};
println!(
"\n{}\n",
format!("[now installing {}]", manifest.name)
.bold()
.on_bright_black()
);
println!("{} ❌ removing current package folders", job(1));
{
let mut deleted_folders = HashSet::new();
@ -137,6 +161,8 @@ impl InstallCommand {
.collect()
});
println!("{} 📦 building dependency graph", job(2));
let graph = project
.dependency_graph(old_graph.as_ref(), &mut refreshed_sources)
.context("failed to build dependency graph")?;
@ -148,7 +174,7 @@ impl InstallCommand {
"{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}",
)?,
)
.with_message(format!("downloading dependencies of {}", manifest.name)),
.with_message(format!("{} 📥 downloading dependencies", job(3))),
);
bar.enable_steady_tick(Duration::from_millis(100));
@ -170,25 +196,19 @@ impl InstallCommand {
}
}
bar.finish_with_message(format!(
"finished downloading dependencies of {}",
manifest.name
));
bar.finish_with_message(format!("{} 📥 downloaded dependencies", job(3),));
let downloaded_graph = Arc::into_inner(downloaded_graph)
.unwrap()
.into_inner()
.unwrap();
println!("{} 🗺️ linking dependencies", job(4));
project
.link_dependencies(&downloaded_graph)
.context("failed to link dependencies")?;
#[cfg(feature = "patches")]
project
.apply_patches(&downloaded_graph)
.context("failed to apply patches")?;
let bin_folder = bin_dir()?;
for versions in downloaded_graph.values() {
@ -224,6 +244,17 @@ impl InstallCommand {
}
}
#[cfg(feature = "patches")]
{
println!("{} 🩹 applying patches", job(5));
project
.apply_patches(&downloaded_graph)
.context("failed to apply patches")?;
}
println!("{} 🧹 finishing up", job(JOBS));
project
.write_lockfile(Lockfile {
name: manifest.name,

View file

@ -1,10 +1,13 @@
use crate::cli::{IsUpToDate, VersionedPackageName};
use crate::cli::{up_to_date_lockfile, VersionedPackageName};
use anyhow::Context;
use clap::Args;
use colored::Colorize;
use pesde::{
patches::setup_patches_repo,
source::traits::{PackageRef, PackageSource},
source::{
refs::PackageRefs,
traits::{PackageRef, PackageSource},
},
Project, MANIFEST_FILE_NAME,
};
@ -17,8 +20,8 @@ pub struct PatchCommand {
impl PatchCommand {
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
let graph = if project.is_up_to_date(true)? {
project.deser_lockfile()?.graph
let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? {
lockfile.graph
} else {
anyhow::bail!("outdated lockfile, please run the install command first")
};
@ -29,6 +32,11 @@ impl PatchCommand {
.get(&name)
.and_then(|versions| versions.get(&version_id))
.context("package not found in graph")?;
if matches!(node.node.pkg_ref, PackageRefs::Workspace(_)) {
anyhow::bail!("cannot patch a workspace package")
}
let source = node.node.pkg_ref.source();
let directory = project

View file

@ -1,4 +1,4 @@
use crate::cli::IsUpToDate;
use crate::cli::up_to_date_lockfile;
use anyhow::Context;
use clap::Args;
use pesde::{names::PackageNames, patches::create_patch, source::version_id::VersionId, Project};
@ -13,8 +13,8 @@ pub struct PatchCommitCommand {
impl PatchCommitCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
let graph = if project.is_up_to_date(true)? {
project.deser_lockfile()?.graph
let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? {
lockfile.graph
} else {
anyhow::bail!("outdated lockfile, please run the install command first")
};

View file

@ -4,7 +4,7 @@ use anyhow::Context;
use clap::Args;
use relative_path::RelativePathBuf;
use crate::cli::IsUpToDate;
use crate::cli::up_to_date_lockfile;
use pesde::{
names::{PackageName, PackageNames},
source::traits::PackageRef,
@ -49,8 +49,8 @@ impl RunCommand {
};
if let Ok(pkg_name) = package_or_script.parse::<PackageName>() {
let graph = if project.is_up_to_date(true)? {
project.deser_lockfile()?.graph
let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? {
lockfile.graph
} else {
anyhow::bail!("outdated lockfile, please run the install command first")
};

View file

@ -1,7 +1,11 @@
use crate::cli::auth::get_token;
use anyhow::Context;
use gix::bstr::BStr;
use pesde::{
lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project,
lockfile::{DownloadedGraph, Lockfile},
names::{PackageName, PackageNames},
source::{version_id::VersionId, workspace::specifier::VersionType},
Project,
};
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
use std::{
@ -10,8 +14,6 @@ use std::{
str::FromStr,
};
use crate::cli::auth::get_token;
pub mod auth;
pub mod commands;
pub mod config;
@ -33,62 +35,58 @@ pub fn bin_dir() -> anyhow::Result<std::path::PathBuf> {
Ok(bin_dir)
}
pub trait IsUpToDate {
fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool>;
}
impl IsUpToDate for Project {
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,
Err(pesde::errors::LockfileReadError::Io(e))
if e.kind() == std::io::ErrorKind::NotFound =>
{
return Ok(false);
}
Err(e) => return Err(e.into()),
};
if manifest.overrides != lockfile.overrides {
log::debug!("overrides are different");
return Ok(false);
pub fn up_to_date_lockfile(project: &Project) -> anyhow::Result<Option<Lockfile>> {
let manifest = project.deser_manifest()?;
let lockfile = match project.deser_lockfile() {
Ok(lockfile) => lockfile,
Err(pesde::errors::LockfileReadError::Io(e))
if e.kind() == std::io::ErrorKind::NotFound =>
{
return Ok(None);
}
Err(e) => return Err(e.into()),
};
if manifest.target.kind() != lockfile.target {
log::debug!("target kind is different");
return Ok(false);
}
if !strict {
return Ok(true);
}
if manifest.name != lockfile.name || manifest.version != lockfile.version {
log::debug!("name or version is different");
return Ok(false);
}
let specs = lockfile
.graph
.into_iter()
.flat_map(|(_, versions)| versions)
.filter_map(|(_, node)| match node.node.direct {
Some((_, spec)) => Some((spec, node.node.ty)),
None => None,
})
.collect::<HashSet<_>>();
let same_dependencies = manifest
.all_dependencies()
.context("failed to get all dependencies")?
.iter()
.all(|(_, (spec, ty))| specs.contains(&(spec.clone(), *ty)));
log::debug!("dependencies are the same: {same_dependencies}");
Ok(same_dependencies)
if manifest.overrides != lockfile.overrides {
log::debug!("overrides are different");
return Ok(None);
}
if manifest.target.kind() != lockfile.target {
log::debug!("target kind is different");
return Ok(None);
}
if manifest.name != lockfile.name || manifest.version != lockfile.version {
log::debug!("name or version is different");
return Ok(None);
}
let specs = lockfile
.graph
.iter()
.flat_map(|(_, versions)| versions)
.filter_map(|(_, node)| {
node.node
.direct
.as_ref()
.map(|(_, spec)| (spec, node.node.ty))
})
.collect::<HashSet<_>>();
let same_dependencies = manifest
.all_dependencies()
.context("failed to get all dependencies")?
.iter()
.all(|(_, (spec, ty))| specs.contains(&(spec, *ty)));
log::debug!("dependencies are the same: {same_dependencies}");
Ok(if same_dependencies {
Some(lockfile)
} else {
None
})
}
#[derive(Debug, Clone)]
@ -143,28 +141,37 @@ impl VersionedPackageName {
}
#[derive(Debug, Clone)]
enum NamedVersionable<V: FromStr = VersionId, N: FromStr = PackageNames> {
enum AnyPackageIdentifier<V: FromStr = VersionId, N: FromStr = PackageNames> {
PackageName(VersionedPackageName<V, N>),
Url((gix::Url, String)),
Workspace(VersionedPackageName<VersionType, PackageName>),
}
impl<V: FromStr<Err = E>, E: Into<anyhow::Error>, N: FromStr<Err = F>, F: Into<anyhow::Error>>
FromStr for NamedVersionable<V, N>
FromStr for AnyPackageIdentifier<V, N>
{
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains("gh#") {
let s = s.replacen("gh#", "https://github.com/", 1);
if let Some(s) = s.strip_prefix("gh#") {
let s = format!("https://github.com/{s}");
let (repo, rev) = s.split_once('#').unwrap();
Ok(NamedVersionable::Url((repo.try_into()?, rev.to_string())))
Ok(AnyPackageIdentifier::Url((
repo.try_into()?,
rev.to_string(),
)))
} else if let Some(rest) = s.strip_prefix("workspace:") {
Ok(AnyPackageIdentifier::Workspace(rest.parse()?))
} else if s.contains(':') {
let (url, rev) = s.split_once('#').unwrap();
Ok(NamedVersionable::Url((url.try_into()?, rev.to_string())))
Ok(AnyPackageIdentifier::Url((
url.try_into()?,
rev.to_string(),
)))
} else {
Ok(NamedVersionable::PackageName(s.parse()?))
Ok(AnyPackageIdentifier::PackageName(s.parse()?))
}
}
}