mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: implement workspace/monorepo support
This commit is contained in:
parent
bd7e1452b0
commit
f1ce6283d8
34 changed files with 880 additions and 202 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -2335,6 +2335,12 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "governor"
|
||||
version = "0.6.3"
|
||||
|
@ -2855,9 +2861,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4"
|
||||
checksum = "030a9b84bb2a2f3673d4c8b8236091ed5d8f6b66a56d8085471d8abd5f3c6a80"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
|
@ -3506,6 +3512,7 @@ dependencies = [
|
|||
"full_moon",
|
||||
"git2",
|
||||
"gix",
|
||||
"glob",
|
||||
"indicatif",
|
||||
"indicatif-log-bridge",
|
||||
"inquire",
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -43,14 +43,14 @@ required-features = ["bin"]
|
|||
uninlined_format_args = "warn"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
toml = "0.8.19"
|
||||
serde_with = "3.9.0"
|
||||
gix = { version = "0.66.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"] }
|
||||
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls", "blocking"] }
|
||||
tar = "0.4.41"
|
||||
flate2 = "1.0.31"
|
||||
flate2 = "1.0.33"
|
||||
pathdiff = "0.2.1"
|
||||
relative-path = { version = "1.9.3", features = ["serde"] }
|
||||
log = "0.4.22"
|
||||
|
@ -63,23 +63,24 @@ url = { version = "2.5.2", features = ["serde"] }
|
|||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
sha2 = "0.10.8"
|
||||
tempfile = "3.12.0"
|
||||
glob = "0.3.1"
|
||||
|
||||
# TODO: remove this when gitoxide adds support for: committing, pushing, adding
|
||||
git2 = { version = "0.19.0", optional = true }
|
||||
|
||||
zip = { version = "2.1.6", optional = true }
|
||||
serde_json = { version = "1.0.122", optional = true }
|
||||
zip = { version = "2.2.0", optional = true }
|
||||
serde_json = { version = "1.0.127", optional = true }
|
||||
|
||||
anyhow = { version = "1.0.86", optional = true }
|
||||
open = { version = "5.3.0", optional = true }
|
||||
keyring = { version = "3.0.5", features = ["crypto-rust", "windows-native", "apple-native", "sync-secret-service"], optional = true }
|
||||
keyring = { version = "3.2.1", features = ["crypto-rust", "windows-native", "apple-native", "sync-secret-service"], optional = true }
|
||||
colored = { version = "2.1.0", optional = true }
|
||||
toml_edit = { version = "0.22.20", optional = true }
|
||||
clap = { version = "4.5.13", features = ["derive"], optional = true }
|
||||
clap = { version = "4.5.16", features = ["derive"], optional = true }
|
||||
dirs = { version = "5.0.1", optional = true }
|
||||
pretty_env_logger = { version = "0.5.0", optional = true }
|
||||
indicatif = { version = "0.17.8", optional = true }
|
||||
indicatif-log-bridge = { version = "0.2.2", optional = true }
|
||||
indicatif-log-bridge = { version = "0.2.3", optional = true }
|
||||
inquire = { version = "0.7.5", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
|
|
|
@ -298,13 +298,6 @@ pub async fn publish_package(
|
|||
{
|
||||
return Err(Error::InvalidArchive);
|
||||
}
|
||||
|
||||
let (dep_scope, dep_name) = specifier.name.as_str();
|
||||
match source.read_file([dep_scope, dep_name], &app_state.project, None) {
|
||||
Ok(Some(_)) => {}
|
||||
Ok(None) => return Err(Error::InvalidArchive),
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
DependencySpecifiers::Wally(specifier) => {
|
||||
if !config.wally_allowed {
|
||||
|
@ -325,6 +318,10 @@ pub async fn publish_package(
|
|||
return Err(Error::InvalidArchive);
|
||||
}
|
||||
}
|
||||
DependencySpecifiers::Workspace(_) => {
|
||||
// workspace specifiers are to be transformed into Pesde specifiers by the sender
|
||||
return Err(Error::InvalidArchive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::{env::current_dir, fs::create_dir_all, sync::Mutex};
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_governor::{Governor, GovernorConfigBuilder};
|
||||
use actix_web::{
|
||||
|
@ -9,6 +7,7 @@ use actix_web::{
|
|||
};
|
||||
use log::info;
|
||||
use rusty_s3::{Bucket, Credentials, UrlStyle};
|
||||
use std::{env::current_dir, fs::create_dir_all, path::PathBuf, sync::Mutex};
|
||||
|
||||
use pesde::{
|
||||
source::{pesde::PesdePackageSource, traits::PackageSource},
|
||||
|
@ -78,6 +77,7 @@ async fn run(with_sentry: bool) -> std::io::Result<()> {
|
|||
|
||||
let project = Project::new(
|
||||
&cwd,
|
||||
None::<PathBuf>,
|
||||
data_dir.join("project"),
|
||||
&cwd,
|
||||
AuthConfig::new().with_git_credentials(Some(gix::sec::identity::Account {
|
||||
|
|
|
@ -197,6 +197,7 @@ impl AddCommand {
|
|||
|
||||
println!("added git {}#{} to {}", spec.repo, spec.rev, dependency_key);
|
||||
}
|
||||
DependencySpecifiers::Workspace(_) => todo!(),
|
||||
}
|
||||
|
||||
project
|
||||
|
|
|
@ -77,7 +77,7 @@ impl ExecuteCommand {
|
|||
.arg(bin_path.to_path(tempdir.path()))
|
||||
.arg("--")
|
||||
.args(&self.args)
|
||||
.current_dir(project.path())
|
||||
.current_dir(project.package_dir())
|
||||
.status()
|
||||
.context("failed to run script")?;
|
||||
|
||||
|
|
|
@ -133,7 +133,9 @@ impl InitCommand {
|
|||
.prompt()
|
||||
.unwrap()
|
||||
{
|
||||
let folder = project.path().join(concat!(".", env!("CARGO_PKG_NAME")));
|
||||
let folder = project
|
||||
.package_dir()
|
||||
.join(concat!(".", env!("CARGO_PKG_NAME")));
|
||||
std::fs::create_dir_all(&folder).context("failed to create scripts folder")?;
|
||||
|
||||
std::fs::write(
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
use crate::cli::{bin_dir, files::make_executable, IsUpToDate};
|
||||
use anyhow::Context;
|
||||
use clap::Args;
|
||||
use indicatif::MultiProgress;
|
||||
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME};
|
||||
use relative_path::RelativePathBuf;
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Args;
|
||||
use indicatif::MultiProgress;
|
||||
|
||||
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME};
|
||||
|
||||
use crate::cli::{bin_dir, files::make_executable, IsUpToDate};
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct InstallCommand {
|
||||
/// The amount of threads to use for downloading
|
||||
|
@ -44,7 +42,7 @@ fn bin_link_file(alias: &str) -> String {
|
|||
let prefix = String::new();
|
||||
#[cfg(unix)]
|
||||
let prefix = "#!/usr/bin/env -S lune run\n";
|
||||
|
||||
// TODO: reimplement workspace support in this
|
||||
format!(
|
||||
r#"{prefix}local process = require("@lune/process")
|
||||
local fs = require("@lune/fs")
|
||||
|
@ -113,7 +111,7 @@ impl InstallCommand {
|
|||
if deleted_folders.insert(folder.to_string()) {
|
||||
log::debug!("deleting the {folder} folder");
|
||||
|
||||
if let Some(e) = std::fs::remove_dir_all(project.path().join(&folder))
|
||||
if let Some(e) = std::fs::remove_dir_all(project.package_dir().join(&folder))
|
||||
.err()
|
||||
.filter(|e| e.kind() != std::io::ErrorKind::NotFound)
|
||||
{
|
||||
|
@ -150,7 +148,7 @@ impl InstallCommand {
|
|||
"{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}",
|
||||
)?,
|
||||
)
|
||||
.with_message("downloading dependencies"),
|
||||
.with_message(format!("downloading dependencies of {}", manifest.name)),
|
||||
);
|
||||
bar.enable_steady_tick(Duration::from_millis(100));
|
||||
|
||||
|
@ -172,7 +170,10 @@ impl InstallCommand {
|
|||
}
|
||||
}
|
||||
|
||||
bar.finish_with_message("finished downloading dependencies");
|
||||
bar.finish_with_message(format!(
|
||||
"finished downloading dependencies of {}",
|
||||
manifest.name
|
||||
));
|
||||
|
||||
let downloaded_graph = Arc::into_inner(downloaded_graph)
|
||||
.unwrap()
|
||||
|
@ -229,6 +230,46 @@ impl InstallCommand {
|
|||
version: manifest.version,
|
||||
target: manifest.target.kind(),
|
||||
overrides: manifest.overrides,
|
||||
workspace: match project.workspace_dir() {
|
||||
Some(_) => {
|
||||
// this might seem counterintuitive, but remember that the workspace
|
||||
// is the package_dir when the user isn't in a member package
|
||||
Default::default()
|
||||
}
|
||||
None => project
|
||||
.workspace_members(project.package_dir())
|
||||
.context("failed to get workspace members")?
|
||||
.into_iter()
|
||||
.map(|(path, manifest)| {
|
||||
(
|
||||
manifest.name,
|
||||
RelativePathBuf::from_path(
|
||||
path.strip_prefix(project.package_dir()).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.map(|(name, path)| {
|
||||
InstallCommand {
|
||||
threads: self.threads,
|
||||
unlocked: self.unlocked,
|
||||
}
|
||||
.run(
|
||||
Project::new(
|
||||
path.to_path(project.package_dir()),
|
||||
Some(project.package_dir()),
|
||||
project.data_dir(),
|
||||
project.cas_dir(),
|
||||
project.auth_config().clone(),
|
||||
),
|
||||
multi.clone(),
|
||||
reqwest.clone(),
|
||||
)
|
||||
.map(|_| (name, path))
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.context("failed to install workspace member's dependencies")?,
|
||||
},
|
||||
|
||||
graph: downloaded_graph,
|
||||
})
|
||||
|
|
|
@ -35,7 +35,10 @@ impl OutdatedCommand {
|
|||
continue;
|
||||
};
|
||||
|
||||
if matches!(specifier, DependencySpecifiers::Git(_)) {
|
||||
if matches!(
|
||||
specifier,
|
||||
DependencySpecifiers::Git(_) | DependencySpecifiers::Workspace(_)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -55,6 +58,7 @@ impl OutdatedCommand {
|
|||
spec.version = VersionReq::STAR;
|
||||
}
|
||||
DependencySpecifiers::Git(_) => {}
|
||||
DependencySpecifiers::Workspace(_) => {}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ impl PatchCommitCommand {
|
|||
let patch = create_patch(&self.directory).context("failed to create patch")?;
|
||||
std::fs::remove_dir_all(self.directory).context("failed to remove patch directory")?;
|
||||
|
||||
let patches_dir = project.path().join("patches");
|
||||
let patches_dir = project.package_dir().join("patches");
|
||||
std::fs::create_dir_all(&patches_dir).context("failed to create patches directory")?;
|
||||
|
||||
let patch_file_name = format!("{}-{}.patch", name.escaped(), version_id.escaped(),);
|
||||
|
|
|
@ -7,13 +7,17 @@ use anyhow::Context;
|
|||
use clap::Args;
|
||||
use colored::Colorize;
|
||||
use reqwest::StatusCode;
|
||||
use semver::VersionReq;
|
||||
use tempfile::tempfile;
|
||||
|
||||
use pesde::{
|
||||
manifest::target::Target,
|
||||
scripts::ScriptName,
|
||||
source::{
|
||||
pesde::PesdePackageSource, specifiers::DependencySpecifiers, traits::PackageSource,
|
||||
pesde::{specifier::PesdeDependencySpecifier, PesdePackageSource},
|
||||
specifiers::DependencySpecifiers,
|
||||
traits::PackageSource,
|
||||
workspace::{specifier::VersionType, WorkspacePackageSource},
|
||||
IGNORED_DIRS, IGNORED_FILES,
|
||||
},
|
||||
Project, DEFAULT_INDEX_NAME, MANIFEST_FILE_NAME,
|
||||
|
@ -52,9 +56,10 @@ impl PublishCommand {
|
|||
#[cfg(feature = "roblox")]
|
||||
let mut display_build_files: Vec<String> = vec![];
|
||||
|
||||
let (lib_path, bin_path) = (
|
||||
let (lib_path, bin_path, target_kind) = (
|
||||
manifest.target.lib_path().cloned(),
|
||||
manifest.target.bin_path().cloned(),
|
||||
manifest.target.kind(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "roblox")]
|
||||
|
@ -124,7 +129,7 @@ impl PublishCommand {
|
|||
for (name, path) in [("lib path", lib_path), ("bin path", bin_path)] {
|
||||
let Some(export_path) = path else { continue };
|
||||
|
||||
let export_path = export_path.to_path(project.path());
|
||||
let export_path = export_path.to_path(project.package_dir());
|
||||
if !export_path.exists() {
|
||||
anyhow::bail!("{name} points to non-existent file");
|
||||
}
|
||||
|
@ -146,7 +151,7 @@ impl PublishCommand {
|
|||
}
|
||||
|
||||
let first_part = export_path
|
||||
.strip_prefix(project.path())
|
||||
.strip_prefix(project.package_dir())
|
||||
.context(format!("{name} not within project directory"))?
|
||||
.components()
|
||||
.next()
|
||||
|
@ -177,7 +182,7 @@ impl PublishCommand {
|
|||
}
|
||||
|
||||
for included_name in &manifest.includes {
|
||||
let included_path = project.path().join(included_name);
|
||||
let included_path = project.package_dir().join(included_name);
|
||||
|
||||
if !included_path.exists() {
|
||||
anyhow::bail!("included file {included_name} does not exist");
|
||||
|
@ -216,7 +221,7 @@ impl PublishCommand {
|
|||
continue;
|
||||
}
|
||||
|
||||
let build_file_path = project.path().join(build_file);
|
||||
let build_file_path = project.package_dir().join(build_file);
|
||||
|
||||
if !build_file_path.exists() {
|
||||
anyhow::bail!("build file {build_file} does not exist");
|
||||
|
@ -281,6 +286,45 @@ impl PublishCommand {
|
|||
DependencySpecifiers::Git(_) => {
|
||||
has_git = true;
|
||||
}
|
||||
DependencySpecifiers::Workspace(spec) => {
|
||||
let pkg_ref = WorkspacePackageSource
|
||||
.resolve(spec, &project, target_kind)
|
||||
.context("failed to resolve workspace package")?
|
||||
.1
|
||||
.pop_last()
|
||||
.context("no versions found for workspace package")?
|
||||
.1;
|
||||
|
||||
let manifest = pkg_ref
|
||||
.path
|
||||
.to_path(
|
||||
project
|
||||
.workspace_dir()
|
||||
.context("failed to get workspace directory")?,
|
||||
)
|
||||
.join(MANIFEST_FILE_NAME);
|
||||
let manifest = std::fs::read_to_string(&manifest)
|
||||
.context("failed to read workspace package manifest")?;
|
||||
let manifest = toml::from_str::<pesde::manifest::Manifest>(&manifest)
|
||||
.context("failed to parse workspace package manifest")?;
|
||||
|
||||
*specifier = DependencySpecifiers::Pesde(PesdeDependencySpecifier {
|
||||
name: spec.name.clone(),
|
||||
version: match spec.version_type {
|
||||
VersionType::Wildcard => VersionReq::STAR,
|
||||
v => VersionReq::parse(&format!("{v}{}", manifest.version))
|
||||
.context(format!("failed to parse version for {v}"))?,
|
||||
},
|
||||
index: Some(
|
||||
manifest
|
||||
.indices
|
||||
.get(DEFAULT_INDEX_NAME)
|
||||
.context("missing default index in workspace package manifest")?
|
||||
.to_string(),
|
||||
),
|
||||
target: Some(manifest.target.kind()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,18 +4,18 @@ use anyhow::Context;
|
|||
use clap::Args;
|
||||
use relative_path::RelativePathBuf;
|
||||
|
||||
use crate::cli::IsUpToDate;
|
||||
use pesde::{
|
||||
names::{PackageName, PackageNames},
|
||||
source::traits::PackageRef,
|
||||
Project, PACKAGES_CONTAINER_NAME,
|
||||
};
|
||||
|
||||
use crate::cli::IsUpToDate;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct RunCommand {
|
||||
/// The package name, script name, or path to a script to run
|
||||
#[arg(index = 1)]
|
||||
package_or_script: String,
|
||||
package_or_script: Option<String>,
|
||||
|
||||
/// Arguments to pass to the script
|
||||
#[arg(index = 2, last = true)]
|
||||
|
@ -30,14 +30,25 @@ impl RunCommand {
|
|||
.arg(path)
|
||||
.arg("--")
|
||||
.args(&self.args)
|
||||
.current_dir(project.path())
|
||||
.current_dir(project.package_dir())
|
||||
.status()
|
||||
.expect("failed to run script");
|
||||
|
||||
std::process::exit(status.code().unwrap_or(1))
|
||||
};
|
||||
|
||||
if let Ok(pkg_name) = self.package_or_script.parse::<PackageName>() {
|
||||
let package_or_script = match self.package_or_script {
|
||||
Some(package_or_script) => package_or_script,
|
||||
None => {
|
||||
if let Some(script_path) = project.deser_manifest()?.target.bin_path() {
|
||||
run(script_path.to_path(project.package_dir()));
|
||||
}
|
||||
|
||||
anyhow::bail!("no package or script specified")
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(pkg_name) = package_or_script.parse::<PackageName>() {
|
||||
let graph = if project.is_up_to_date(true)? {
|
||||
project.deser_lockfile()?.graph
|
||||
} else {
|
||||
|
@ -55,12 +66,14 @@ impl RunCommand {
|
|||
anyhow::bail!("package has no bin path");
|
||||
};
|
||||
|
||||
let base_folder = node
|
||||
.node
|
||||
.base_folder(project.deser_manifest()?.target.kind(), true);
|
||||
let base_folder = project
|
||||
.deser_manifest()?
|
||||
.target
|
||||
.kind()
|
||||
.packages_folder(&node.node.pkg_ref.target_kind());
|
||||
let container_folder = node.node.container_folder(
|
||||
&project
|
||||
.path()
|
||||
.package_dir()
|
||||
.join(base_folder)
|
||||
.join(PACKAGES_CONTAINER_NAME),
|
||||
&pkg_name,
|
||||
|
@ -72,13 +85,13 @@ impl RunCommand {
|
|||
}
|
||||
|
||||
if let Ok(manifest) = project.deser_manifest() {
|
||||
if let Some(script_path) = manifest.scripts.get(&self.package_or_script) {
|
||||
run(script_path.to_path(project.path()))
|
||||
if let Some(script_path) = manifest.scripts.get(&package_or_script) {
|
||||
run(script_path.to_path(project.package_dir()))
|
||||
}
|
||||
};
|
||||
|
||||
let relative_path = RelativePathBuf::from(self.package_or_script);
|
||||
let path = relative_path.to_path(project.path());
|
||||
let relative_path = RelativePathBuf::from(package_or_script);
|
||||
let path = relative_path.to_path(project.package_dir());
|
||||
|
||||
if !path.exists() {
|
||||
anyhow::bail!("path does not exist: {}", path.display());
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::path::Path;
|
||||
|
||||
pub fn make_executable<P: AsRef<Path>>(_path: P) -> anyhow::Result<()> {
|
||||
// TODO: test if this actually works
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use anyhow::Context;
|
||||
|
|
|
@ -44,8 +44,13 @@ impl Project {
|
|||
|
||||
let container_folder = node.container_folder(
|
||||
&self
|
||||
.path()
|
||||
.join(node.base_folder(manifest.target.kind(), true))
|
||||
.package_dir()
|
||||
.join(
|
||||
manifest
|
||||
.target
|
||||
.kind()
|
||||
.packages_folder(&node.pkg_ref.target_kind()),
|
||||
)
|
||||
.join(PACKAGES_CONTAINER_NAME),
|
||||
name,
|
||||
version_id.version(),
|
||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -6,7 +6,7 @@
|
|||
#[cfg(not(any(feature = "roblox", feature = "lune", feature = "luau")))]
|
||||
compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled");
|
||||
|
||||
use crate::lockfile::Lockfile;
|
||||
use crate::{lockfile::Lockfile, manifest::Manifest};
|
||||
use gix::sec::identity::Account;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -108,7 +108,8 @@ impl AuthConfig {
|
|||
/// The main struct of the pesde library, representing a project
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Project {
|
||||
path: PathBuf,
|
||||
package_dir: PathBuf,
|
||||
workspace_dir: Option<PathBuf>,
|
||||
data_dir: PathBuf,
|
||||
auth_config: AuthConfig,
|
||||
cas_dir: PathBuf,
|
||||
|
@ -116,23 +117,30 @@ pub struct Project {
|
|||
|
||||
impl Project {
|
||||
/// Create a new `Project`
|
||||
pub fn new<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>>(
|
||||
path: P,
|
||||
data_dir: Q,
|
||||
cas_dir: R,
|
||||
pub fn new<P: AsRef<Path>, Q: AsRef<Path>, R: AsRef<Path>, S: AsRef<Path>>(
|
||||
package_dir: P,
|
||||
workspace_dir: Option<Q>,
|
||||
data_dir: R,
|
||||
cas_dir: S,
|
||||
auth_config: AuthConfig,
|
||||
) -> Self {
|
||||
Project {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
package_dir: package_dir.as_ref().to_path_buf(),
|
||||
workspace_dir: workspace_dir.map(|d| d.as_ref().to_path_buf()),
|
||||
data_dir: data_dir.as_ref().to_path_buf(),
|
||||
auth_config,
|
||||
cas_dir: cas_dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the path
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
/// Access the package directory
|
||||
pub fn package_dir(&self) -> &Path {
|
||||
&self.package_dir
|
||||
}
|
||||
|
||||
/// Access the workspace directory
|
||||
pub fn workspace_dir(&self) -> Option<&Path> {
|
||||
self.workspace_dir.as_deref()
|
||||
}
|
||||
|
||||
/// Access the data directory
|
||||
|
@ -152,37 +160,71 @@ impl Project {
|
|||
|
||||
/// Read the manifest file
|
||||
pub fn read_manifest(&self) -> Result<String, errors::ManifestReadError> {
|
||||
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
|
||||
let string = std::fs::read_to_string(self.package_dir.join(MANIFEST_FILE_NAME))?;
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
/// Deserialize the manifest file
|
||||
pub fn deser_manifest(&self) -> Result<manifest::Manifest, errors::ManifestReadError> {
|
||||
let string = std::fs::read_to_string(self.path.join(MANIFEST_FILE_NAME))?;
|
||||
pub fn deser_manifest(&self) -> Result<Manifest, errors::ManifestReadError> {
|
||||
let string = std::fs::read_to_string(self.package_dir.join(MANIFEST_FILE_NAME))?;
|
||||
Ok(toml::from_str(&string)?)
|
||||
}
|
||||
|
||||
/// Write the manifest file
|
||||
pub fn write_manifest<S: AsRef<[u8]>>(&self, manifest: S) -> Result<(), std::io::Error> {
|
||||
std::fs::write(self.path.join(MANIFEST_FILE_NAME), manifest.as_ref())
|
||||
std::fs::write(self.package_dir.join(MANIFEST_FILE_NAME), manifest.as_ref())
|
||||
}
|
||||
|
||||
/// Deserialize the lockfile
|
||||
pub fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
|
||||
let string = std::fs::read_to_string(self.path.join(LOCKFILE_FILE_NAME))?;
|
||||
let string = std::fs::read_to_string(self.package_dir.join(LOCKFILE_FILE_NAME))?;
|
||||
Ok(toml::from_str(&string)?)
|
||||
}
|
||||
|
||||
/// Write the lockfile
|
||||
pub fn write_lockfile(&self, lockfile: Lockfile) -> Result<(), errors::LockfileWriteError> {
|
||||
let string = toml::to_string(&lockfile)?;
|
||||
std::fs::write(self.path.join(LOCKFILE_FILE_NAME), string)?;
|
||||
std::fs::write(self.package_dir.join(LOCKFILE_FILE_NAME), string)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the workspace members
|
||||
pub fn workspace_members<P: AsRef<Path>>(
|
||||
&self,
|
||||
dir: P,
|
||||
) -> Result<HashMap<PathBuf, Manifest>, errors::WorkspaceMembersError> {
|
||||
let dir = dir.as_ref().to_path_buf();
|
||||
let manifest = std::fs::read_to_string(dir.join(MANIFEST_FILE_NAME))
|
||||
.map_err(|e| errors::WorkspaceMembersError::ManifestMissing(dir.to_path_buf(), e))?;
|
||||
let manifest = toml::from_str::<Manifest>(&manifest)
|
||||
.map_err(|e| errors::WorkspaceMembersError::ManifestDeser(dir.to_path_buf(), e))?;
|
||||
|
||||
let members = manifest
|
||||
.workspace_members
|
||||
.into_iter()
|
||||
.map(|glob| dir.join(glob))
|
||||
.map(|path| glob::glob(&path.as_os_str().to_string_lossy()))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flat_map(|paths| paths.into_iter())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
members
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let manifest = std::fs::read_to_string(path.join(MANIFEST_FILE_NAME))
|
||||
.map_err(|e| errors::WorkspaceMembersError::ManifestMissing(path.clone(), e))?;
|
||||
let manifest = toml::from_str::<Manifest>(&manifest)
|
||||
.map_err(|e| errors::WorkspaceMembersError::ManifestDeser(path.clone(), e))?;
|
||||
Ok((path, manifest))
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when using the pesde library
|
||||
pub mod errors {
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when reading the manifest file
|
||||
|
@ -223,4 +265,29 @@ pub mod errors {
|
|||
#[error("error serializing lockfile")]
|
||||
Serde(#[from] toml::ser::Error),
|
||||
}
|
||||
|
||||
/// Errors that can occur when finding workspace members
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum WorkspaceMembersError {
|
||||
/// The manifest file could not be found
|
||||
#[error("missing manifest file at {0}")]
|
||||
ManifestMissing(PathBuf, #[source] std::io::Error),
|
||||
|
||||
/// An error occurred deserializing the manifest file
|
||||
#[error("error deserializing manifest file at {0}")]
|
||||
ManifestDeser(PathBuf, #[source] toml::de::Error),
|
||||
|
||||
/// An error occurred interacting with the filesystem
|
||||
#[error("error interacting with the filesystem")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// An invalid glob pattern was found
|
||||
#[error("invalid glob pattern")]
|
||||
Glob(#[from] glob::PatternError),
|
||||
|
||||
/// An error occurred while globbing
|
||||
#[error("error globbing")]
|
||||
Globbing(#[from] glob::GlobError),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,10 +92,10 @@ pub fn get_lib_require_path(
|
|||
) -> String {
|
||||
let path = pathdiff::diff_paths(destination_dir, base_dir).unwrap();
|
||||
let path = if use_new_structure {
|
||||
log::debug!("using new structure for require path");
|
||||
log::debug!("using new structure for require path with {:?}", lib_file);
|
||||
lib_file.to_path(path)
|
||||
} else {
|
||||
log::debug!("using old structure for require path");
|
||||
log::debug!("using old structure for require path with {:?}", lib_file);
|
||||
path
|
||||
};
|
||||
|
||||
|
|
|
@ -44,8 +44,13 @@ impl Project {
|
|||
|
||||
let container_folder = node.node.container_folder(
|
||||
&self
|
||||
.path()
|
||||
.join(node.node.base_folder(manifest.target.kind(), true))
|
||||
.package_dir()
|
||||
.join(
|
||||
manifest
|
||||
.target
|
||||
.kind()
|
||||
.packages_folder(&node.node.pkg_ref.target_kind()),
|
||||
)
|
||||
.join(PACKAGES_CONTAINER_NAME),
|
||||
name,
|
||||
version_id.version(),
|
||||
|
@ -99,7 +104,7 @@ impl Project {
|
|||
|
||||
execute_script(
|
||||
ScriptName::RobloxSyncConfigGenerator,
|
||||
&script_path.to_path(self.path()),
|
||||
&script_path.to_path(self.package_dir()),
|
||||
std::iter::once(container_folder.as_os_str())
|
||||
.chain(build_files.iter().map(OsStr::new)),
|
||||
self,
|
||||
|
@ -117,56 +122,64 @@ impl Project {
|
|||
|
||||
for (name, versions) in graph {
|
||||
for (version_id, node) in versions {
|
||||
let base_folder = create_and_canonicalize(
|
||||
self.path().join(
|
||||
self.path()
|
||||
.join(node.node.base_folder(manifest.target.kind(), true)),
|
||||
),
|
||||
)?;
|
||||
let packages_container_folder = base_folder.join(PACKAGES_CONTAINER_NAME);
|
||||
let node_container_folder = {
|
||||
let base_folder = create_and_canonicalize(
|
||||
self.package_dir().join(
|
||||
manifest
|
||||
.target
|
||||
.kind()
|
||||
.packages_folder(&node.node.pkg_ref.target_kind()),
|
||||
),
|
||||
)?;
|
||||
let packages_container_folder = base_folder.join(PACKAGES_CONTAINER_NAME);
|
||||
|
||||
let container_folder = node.node.container_folder(
|
||||
&packages_container_folder,
|
||||
name,
|
||||
version_id.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_id))
|
||||
.and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types)))
|
||||
{
|
||||
if let Some(lib_file) = node.target.lib_path() {
|
||||
write_cas(
|
||||
base_folder.join(format!("{alias}.luau")),
|
||||
self.cas_dir(),
|
||||
&generator::generate_lib_linking_module(
|
||||
&generator::get_lib_require_path(
|
||||
&node.target.kind(),
|
||||
&base_folder,
|
||||
lib_file,
|
||||
&container_folder,
|
||||
node.node.pkg_ref.use_new_structure(),
|
||||
if let Some((alias, types)) = package_types
|
||||
.get(name)
|
||||
.and_then(|v| v.get(version_id))
|
||||
.and_then(|types| {
|
||||
node.node.direct.as_ref().map(|(alias, _)| (alias, types))
|
||||
})
|
||||
{
|
||||
if let Some(lib_file) = node.target.lib_path() {
|
||||
write_cas(
|
||||
base_folder.join(format!("{alias}.luau")),
|
||||
self.cas_dir(),
|
||||
&generator::generate_lib_linking_module(
|
||||
&generator::get_lib_require_path(
|
||||
&node.target.kind(),
|
||||
&base_folder,
|
||||
lib_file,
|
||||
&container_folder,
|
||||
node.node.pkg_ref.use_new_structure(),
|
||||
),
|
||||
types,
|
||||
),
|
||||
types,
|
||||
),
|
||||
)?;
|
||||
};
|
||||
)?;
|
||||
};
|
||||
|
||||
if let Some(bin_file) = node.target.bin_path() {
|
||||
write_cas(
|
||||
base_folder.join(format!("{alias}.bin.luau")),
|
||||
self.cas_dir(),
|
||||
&generator::generate_bin_linking_module(
|
||||
&generator::get_bin_require_path(
|
||||
&base_folder,
|
||||
bin_file,
|
||||
&container_folder,
|
||||
if let Some(bin_file) = node.target.bin_path() {
|
||||
write_cas(
|
||||
base_folder.join(format!("{alias}.bin.luau")),
|
||||
self.cas_dir(),
|
||||
&generator::generate_bin_linking_module(
|
||||
&generator::get_bin_require_path(
|
||||
&base_folder,
|
||||
bin_file,
|
||||
&container_folder,
|
||||
),
|
||||
),
|
||||
),
|
||||
)?;
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container_folder
|
||||
};
|
||||
|
||||
for (dependency_name, (dependency_version_id, dependency_alias)) in
|
||||
&node.node.dependencies
|
||||
|
@ -185,9 +198,19 @@ impl Project {
|
|||
continue;
|
||||
};
|
||||
|
||||
let packages_container_folder = create_and_canonicalize(
|
||||
self.package_dir().join(
|
||||
node.node
|
||||
.pkg_ref
|
||||
.target_kind()
|
||||
.packages_folder(&dependency_node.node.pkg_ref.target_kind()),
|
||||
),
|
||||
)?
|
||||
.join(PACKAGES_CONTAINER_NAME);
|
||||
|
||||
let linker_folder = create_and_canonicalize(
|
||||
container_folder
|
||||
.join(dependency_node.node.base_folder(node.target.kind(), false)),
|
||||
node_container_folder
|
||||
.join(node.node.base_folder(dependency_node.target.kind())),
|
||||
)?;
|
||||
|
||||
write_cas(
|
||||
|
@ -203,7 +226,7 @@ impl Project {
|
|||
dependency_name,
|
||||
dependency_version_id.version(),
|
||||
),
|
||||
node.node.pkg_ref.use_new_structure(),
|
||||
dependency_node.node.pkg_ref.use_new_structure(),
|
||||
),
|
||||
package_types
|
||||
.get(dependency_name)
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
version_id::VersionId,
|
||||
},
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -36,10 +37,9 @@ pub struct DependencyGraphNode {
|
|||
}
|
||||
|
||||
impl DependencyGraphNode {
|
||||
/// Returns the folder to store dependencies in for this package
|
||||
pub fn base_folder(&self, project_target: TargetKind, is_top_level: bool) -> String {
|
||||
if is_top_level || self.pkg_ref.use_new_structure() {
|
||||
project_target.packages_folder(&self.pkg_ref.target_kind())
|
||||
pub(crate) fn base_folder(&self, project_target: TargetKind) -> String {
|
||||
if self.pkg_ref.use_new_structure() {
|
||||
self.pkg_ref.target_kind().packages_folder(&project_target)
|
||||
} else {
|
||||
"..".to_string()
|
||||
}
|
||||
|
@ -62,8 +62,7 @@ impl DependencyGraphNode {
|
|||
/// A graph of `DependencyGraphNode`s
|
||||
pub type DependencyGraph = Graph<DependencyGraphNode>;
|
||||
|
||||
/// Inserts a node into a graph
|
||||
pub fn insert_node(
|
||||
pub(crate) fn insert_node(
|
||||
graph: &mut DependencyGraph,
|
||||
name: PackageNames,
|
||||
version: VersionId,
|
||||
|
@ -128,6 +127,10 @@ pub struct Lockfile {
|
|||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
|
||||
|
||||
/// The workspace members
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub workspace: BTreeMap<PackageName, RelativePathBuf>,
|
||||
|
||||
/// The graph of dependencies
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub graph: DownloadedGraph,
|
||||
|
|
96
src/main.rs
96
src/main.rs
|
@ -1,12 +1,14 @@
|
|||
use std::{fs::create_dir_all, path::PathBuf};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use indicatif::MultiProgress;
|
||||
use indicatif_log_bridge::LogWrapper;
|
||||
|
||||
use pesde::{AuthConfig, Project, MANIFEST_FILE_NAME};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::cli::{
|
||||
auth::get_token,
|
||||
|
@ -67,23 +69,6 @@ fn get_root(path: &std::path::Path) -> PathBuf {
|
|||
|
||||
fn run() -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir().expect("failed to get current working directory");
|
||||
let project_root_dir = 'finder: {
|
||||
let mut project_root = cwd.clone();
|
||||
|
||||
while project_root.components().count() > 1 {
|
||||
if project_root.join(MANIFEST_FILE_NAME).exists() {
|
||||
break 'finder project_root;
|
||||
}
|
||||
|
||||
if let Some(parent) = project_root.parent() {
|
||||
project_root = parent.to_path_buf();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cwd.clone()
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
'scripts: {
|
||||
|
@ -105,17 +90,85 @@ fn run() -> anyhow::Result<()> {
|
|||
break 'scripts;
|
||||
}
|
||||
|
||||
// the bin script will search for the project root itself, so we do that to ensure
|
||||
// consistency across platforms, since the script is executed using a shebang
|
||||
// on unix systems
|
||||
let status = std::process::Command::new("lune")
|
||||
.arg("run")
|
||||
.arg(exe.with_extension(""))
|
||||
.arg("--")
|
||||
.args(std::env::args_os().skip(1))
|
||||
.current_dir(project_root_dir)
|
||||
.current_dir(cwd)
|
||||
.status()
|
||||
.expect("failed to run lune");
|
||||
|
||||
std::process::exit(status.code().unwrap());
|
||||
}
|
||||
|
||||
let (project_root_dir, project_workspace_dir) = 'finder: {
|
||||
let mut current_path = Some(cwd.clone());
|
||||
let mut project_root = None::<PathBuf>;
|
||||
let mut workspace_dir = None::<PathBuf>;
|
||||
|
||||
fn get_workspace_members(path: &Path) -> anyhow::Result<HashSet<PathBuf>> {
|
||||
let manifest = std::fs::read_to_string(path.join(MANIFEST_FILE_NAME))
|
||||
.context("failed to read manifest")?;
|
||||
let manifest: pesde::manifest::Manifest =
|
||||
toml::from_str(&manifest).context("failed to parse manifest")?;
|
||||
|
||||
if manifest.workspace_members.is_empty() {
|
||||
return Ok(HashSet::new());
|
||||
}
|
||||
|
||||
manifest
|
||||
.workspace_members
|
||||
.iter()
|
||||
.map(|member| path.join(member))
|
||||
.map(|p| glob::glob(&p.to_string_lossy()))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.context("invalid glob patterns")?
|
||||
.into_iter()
|
||||
.flat_map(|paths| paths.into_iter())
|
||||
.collect::<Result<HashSet<_>, _>>()
|
||||
.context("failed to expand glob patterns")
|
||||
}
|
||||
|
||||
while let Some(path) = current_path {
|
||||
current_path = path.parent().map(|p| p.to_path_buf());
|
||||
|
||||
if !path.join(MANIFEST_FILE_NAME).exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match (project_root.as_ref(), workspace_dir.as_ref()) {
|
||||
(Some(project_root), Some(workspace_dir)) => {
|
||||
break 'finder (project_root.clone(), Some(workspace_dir.clone()));
|
||||
}
|
||||
|
||||
(Some(project_root), None) => {
|
||||
if get_workspace_members(&path)?.contains(project_root) {
|
||||
workspace_dir = Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
(None, None) => {
|
||||
if get_workspace_members(&path)?.contains(&cwd) {
|
||||
// initializing a new member of a workspace
|
||||
break 'finder (cwd, Some(path));
|
||||
} else {
|
||||
project_root = Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
(None, Some(_)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// we mustn't expect the project root to be found, as that would
|
||||
// disable the ability to run pesde in a non-project directory (for example to init it)
|
||||
(project_root.unwrap_or_else(|| cwd.clone()), workspace_dir)
|
||||
};
|
||||
|
||||
let multi = {
|
||||
let logger = pretty_env_logger::formatted_builder()
|
||||
.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info"))
|
||||
|
@ -143,6 +196,7 @@ fn run() -> anyhow::Result<()> {
|
|||
|
||||
let project = Project::new(
|
||||
project_root_dir,
|
||||
project_workspace_dir,
|
||||
data_dir,
|
||||
cas_dir,
|
||||
AuthConfig::new()
|
||||
|
|
|
@ -74,6 +74,9 @@ pub struct Manifest {
|
|||
#[serde(default, skip_serializing)]
|
||||
/// Which version of the pesde CLI this package uses
|
||||
pub pesde_version: Option<Version>,
|
||||
/// A list of globs pointing to workspace members' directories
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workspace_members: Vec<String>,
|
||||
|
||||
/// The standard dependencies of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::{lockfile::DownloadedGraph, Project, MANIFEST_FILE_NAME, PACKAGES_CONTAINER_NAME};
|
||||
use crate::{
|
||||
lockfile::DownloadedGraph, source::traits::PackageRef, Project, MANIFEST_FILE_NAME,
|
||||
PACKAGES_CONTAINER_NAME,
|
||||
};
|
||||
use git2::{ApplyLocation, ApplyOptions, Diff, DiffFormat, DiffLineType, Repository, Signature};
|
||||
use relative_path::RelativePathBuf;
|
||||
use std::{fs::read, path::Path};
|
||||
|
@ -73,7 +76,7 @@ impl Project {
|
|||
|
||||
for (name, versions) in manifest.patches {
|
||||
for (version_id, patch_path) in versions {
|
||||
let patch_path = patch_path.to_path(self.path());
|
||||
let patch_path = patch_path.to_path(self.package_dir());
|
||||
let patch = Diff::from_buffer(&read(&patch_path).map_err(|e| {
|
||||
errors::ApplyPatchesError::PatchReadError(patch_path.clone(), e)
|
||||
})?)?;
|
||||
|
@ -87,8 +90,13 @@ impl Project {
|
|||
|
||||
let container_folder = node.node.container_folder(
|
||||
&self
|
||||
.path()
|
||||
.join(node.node.base_folder(manifest.target.kind(), true))
|
||||
.package_dir()
|
||||
.join(
|
||||
manifest
|
||||
.target
|
||||
.kind()
|
||||
.packages_folder(&node.node.pkg_ref.target_kind()),
|
||||
)
|
||||
.join(PACKAGES_CONTAINER_NAME),
|
||||
&name,
|
||||
version_id.version(),
|
||||
|
|
|
@ -40,6 +40,11 @@ impl Project {
|
|||
continue;
|
||||
};
|
||||
|
||||
if matches!(specifier, DependencySpecifiers::Workspace(_)) {
|
||||
// workspace dependencies must always be resolved brand new
|
||||
continue;
|
||||
}
|
||||
|
||||
if all_specifiers
|
||||
.remove(&(specifier.clone(), node.ty))
|
||||
.is_none()
|
||||
|
@ -180,6 +185,9 @@ impl Project {
|
|||
DependencySpecifiers::Git(specifier) => PackageSources::Git(
|
||||
crate::source::git::GitPackageSource::new(specifier.repo.clone()),
|
||||
),
|
||||
DependencySpecifiers::Workspace(_) => {
|
||||
PackageSources::Workspace(crate::source::workspace::WorkspacePackageSource)
|
||||
}
|
||||
};
|
||||
|
||||
if refreshed_sources.insert(source.clone()) {
|
||||
|
|
|
@ -45,7 +45,7 @@ pub(crate) fn execute_script<A: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
.arg(script_path.as_os_str())
|
||||
.arg("--")
|
||||
.args(args)
|
||||
.current_dir(project.path())
|
||||
.current_dir(project.package_dir())
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
|
|
@ -4,12 +4,15 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
manifest::target::TargetKind,
|
||||
source::{IGNORED_DIRS, IGNORED_FILES},
|
||||
util::hash,
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::util::hash;
|
||||
|
||||
/// A file system entry
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FSEntry {
|
||||
|
@ -23,8 +26,14 @@ pub enum FSEntry {
|
|||
|
||||
/// A package's file system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PackageFS(pub(crate) BTreeMap<RelativePathBuf, FSEntry>);
|
||||
// don't need to differentiate between CAS and non-CAS, since non-CAS won't be serialized
|
||||
#[serde(untagged)]
|
||||
pub enum PackageFS {
|
||||
/// A package stored in the CAS
|
||||
CAS(BTreeMap<RelativePathBuf, FSEntry>),
|
||||
/// A package that's to be copied
|
||||
Copy(PathBuf, TargetKind),
|
||||
}
|
||||
|
||||
pub(crate) fn store_in_cas<P: AsRef<Path>>(
|
||||
cas_dir: P,
|
||||
|
@ -92,6 +101,40 @@ pub(crate) fn store_reader_in_cas<P: AsRef<Path>>(
|
|||
Ok(hash)
|
||||
}
|
||||
|
||||
fn copy_dir_all(
|
||||
src: impl AsRef<Path>,
|
||||
dst: impl AsRef<Path>,
|
||||
target: TargetKind,
|
||||
) -> std::io::Result<()> {
|
||||
std::fs::create_dir_all(&dst)?;
|
||||
'outer: for entry in std::fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
if ty.is_dir() {
|
||||
if IGNORED_DIRS.contains(&file_name.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for other_target in TargetKind::VARIANTS {
|
||||
if target.packages_folder(other_target) == file_name {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
copy_dir_all(entry.path(), dst.as_ref().join(&file_name), target)?;
|
||||
} else {
|
||||
if IGNORED_FILES.contains(&file_name.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::fs::copy(entry.path(), dst.as_ref().join(file_name))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl PackageFS {
|
||||
/// Write the package to the given destination
|
||||
pub fn write_to<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
|
@ -100,27 +143,34 @@ impl PackageFS {
|
|||
cas_path: Q,
|
||||
link: bool,
|
||||
) -> std::io::Result<()> {
|
||||
for (path, entry) in &self.0 {
|
||||
let path = path.to_path(destination.as_ref());
|
||||
match self {
|
||||
PackageFS::CAS(entries) => {
|
||||
for (path, entry) in entries {
|
||||
let path = path.to_path(destination.as_ref());
|
||||
|
||||
match entry {
|
||||
FSEntry::File(hash) => {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
match entry {
|
||||
FSEntry::File(hash) => {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let (prefix, rest) = hash.split_at(2);
|
||||
let cas_file_path = cas_path.as_ref().join(prefix).join(rest);
|
||||
let (prefix, rest) = hash.split_at(2);
|
||||
let cas_file_path = cas_path.as_ref().join(prefix).join(rest);
|
||||
|
||||
if link {
|
||||
std::fs::hard_link(cas_file_path, path)?;
|
||||
} else {
|
||||
std::fs::copy(cas_file_path, path)?;
|
||||
if link {
|
||||
std::fs::hard_link(cas_file_path, path)?;
|
||||
} else {
|
||||
std::fs::copy(cas_file_path, path)?;
|
||||
}
|
||||
}
|
||||
FSEntry::Directory => {
|
||||
std::fs::create_dir_all(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
FSEntry::Directory => {
|
||||
std::fs::create_dir_all(path)?;
|
||||
}
|
||||
}
|
||||
PackageFS::Copy(src, target) => {
|
||||
copy_dir_all(src, destination, *target)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +183,10 @@ impl PackageFS {
|
|||
file_hash: H,
|
||||
cas_path: P,
|
||||
) -> Option<String> {
|
||||
if !matches!(self, PackageFS::CAS(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (prefix, rest) = file_hash.as_ref().split_at(2);
|
||||
let cas_file_path = cas_path.as_ref().join(prefix).join(rest);
|
||||
std::fs::read_to_string(cas_file_path).ok()
|
||||
|
|
|
@ -162,6 +162,7 @@ impl PackageSource for GitPackageSource {
|
|||
);
|
||||
}
|
||||
DependencySpecifiers::Git(_) => {}
|
||||
DependencySpecifiers::Workspace(_) => todo!(),
|
||||
}
|
||||
|
||||
Ok((alias, (spec, ty)))
|
||||
|
@ -262,21 +263,26 @@ impl PackageSource for GitPackageSource {
|
|||
errors::DownloadError::DeserializeFile(Box::new(self.repo_url.clone()), e)
|
||||
})?;
|
||||
|
||||
let manifest = match fs.0.get(&RelativePathBuf::from(MANIFEST_FILE_NAME)) {
|
||||
Some(FSEntry::File(hash)) => match fs
|
||||
.read_file(hash, project.cas_dir())
|
||||
.map(|m| toml::de::from_str::<Manifest>(&m))
|
||||
{
|
||||
Some(Ok(m)) => Some(m),
|
||||
Some(Err(e)) => {
|
||||
return Err(errors::DownloadError::DeserializeFile(
|
||||
Box::new(self.repo_url.clone()),
|
||||
e,
|
||||
))
|
||||
let manifest = match &fs {
|
||||
PackageFS::CAS(entries) => {
|
||||
match entries.get(&RelativePathBuf::from(MANIFEST_FILE_NAME)) {
|
||||
Some(FSEntry::File(hash)) => match fs
|
||||
.read_file(hash, project.cas_dir())
|
||||
.map(|m| toml::de::from_str::<Manifest>(&m))
|
||||
{
|
||||
Some(Ok(m)) => Some(m),
|
||||
Some(Err(e)) => {
|
||||
return Err(errors::DownloadError::DeserializeFile(
|
||||
Box::new(self.repo_url.clone()),
|
||||
e,
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
_ => unreachable!("the package fs should be CAS"),
|
||||
};
|
||||
|
||||
let target = match manifest {
|
||||
|
@ -380,7 +386,7 @@ impl PackageSource for GitPackageSource {
|
|||
None => None,
|
||||
};
|
||||
|
||||
let fs = PackageFS(entries);
|
||||
let fs = PackageFS::CAS(entries);
|
||||
|
||||
let target = match manifest {
|
||||
Some(manifest) => manifest.target,
|
||||
|
|
|
@ -29,6 +29,8 @@ pub mod version_id;
|
|||
/// The Wally package source
|
||||
#[cfg(feature = "wally-compat")]
|
||||
pub mod wally;
|
||||
/// The workspace package source
|
||||
pub mod workspace;
|
||||
|
||||
/// Files that will not be stored when downloading a package. These are only files which break pesde's functionality, or are meaningless and possibly heavy (e.g. `.DS_Store`)
|
||||
pub const IGNORED_FILES: &[&str] = &["foreman.toml", "aftman.toml", "rokit.toml", ".DS_Store"];
|
||||
|
@ -49,6 +51,8 @@ pub enum PackageSources {
|
|||
Wally(wally::WallyPackageSource),
|
||||
/// A Git package source
|
||||
Git(git::GitPackageSource),
|
||||
/// A workspace package source
|
||||
Workspace(workspace::WorkspacePackageSource),
|
||||
}
|
||||
|
||||
impl PackageSource for PackageSources {
|
||||
|
@ -64,6 +68,7 @@ impl PackageSource for PackageSources {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
PackageSources::Wally(source) => source.refresh(project).map_err(Into::into),
|
||||
PackageSources::Git(source) => source.refresh(project).map_err(Into::into),
|
||||
PackageSources::Workspace(source) => source.refresh(project).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,11 +76,11 @@ impl PackageSource for PackageSources {
|
|||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
project_target: TargetKind,
|
||||
package_target: TargetKind,
|
||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
|
||||
match (self, specifier) {
|
||||
(PackageSources::Pesde(source), DependencySpecifiers::Pesde(specifier)) => source
|
||||
.resolve(specifier, project, project_target)
|
||||
.resolve(specifier, project, package_target)
|
||||
.map(|(name, results)| {
|
||||
(
|
||||
name,
|
||||
|
@ -89,7 +94,7 @@ impl PackageSource for PackageSources {
|
|||
|
||||
#[cfg(feature = "wally-compat")]
|
||||
(PackageSources::Wally(source), DependencySpecifiers::Wally(specifier)) => source
|
||||
.resolve(specifier, project, project_target)
|
||||
.resolve(specifier, project, package_target)
|
||||
.map(|(name, results)| {
|
||||
(
|
||||
name,
|
||||
|
@ -102,7 +107,7 @@ impl PackageSource for PackageSources {
|
|||
.map_err(Into::into),
|
||||
|
||||
(PackageSources::Git(source), DependencySpecifiers::Git(specifier)) => source
|
||||
.resolve(specifier, project, project_target)
|
||||
.resolve(specifier, project, package_target)
|
||||
.map(|(name, results)| {
|
||||
(
|
||||
name,
|
||||
|
@ -114,6 +119,23 @@ impl PackageSource for PackageSources {
|
|||
})
|
||||
.map_err(Into::into),
|
||||
|
||||
(PackageSources::Workspace(source), DependencySpecifiers::Workspace(specifier)) => {
|
||||
source
|
||||
.resolve(specifier, project, package_target)
|
||||
.map(|(name, results)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(version, pkg_ref)| {
|
||||
(version, PackageRefs::Workspace(pkg_ref))
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
_ => Err(errors::ResolveError::Mismatch),
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +160,10 @@ impl PackageSource for PackageSources {
|
|||
.download(pkg_ref, project, reqwest)
|
||||
.map_err(Into::into),
|
||||
|
||||
(PackageSources::Workspace(source), PackageRefs::Workspace(pkg_ref)) => source
|
||||
.download(pkg_ref, project, reqwest)
|
||||
.map_err(Into::into),
|
||||
|
||||
_ => Err(errors::DownloadError::Mismatch),
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +180,10 @@ pub mod errors {
|
|||
/// A git-based package source failed to refresh
|
||||
#[error("error refreshing pesde package source")]
|
||||
GitBased(#[from] crate::source::git_index::errors::RefreshError),
|
||||
|
||||
/// A workspace package source failed to refresh
|
||||
#[error("error refreshing workspace package source")]
|
||||
Workspace(#[from] crate::source::workspace::errors::RefreshError),
|
||||
}
|
||||
|
||||
/// Errors that can occur when resolving a package
|
||||
|
@ -176,6 +206,10 @@ pub mod errors {
|
|||
/// A Git package source failed to resolve
|
||||
#[error("error resolving git package")]
|
||||
Git(#[from] crate::source::git::errors::ResolveError),
|
||||
|
||||
/// A workspace package source failed to resolve
|
||||
#[error("error resolving workspace package")]
|
||||
Workspace(#[from] crate::source::workspace::errors::ResolveError),
|
||||
}
|
||||
|
||||
/// Errors that can occur when downloading a package
|
||||
|
@ -198,5 +232,9 @@ pub mod errors {
|
|||
/// A Git package source failed to download
|
||||
#[error("error downloading git package")]
|
||||
Git(#[from] crate::source::git::errors::DownloadError),
|
||||
|
||||
/// A workspace package source failed to download
|
||||
#[error("error downloading workspace package")]
|
||||
Workspace(#[from] crate::source::workspace::errors::DownloadError),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ impl PackageSource for PesdePackageSource {
|
|||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
project_target: TargetKind,
|
||||
package_target: TargetKind,
|
||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
|
||||
let (scope, name) = specifier.name.as_str();
|
||||
let string = match self.read_file([scope, name], project, None) {
|
||||
|
@ -221,7 +221,7 @@ impl PackageSource for PesdePackageSource {
|
|||
specifier.version.matches(version)
|
||||
&& specifier
|
||||
.target
|
||||
.map_or(project_target.is_compatible_with(target), |t| t == *target)
|
||||
.map_or(package_target.is_compatible_with(target), |t| t == *target)
|
||||
})
|
||||
.map(|(id, entry)| {
|
||||
let version = id.version().clone();
|
||||
|
@ -331,7 +331,7 @@ impl PackageSource for PesdePackageSource {
|
|||
entries.insert(path, FSEntry::File(hash));
|
||||
}
|
||||
|
||||
let fs = PackageFS(entries);
|
||||
let fs = PackageFS::CAS(entries);
|
||||
|
||||
if let Some(parent) = index_file.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
|
|
|
@ -16,6 +16,8 @@ pub enum PackageRefs {
|
|||
Wally(crate::source::wally::pkg_ref::WallyPackageRef),
|
||||
/// A Git package reference
|
||||
Git(crate::source::git::pkg_ref::GitPackageRef),
|
||||
/// A workspace package reference
|
||||
Workspace(crate::source::workspace::pkg_ref::WorkspacePackageRef),
|
||||
}
|
||||
|
||||
impl PackageRefs {
|
||||
|
@ -37,6 +39,7 @@ impl PackageRef for PackageRefs {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
PackageRefs::Wally(pkg_ref) => pkg_ref.dependencies(),
|
||||
PackageRefs::Git(pkg_ref) => pkg_ref.dependencies(),
|
||||
PackageRefs::Workspace(pkg_ref) => pkg_ref.dependencies(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +49,7 @@ impl PackageRef for PackageRefs {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
PackageRefs::Wally(pkg_ref) => pkg_ref.use_new_structure(),
|
||||
PackageRefs::Git(pkg_ref) => pkg_ref.use_new_structure(),
|
||||
PackageRefs::Workspace(pkg_ref) => pkg_ref.use_new_structure(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +59,7 @@ impl PackageRef for PackageRefs {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
PackageRefs::Wally(pkg_ref) => pkg_ref.target_kind(),
|
||||
PackageRefs::Git(pkg_ref) => pkg_ref.target_kind(),
|
||||
PackageRefs::Workspace(pkg_ref) => pkg_ref.target_kind(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +69,7 @@ impl PackageRef for PackageRefs {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
PackageRefs::Wally(pkg_ref) => pkg_ref.source(),
|
||||
PackageRefs::Git(pkg_ref) => pkg_ref.source(),
|
||||
PackageRefs::Workspace(pkg_ref) => pkg_ref.source(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ pub enum DependencySpecifiers {
|
|||
Wally(crate::source::wally::specifier::WallyDependencySpecifier),
|
||||
/// A Git dependency specifier
|
||||
Git(crate::source::git::specifier::GitDependencySpecifier),
|
||||
/// A workspace dependency specifier
|
||||
Workspace(crate::source::workspace::specifier::WorkspaceDependencySpecifier),
|
||||
}
|
||||
impl DependencySpecifier for DependencySpecifiers {}
|
||||
|
||||
|
@ -23,6 +25,7 @@ impl Display for DependencySpecifiers {
|
|||
#[cfg(feature = "wally-compat")]
|
||||
DependencySpecifiers::Wally(specifier) => write!(f, "{specifier}"),
|
||||
DependencySpecifiers::Git(specifier) => write!(f, "{specifier}"),
|
||||
DependencySpecifiers::Workspace(specifier) => write!(f, "{specifier}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ pub(crate) fn find_lib_path(
|
|||
|
||||
let result = execute_script(
|
||||
ScriptName::SourcemapGenerator,
|
||||
&script_path.to_path(&project.path),
|
||||
&script_path.to_path(&project.package_dir),
|
||||
[package_dir],
|
||||
project,
|
||||
true,
|
||||
|
|
|
@ -94,7 +94,7 @@ impl PackageSource for WallyPackageSource {
|
|||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
_project_target: TargetKind,
|
||||
_package_target: TargetKind,
|
||||
) -> Result<crate::source::ResolveResult<Self::Ref>, Self::ResolveError> {
|
||||
let (scope, name) = specifier.name.as_str();
|
||||
let string = match self.read_file([scope, name], project, None) {
|
||||
|
@ -238,7 +238,7 @@ impl PackageSource for WallyPackageSource {
|
|||
entries.insert(path, FSEntry::File(hash));
|
||||
}
|
||||
|
||||
let fs = PackageFS(entries);
|
||||
let fs = PackageFS::CAS(entries);
|
||||
|
||||
if let Some(parent) = index_file.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(errors::DownloadError::WriteIndex)?;
|
||||
|
|
173
src/source/workspace/mod.rs
Normal file
173
src/source/workspace/mod.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use crate::{
|
||||
manifest::target::{Target, TargetKind},
|
||||
names::PackageNames,
|
||||
source::{
|
||||
fs::PackageFS, specifiers::DependencySpecifiers, traits::PackageSource,
|
||||
version_id::VersionId, workspace::pkg_ref::WorkspacePackageRef, ResolveResult,
|
||||
},
|
||||
Project, DEFAULT_INDEX_NAME,
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use reqwest::blocking::Client;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// The workspace package reference
|
||||
pub mod pkg_ref;
|
||||
/// The workspace dependency specifier
|
||||
pub mod specifier;
|
||||
|
||||
/// The workspace package source
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WorkspacePackageSource;
|
||||
|
||||
impl PackageSource for WorkspacePackageSource {
|
||||
type Specifier = specifier::WorkspaceDependencySpecifier;
|
||||
type Ref = WorkspacePackageRef;
|
||||
type RefreshError = errors::RefreshError;
|
||||
type ResolveError = errors::ResolveError;
|
||||
type DownloadError = errors::DownloadError;
|
||||
|
||||
fn refresh(&self, _project: &Project) -> Result<(), Self::RefreshError> {
|
||||
// no-op
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &Self::Specifier,
|
||||
project: &Project,
|
||||
_package_target: TargetKind,
|
||||
) -> Result<ResolveResult<Self::Ref>, Self::ResolveError> {
|
||||
let (path, manifest) = 'finder: {
|
||||
let workspace_dir = project
|
||||
.workspace_dir
|
||||
.as_ref()
|
||||
.unwrap_or(&project.package_dir);
|
||||
|
||||
for (path, manifest) in project.workspace_members(workspace_dir)? {
|
||||
if manifest.name == specifier.name {
|
||||
break 'finder (path, manifest);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(errors::ResolveError::NoWorkspaceMember(
|
||||
specifier.name.to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
Ok((
|
||||
PackageNames::Pesde(manifest.name.clone()),
|
||||
BTreeMap::from([(
|
||||
VersionId::new(manifest.version.clone(), manifest.target.kind()),
|
||||
WorkspacePackageRef {
|
||||
// workspace_dir is guaranteed to be Some by the workspace_members method
|
||||
// strip_prefix is guaranteed to be Some by same method
|
||||
// from_path is guaranteed to be Ok because we just stripped the absolute path
|
||||
path: RelativePathBuf::from_path(
|
||||
path.strip_prefix(project.workspace_dir.clone().unwrap())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
dependencies: manifest
|
||||
.all_dependencies()?
|
||||
.into_iter()
|
||||
.map(|(alias, (mut spec, ty))| {
|
||||
match &mut spec {
|
||||
DependencySpecifiers::Pesde(spec) => {
|
||||
let index_name =
|
||||
spec.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
|
||||
|
||||
spec.index = Some(
|
||||
manifest
|
||||
.indices
|
||||
.get(index_name)
|
||||
.ok_or(errors::ResolveError::IndexNotFound(
|
||||
index_name.to_string(),
|
||||
manifest.name.to_string(),
|
||||
))?
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "wally-compat")]
|
||||
DependencySpecifiers::Wally(spec) => {
|
||||
let index_name =
|
||||
spec.index.as_deref().unwrap_or(DEFAULT_INDEX_NAME);
|
||||
|
||||
spec.index = Some(
|
||||
manifest
|
||||
.wally_indices
|
||||
.get(index_name)
|
||||
.ok_or(errors::ResolveError::IndexNotFound(
|
||||
index_name.to_string(),
|
||||
manifest.name.to_string(),
|
||||
))?
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
DependencySpecifiers::Git(_) => {}
|
||||
DependencySpecifiers::Workspace(_) => {}
|
||||
}
|
||||
|
||||
Ok((alias, (spec, ty)))
|
||||
})
|
||||
.collect::<Result<_, errors::ResolveError>>()?,
|
||||
target: manifest.target,
|
||||
},
|
||||
)]),
|
||||
))
|
||||
}
|
||||
|
||||
fn download(
|
||||
&self,
|
||||
pkg_ref: &Self::Ref,
|
||||
project: &Project,
|
||||
_reqwest: &Client,
|
||||
) -> Result<(PackageFS, Target), Self::DownloadError> {
|
||||
let path = pkg_ref.path.to_path(project.workspace_dir.clone().unwrap());
|
||||
|
||||
Ok((
|
||||
PackageFS::Copy(path, pkg_ref.target.kind()),
|
||||
pkg_ref.target.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when using a workspace package source
|
||||
pub mod errors {
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when refreshing the workspace package source
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum RefreshError {}
|
||||
|
||||
/// Errors that can occur when resolving a workspace package
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum ResolveError {
|
||||
/// An error occurred reading the workspace members
|
||||
#[error("failed to read workspace members")]
|
||||
ReadWorkspaceMembers(#[from] crate::errors::WorkspaceMembersError),
|
||||
|
||||
/// No workspace member was found with the given name
|
||||
#[error("no workspace member found with name {0}")]
|
||||
NoWorkspaceMember(String),
|
||||
|
||||
/// An error occurred getting all dependencies
|
||||
#[error("failed to get all dependencies")]
|
||||
AllDependencies(#[from] crate::manifest::errors::AllDependenciesError),
|
||||
|
||||
/// An index of a member package was not found
|
||||
#[error("index {0} not found in member {1}")]
|
||||
IndexNotFound(String, String),
|
||||
}
|
||||
|
||||
/// Errors that can occur when downloading a workspace package
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum DownloadError {
|
||||
/// An error occurred reading the workspace members
|
||||
#[error("failed to read workspace members")]
|
||||
ReadWorkspaceMembers(#[from] std::io::Error),
|
||||
}
|
||||
}
|
40
src/source/workspace/pkg_ref.rs
Normal file
40
src/source/workspace/pkg_ref.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use relative_path::RelativePathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
manifest::{
|
||||
target::{Target, TargetKind},
|
||||
DependencyType,
|
||||
},
|
||||
source::{workspace::WorkspacePackageSource, DependencySpecifiers, PackageRef, PackageSources},
|
||||
};
|
||||
|
||||
/// A workspace package reference
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct WorkspacePackageRef {
|
||||
/// The path of the package
|
||||
pub path: RelativePathBuf,
|
||||
/// The dependencies of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub dependencies: BTreeMap<String, (DependencySpecifiers, DependencyType)>,
|
||||
/// The target of the package
|
||||
pub target: Target,
|
||||
}
|
||||
impl PackageRef for WorkspacePackageRef {
|
||||
fn dependencies(&self) -> &BTreeMap<String, (DependencySpecifiers, DependencyType)> {
|
||||
&self.dependencies
|
||||
}
|
||||
|
||||
fn use_new_structure(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn target_kind(&self) -> TargetKind {
|
||||
self.target.kind()
|
||||
}
|
||||
|
||||
fn source(&self) -> PackageSources {
|
||||
PackageSources::Workspace(WorkspacePackageSource)
|
||||
}
|
||||
}
|
78
src/source/workspace/specifier.rs
Normal file
78
src/source/workspace/specifier.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use crate::{names::PackageName, source::DependencySpecifier};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
/// The specifier for a workspace dependency
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct WorkspaceDependencySpecifier {
|
||||
/// The name of the workspace package
|
||||
#[serde(rename = "workspace")]
|
||||
pub name: PackageName,
|
||||
/// The version type to use when publishing the package
|
||||
#[serde(default, rename = "version")]
|
||||
pub version_type: VersionType,
|
||||
}
|
||||
impl DependencySpecifier for WorkspaceDependencySpecifier {}
|
||||
|
||||
impl Display for WorkspaceDependencySpecifier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "workspace:{}{}", self.version_type, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of version to use when publishing a package
|
||||
#[derive(
|
||||
Debug, SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Default,
|
||||
)]
|
||||
pub enum VersionType {
|
||||
/// The "^" version type
|
||||
#[default]
|
||||
Caret,
|
||||
/// The "~" version type
|
||||
Tilde,
|
||||
/// The "=" version type
|
||||
Exact,
|
||||
/// The "*" version type
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
impl Display for VersionType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VersionType::Caret => write!(f, "^"),
|
||||
VersionType::Tilde => write!(f, "~"),
|
||||
VersionType::Exact => write!(f, "="),
|
||||
VersionType::Wildcard => write!(f, "*"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionType {
|
||||
type Err = errors::VersionTypeFromStr;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"^" => Ok(VersionType::Caret),
|
||||
"~" => Ok(VersionType::Tilde),
|
||||
"=" => Ok(VersionType::Exact),
|
||||
"*" => Ok(VersionType::Wildcard),
|
||||
_ => Err(errors::VersionTypeFromStr::InvalidVersionType(
|
||||
s.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when using a version type
|
||||
pub mod errors {
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when parsing a version type
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VersionTypeFromStr {
|
||||
/// The version type is invalid
|
||||
#[error("invalid version type: {0}")]
|
||||
InvalidVersionType(String),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue