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 clap::Args;
use semver::VersionReq; use semver::VersionReq;
use crate::cli::{config::read_config, NamedVersionable, VersionedPackageName}; use crate::cli::{config::read_config, AnyPackageIdentifier, VersionedPackageName};
use pesde::{ use pesde::{
manifest::target::TargetKind, manifest::target::TargetKind,
names::PackageNames, names::PackageNames,
@ -13,6 +13,7 @@ use pesde::{
pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource}, pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource},
specifiers::DependencySpecifiers, specifiers::DependencySpecifiers,
traits::PackageSource, traits::PackageSource,
workspace::WorkspacePackageSource,
PackageSources, PackageSources,
}, },
Project, DEFAULT_INDEX_NAME, Project, DEFAULT_INDEX_NAME,
@ -22,7 +23,7 @@ use pesde::{
pub struct AddCommand { pub struct AddCommand {
/// The package name to add /// The package name to add
#[arg(index = 1)] #[arg(index = 1)]
name: NamedVersionable<VersionReq>, name: AnyPackageIdentifier<VersionReq>,
/// The index in which to search for the package /// The index in which to search for the package
#[arg(short, long)] #[arg(short, long)]
@ -52,7 +53,7 @@ impl AddCommand {
.context("failed to read manifest")?; .context("failed to read manifest")?;
let (source, specifier) = match &self.name { let (source, specifier) = match &self.name {
NamedVersionable::PackageName(versioned) => match &versioned { AnyPackageIdentifier::PackageName(versioned) => match &versioned {
VersionedPackageName(PackageNames::Pesde(name), version) => { VersionedPackageName(PackageNames::Pesde(name), version) => {
let index = manifest let index = manifest
.indices .indices
@ -103,7 +104,7 @@ impl AddCommand {
(source, specifier) (source, specifier)
} }
}, },
NamedVersionable::Url((url, rev)) => ( AnyPackageIdentifier::Url((url, rev)) => (
PackageSources::Git(GitPackageSource::new(url.clone())), PackageSources::Git(GitPackageSource::new(url.clone())),
DependencySpecifiers::Git(GitDependencySpecifier { DependencySpecifiers::Git(GitDependencySpecifier {
repo: url.clone(), repo: url.clone(),
@ -111,6 +112,15 @@ impl AddCommand {
path: None, 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 source
.refresh(&project) .refresh(&project)
@ -141,15 +151,16 @@ impl AddCommand {
"dependencies" "dependencies"
}; };
let alias = self.alias.unwrap_or_else(|| match self.name { let alias = self.alias.unwrap_or_else(|| match self.name.clone() {
NamedVersionable::PackageName(versioned) => versioned.0.as_str().1.to_string(), AnyPackageIdentifier::PackageName(versioned) => versioned.0.as_str().1.to_string(),
NamedVersionable::Url((url, _)) => url AnyPackageIdentifier::Url((url, _)) => url
.path .path
.to_string() .to_string()
.split('/') .split('/')
.last() .last()
.map(|s| s.to_string()) .map(|s| s.to_string())
.unwrap_or(url.path.to_string()), .unwrap_or(url.path.to_string()),
AnyPackageIdentifier::Workspace(versioned) => versioned.0.as_str().1.to_string(),
}); });
let field = &mut manifest[dependency_key] let field = &mut manifest[dependency_key]
@ -198,7 +209,19 @@ impl AddCommand {
println!("added git {}#{} to {}", spec.repo, spec.rev, dependency_key); 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 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 anyhow::Context;
use clap::Args; use clap::Args;
use colored::{ColoredString, Colorize};
use indicatif::MultiProgress; use indicatif::MultiProgress;
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME}; use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME};
use relative_path::RelativePathBuf; 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 { impl InstallCommand {
pub fn run( pub fn run(
self, self,
@ -85,12 +95,19 @@ impl InstallCommand {
let lockfile = if self.unlocked { let lockfile = if self.unlocked {
None None
} else if project } else {
.is_up_to_date(false)
.context("failed to check if project is up to date")?
{
match project.deser_lockfile() { 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)) Err(pesde::errors::LockfileReadError::Io(e))
if e.kind() == std::io::ErrorKind::NotFound => if e.kind() == std::io::ErrorKind::NotFound =>
{ {
@ -98,10 +115,17 @@ impl InstallCommand {
} }
Err(e) => return Err(e.into()), 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(); let mut deleted_folders = HashSet::new();
@ -137,6 +161,8 @@ impl InstallCommand {
.collect() .collect()
}); });
println!("{} 📦 building dependency graph", job(2));
let graph = project let graph = project
.dependency_graph(old_graph.as_ref(), &mut refreshed_sources) .dependency_graph(old_graph.as_ref(), &mut refreshed_sources)
.context("failed to build dependency graph")?; .context("failed to build dependency graph")?;
@ -148,7 +174,7 @@ impl InstallCommand {
"{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}", "{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)); bar.enable_steady_tick(Duration::from_millis(100));
@ -170,25 +196,19 @@ impl InstallCommand {
} }
} }
bar.finish_with_message(format!( bar.finish_with_message(format!("{} 📥 downloaded dependencies", job(3),));
"finished downloading dependencies of {}",
manifest.name
));
let downloaded_graph = Arc::into_inner(downloaded_graph) let downloaded_graph = Arc::into_inner(downloaded_graph)
.unwrap() .unwrap()
.into_inner() .into_inner()
.unwrap(); .unwrap();
println!("{} 🗺️ linking dependencies", job(4));
project project
.link_dependencies(&downloaded_graph) .link_dependencies(&downloaded_graph)
.context("failed to link dependencies")?; .context("failed to link dependencies")?;
#[cfg(feature = "patches")]
project
.apply_patches(&downloaded_graph)
.context("failed to apply patches")?;
let bin_folder = bin_dir()?; let bin_folder = bin_dir()?;
for versions in downloaded_graph.values() { 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 project
.write_lockfile(Lockfile { .write_lockfile(Lockfile {
name: manifest.name, 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 anyhow::Context;
use clap::Args; use clap::Args;
use colored::Colorize; use colored::Colorize;
use pesde::{ use pesde::{
patches::setup_patches_repo, patches::setup_patches_repo,
source::traits::{PackageRef, PackageSource}, source::{
refs::PackageRefs,
traits::{PackageRef, PackageSource},
},
Project, MANIFEST_FILE_NAME, Project, MANIFEST_FILE_NAME,
}; };
@ -17,8 +20,8 @@ pub struct PatchCommand {
impl PatchCommand { impl PatchCommand {
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
let graph = if project.is_up_to_date(true)? { let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? {
project.deser_lockfile()?.graph lockfile.graph
} else { } else {
anyhow::bail!("outdated lockfile, please run the install command first") anyhow::bail!("outdated lockfile, please run the install command first")
}; };
@ -29,6 +32,11 @@ impl PatchCommand {
.get(&name) .get(&name)
.and_then(|versions| versions.get(&version_id)) .and_then(|versions| versions.get(&version_id))
.context("package not found in graph")?; .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 source = node.node.pkg_ref.source();
let directory = project let directory = project

View file

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

View file

@ -4,7 +4,7 @@ use anyhow::Context;
use clap::Args; use clap::Args;
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use crate::cli::IsUpToDate; use crate::cli::up_to_date_lockfile;
use pesde::{ use pesde::{
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::traits::PackageRef, source::traits::PackageRef,
@ -49,8 +49,8 @@ impl RunCommand {
}; };
if let Ok(pkg_name) = package_or_script.parse::<PackageName>() { if let Ok(pkg_name) = package_or_script.parse::<PackageName>() {
let graph = if project.is_up_to_date(true)? { let graph = if let Some(lockfile) = up_to_date_lockfile(&project)? {
project.deser_lockfile()?.graph lockfile.graph
} else { } else {
anyhow::bail!("outdated lockfile, please run the install command first") 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 anyhow::Context;
use gix::bstr::BStr; use gix::bstr::BStr;
use pesde::{ 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 serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
use std::{ use std::{
@ -10,8 +14,6 @@ use std::{
str::FromStr, str::FromStr,
}; };
use crate::cli::auth::get_token;
pub mod auth; pub mod auth;
pub mod commands; pub mod commands;
pub mod config; pub mod config;
@ -33,62 +35,58 @@ pub fn bin_dir() -> anyhow::Result<std::path::PathBuf> {
Ok(bin_dir) Ok(bin_dir)
} }
pub trait IsUpToDate { pub fn up_to_date_lockfile(project: &Project) -> anyhow::Result<Option<Lockfile>> {
fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool>; let manifest = project.deser_manifest()?;
} let lockfile = match project.deser_lockfile() {
Ok(lockfile) => lockfile,
impl IsUpToDate for Project { Err(pesde::errors::LockfileReadError::Io(e))
fn is_up_to_date(&self, strict: bool) -> anyhow::Result<bool> { if e.kind() == std::io::ErrorKind::NotFound =>
let manifest = self.deser_manifest()?; {
let lockfile = match self.deser_lockfile() { return Ok(None);
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);
} }
Err(e) => return Err(e.into()),
};
if manifest.target.kind() != lockfile.target { if manifest.overrides != lockfile.overrides {
log::debug!("target kind is different"); log::debug!("overrides are different");
return Ok(false); return Ok(None);
}
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.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)] #[derive(Debug, Clone)]
@ -143,28 +141,37 @@ impl VersionedPackageName {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum NamedVersionable<V: FromStr = VersionId, N: FromStr = PackageNames> { enum AnyPackageIdentifier<V: FromStr = VersionId, N: FromStr = PackageNames> {
PackageName(VersionedPackageName<V, N>), PackageName(VersionedPackageName<V, N>),
Url((gix::Url, String)), 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>> 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; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains("gh#") { if let Some(s) = s.strip_prefix("gh#") {
let s = s.replacen("gh#", "https://github.com/", 1); let s = format!("https://github.com/{s}");
let (repo, rev) = s.split_once('#').unwrap(); 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(':') { } else if s.contains(':') {
let (url, rev) = s.split_once('#').unwrap(); 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 { } else {
Ok(NamedVersionable::PackageName(s.parse()?)) Ok(AnyPackageIdentifier::PackageName(s.parse()?))
} }
} }
} }