feat: support adding git dependencies with the cli

This commit is contained in:
daimond113 2024-08-11 20:08:27 +02:00
parent 876fa21bcb
commit 7267dc2300
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
3 changed files with 118 additions and 75 deletions

View file

@ -4,10 +4,12 @@ use anyhow::Context;
use clap::Args; use clap::Args;
use semver::VersionReq; use semver::VersionReq;
use crate::cli::{config::read_config, NamedVersionable, VersionedPackageName};
use pesde::{ use pesde::{
manifest::target::TargetKind, manifest::target::TargetKind,
names::PackageNames, names::PackageNames,
source::{ source::{
git::{specifier::GitDependencySpecifier, GitPackageSource},
pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource}, pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource},
specifiers::DependencySpecifiers, specifiers::DependencySpecifiers,
traits::PackageSource, traits::PackageSource,
@ -16,13 +18,11 @@ use pesde::{
Project, DEFAULT_INDEX_NAME, Project, DEFAULT_INDEX_NAME,
}; };
use crate::cli::{config::read_config, VersionedPackageName};
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddCommand { pub struct AddCommand {
/// The package name to add /// The package name to add
#[arg(index = 1)] #[arg(index = 1)]
name: VersionedPackageName<VersionReq>, name: NamedVersionable<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)]
@ -51,60 +51,70 @@ impl AddCommand {
.deser_manifest() .deser_manifest()
.context("failed to read manifest")?; .context("failed to read manifest")?;
let source = match &self.name.0 { let (source, specifier) = match &self.name {
PackageNames::Pesde(_) => { NamedVersionable::PackageName(versioned) => match &versioned {
let index = manifest VersionedPackageName(PackageNames::Pesde(name), version) => {
.indices let index = manifest
.get(self.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME)) .indices
.cloned(); .get(self.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME))
.cloned();
if let Some(index) = self.index.as_ref().filter(|_| index.is_none()) { if let Some(index) = self.index.as_ref().filter(|_| index.is_none()) {
log::error!("index {index} not found"); log::error!("index {index} not found");
return Ok(()); return Ok(());
}
let index = index.unwrap_or(read_config()?.default_index);
let source = PackageSources::Pesde(PesdePackageSource::new(index));
let specifier = DependencySpecifiers::Pesde(PesdeDependencySpecifier {
name: name.clone(),
version: version.clone().unwrap_or(VersionReq::STAR),
index: self.index,
target: self.target,
});
(source, specifier)
} }
#[cfg(feature = "wally-compat")]
VersionedPackageName(PackageNames::Wally(name), version) => {
let index = manifest
.wally_indices
.get(self.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME))
.cloned();
let index = index.unwrap_or(read_config()?.default_index); if let Some(index) = self.index.as_ref().filter(|_| index.is_none()) {
log::error!("wally index {index} not found");
return Ok(());
}
PackageSources::Pesde(PesdePackageSource::new(index)) let index = index.unwrap_or(read_config()?.default_index);
}
#[cfg(feature = "wally-compat")]
PackageNames::Wally(_) => {
let index = manifest
.wally_indices
.get(self.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME))
.cloned();
if let Some(index) = self.index.as_ref().filter(|_| index.is_none()) { let source =
log::error!("wally index {index} not found"); PackageSources::Wally(pesde::source::wally::WallyPackageSource::new(index));
return Ok(()); let specifier = DependencySpecifiers::Wally(
pesde::source::wally::specifier::WallyDependencySpecifier {
name: name.clone(),
version: version.clone().unwrap_or(VersionReq::STAR),
index: self.index,
},
);
(source, specifier)
} }
},
let index = index.unwrap_or(read_config()?.default_index); NamedVersionable::Url((url, rev)) => (
PackageSources::Git(GitPackageSource::new(url.clone())),
PackageSources::Wally(pesde::source::wally::WallyPackageSource::new(index)) DependencySpecifiers::Git(GitDependencySpecifier {
} repo: url.clone(),
rev: rev.to_string(),
}),
),
}; };
source source
.refresh(&project) .refresh(&project)
.context("failed to refresh package source")?; .context("failed to refresh package source")?;
let specifier = match &self.name.0 {
PackageNames::Pesde(name) => DependencySpecifiers::Pesde(PesdeDependencySpecifier {
name: name.clone(),
version: self.name.1.unwrap_or(VersionReq::STAR),
index: self.index,
target: self.target,
}),
#[cfg(feature = "wally-compat")]
PackageNames::Wally(name) => DependencySpecifiers::Wally(
pesde::source::wally::specifier::WallyDependencySpecifier {
name: name.clone(),
version: self.name.1.unwrap_or(VersionReq::STAR),
index: self.index,
},
),
};
let Some(version_id) = source let Some(version_id) = source
.resolve(&specifier, &project, manifest.target.kind()) .resolve(&specifier, &project, manifest.target.kind())
.context("failed to resolve package")? .context("failed to resolve package")?
@ -112,11 +122,7 @@ impl AddCommand {
.pop_last() .pop_last()
.map(|(v_id, _)| v_id) .map(|(v_id, _)| v_id)
else { else {
log::error!( log::error!("no versions found for package {specifier}");
"no versions found for package: {} (current target: {}, try a different one)",
self.name.0,
manifest.target.kind()
);
return Ok(()); return Ok(());
}; };
@ -134,25 +140,31 @@ impl AddCommand {
"dependencies" "dependencies"
}; };
let alias = self let alias = self.alias.unwrap_or_else(|| match self.name {
.alias NamedVersionable::PackageName(versioned) => versioned.0.as_str().1.to_string(),
.as_deref() NamedVersionable::Url((url, _)) => url
.unwrap_or_else(|| self.name.0.as_str().1); .path
.to_string()
.split('/')
.last()
.map(|s| s.to_string())
.unwrap_or(url.path.to_string()),
});
match specifier { match specifier {
DependencySpecifiers::Pesde(spec) => { DependencySpecifiers::Pesde(spec) => {
manifest[dependency_key][alias]["name"] = manifest[dependency_key][&alias]["name"] =
toml_edit::value(spec.name.clone().to_string()); toml_edit::value(spec.name.clone().to_string());
manifest[dependency_key][alias]["version"] = manifest[dependency_key][&alias]["version"] =
toml_edit::value(format!("^{}", version_id.version())); toml_edit::value(format!("^{}", version_id.version()));
if *version_id.target() != project_target { if *version_id.target() != project_target {
manifest[dependency_key][alias]["target"] = manifest[dependency_key][&alias]["target"] =
toml_edit::value(version_id.target().to_string()); toml_edit::value(version_id.target().to_string());
} }
if let Some(index) = spec.index.filter(|i| i != DEFAULT_INDEX_NAME) { if let Some(index) = spec.index.filter(|i| i != DEFAULT_INDEX_NAME) {
manifest[dependency_key][alias]["index"] = toml_edit::value(index); manifest[dependency_key][&alias]["index"] = toml_edit::value(index);
} }
println!( println!(
@ -165,13 +177,13 @@ impl AddCommand {
} }
#[cfg(feature = "wally-compat")] #[cfg(feature = "wally-compat")]
DependencySpecifiers::Wally(spec) => { DependencySpecifiers::Wally(spec) => {
manifest[dependency_key][alias]["wally"] = manifest[dependency_key][&alias]["wally"] =
toml_edit::value(spec.name.clone().to_string()); toml_edit::value(spec.name.clone().to_string());
manifest[dependency_key][alias]["version"] = manifest[dependency_key][&alias]["version"] =
toml_edit::value(format!("^{}", version_id.version())); toml_edit::value(format!("^{}", version_id.version()));
if let Some(index) = spec.index.filter(|i| i != DEFAULT_INDEX_NAME) { if let Some(index) = spec.index.filter(|i| i != DEFAULT_INDEX_NAME) {
manifest[dependency_key][alias]["index"] = toml_edit::value(index); manifest[dependency_key][&alias]["index"] = toml_edit::value(index);
} }
println!( println!(
@ -181,8 +193,12 @@ impl AddCommand {
dependency_key dependency_key
); );
} }
DependencySpecifiers::Git(_) => { DependencySpecifiers::Git(spec) => {
unreachable!("git dependencies are not supported in the add command"); manifest[dependency_key][&alias]["repo"] =
toml_edit::value(spec.repo.to_bstring().to_string());
manifest[dependency_key][&alias]["rev"] = toml_edit::value(spec.rev.clone());
println!("added git {}#{} to {}", spec.repo, spec.rev, dependency_key);
} }
} }

View file

@ -1,3 +1,13 @@
use std::{collections::HashSet, fs::create_dir_all, str::FromStr};
use anyhow::Context;
use pesde::{
lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project,
};
use crate::cli::auth::get_token;
pub mod auth; pub mod auth;
pub mod commands; pub mod commands;
pub mod config; pub mod config;
@ -5,13 +15,6 @@ pub mod files;
pub mod scripts; pub mod scripts;
pub mod version; pub mod version;
use crate::cli::auth::get_token;
use anyhow::Context;
use pesde::{
lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project,
};
use std::{collections::HashSet, fs::create_dir_all, str::FromStr};
pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME")); pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME"));
pub fn home_dir() -> anyhow::Result<std::path::PathBuf> { pub fn home_dir() -> anyhow::Result<std::path::PathBuf> {
@ -135,6 +138,33 @@ impl VersionedPackageName {
} }
} }
#[derive(Debug, Clone)]
enum NamedVersionable<V: FromStr = VersionId, N: FromStr = PackageNames> {
PackageName(VersionedPackageName<V, N>),
Url((gix::Url, String)),
}
impl<V: FromStr<Err = E>, E: Into<anyhow::Error>, N: FromStr<Err = F>, F: Into<anyhow::Error>>
FromStr for NamedVersionable<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);
let (repo, rev) = s.split_once('#').unwrap();
Ok(NamedVersionable::Url((repo.try_into()?, rev.to_string())))
} else if s.contains(':') {
let (url, rev) = s.split_once('#').unwrap();
Ok(NamedVersionable::Url((url.try_into()?, rev.to_string())))
} else {
Ok(NamedVersionable::PackageName(s.parse()?))
}
}
}
pub fn parse_gix_url(s: &str) -> Result<gix::Url, gix::url::parse::Error> { pub fn parse_gix_url(s: &str) -> Result<gix::Url, gix::url::parse::Error> {
s.try_into() s.try_into()
} }

View file

@ -2,7 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{manifest::target::TargetKind, source::DependencySpecifier}; use crate::source::DependencySpecifier;
/// The specifier for a Git dependency /// The specifier for a Git dependency
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
@ -15,9 +15,6 @@ pub struct GitDependencySpecifier {
pub repo: gix::Url, pub repo: gix::Url,
/// The revision of the package /// The revision of the package
pub rev: String, pub rev: String,
/// The target to use for the package
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<TargetKind>,
} }
impl DependencySpecifier for GitDependencySpecifier {} impl DependencySpecifier for GitDependencySpecifier {}