From 702153d81bf6e505e4f14740f3da758758574669 Mon Sep 17 00:00:00 2001 From: daimond113 <72147841+daimond113@users.noreply.github.com> Date: Wed, 4 Sep 2024 19:48:37 +0200 Subject: [PATCH] feat: proper update command --- src/cli/commands/init.rs | 4 +- src/cli/commands/install.rs | 151 +++++++++++++++--------------------- src/cli/commands/mod.rs | 10 +-- src/cli/commands/update.rs | 60 ++++++++++++++ src/cli/mod.rs | 100 +++++++++++++++++++++++- src/download.rs | 23 ++++-- src/patches.rs | 10 +-- src/resolver.rs | 5 +- 8 files changed, 245 insertions(+), 118 deletions(-) create mode 100644 src/cli/commands/update.rs diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index 49af6a0..4cf560b 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -19,8 +19,8 @@ fn script_contents(path: &Path) -> String { r#"local process = require("@lune/process") local home_dir = if process.os == "windows" then process.env.userprofile else process.env.HOME -require(home_dir .. "/{HOME_DIR}/scripts/{}"#, - path.display() +require(home_dir .. {:?})"#, + format!("/{HOME_DIR}/scripts/{}", path.display()) ) } diff --git a/src/cli/commands/install.rs b/src/cli/commands/install.rs index 560026d..ccfd104 100644 --- a/src/cli/commands/install.rs +++ b/src/cli/commands/install.rs @@ -1,25 +1,30 @@ -use crate::cli::{bin_dir, files::make_executable}; +use crate::cli::{ + bin_dir, download_graph, files::make_executable, run_on_workspace_members, up_to_date_lockfile, +}; use anyhow::Context; use clap::Args; use colored::{ColoredString, Colorize}; use indicatif::MultiProgress; -use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME}; -use relative_path::RelativePathBuf; -use std::{ - collections::{BTreeSet, HashSet}, - sync::Arc, - time::Duration, +use pesde::{ + lockfile::Lockfile, + manifest::{target::TargetKind, DependencyType}, + Project, MANIFEST_FILE_NAME, }; +use std::collections::{BTreeSet, HashSet}; -#[derive(Debug, Args)] +#[derive(Debug, Args, Copy, Clone)] pub struct InstallCommand { /// The amount of threads to use for downloading #[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))] threads: u64, - /// Whether to ignore the lockfile, refreshing it - #[arg(short, long)] - pub unlocked: bool, + /// Whether to error on changes in the lockfile + #[arg(long)] + locked: bool, + + /// Whether to not install dev dependencies + #[arg(long)] + prod: bool, } fn bin_link_file(alias: &str) -> String { @@ -92,8 +97,16 @@ impl InstallCommand { .deser_manifest() .context("failed to read manifest")?; - let lockfile = if self.unlocked { - None + let lockfile = if self.locked { + match up_to_date_lockfile(&project)? { + None => { + anyhow::bail!( + "lockfile is out of sync, run `{} install` to update it", + env!("CARGO_BIN_NAME") + ); + } + file => file, + } } else { match project.deser_lockfile() { Ok(lockfile) => { @@ -166,51 +179,45 @@ impl InstallCommand { .dependency_graph(old_graph.as_ref(), &mut refreshed_sources) .context("failed to build dependency graph")?; - let bar = multi.add( - indicatif::ProgressBar::new(graph.values().map(|versions| versions.len() as u64).sum()) - .with_style( - indicatif::ProgressStyle::default_bar().template( - "{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}", - )?, - ) - .with_message(format!("{} πŸ“₯ downloading dependencies", job(3))), - ); - bar.enable_steady_tick(Duration::from_millis(100)); + let downloaded_graph = download_graph( + &project, + &mut refreshed_sources, + &graph, + &multi, + &reqwest, + self.threads as usize, + self.prod, + true, + format!("{} πŸ“₯ downloading dependencies", job(3)), + format!("{} πŸ“₯ downloaded dependencies", job(3)), + )?; - let (rx, downloaded_graph) = project - .download_graph( - &graph, - &mut refreshed_sources, - &reqwest, - self.threads as usize, - ) - .context("failed to download dependencies")?; - - while let Ok(result) = rx.recv() { - bar.inc(1); - - match result { - Ok(()) => {} - Err(e) => return Err(e.into()), - } - } - - bar.finish_with_message(format!("{} πŸ“₯ downloaded dependencies", job(3),)); - - let downloaded_graph = Arc::into_inner(downloaded_graph) - .unwrap() - .into_inner() - .unwrap(); + let filtered_graph = if self.prod { + downloaded_graph + .clone() + .into_iter() + .map(|(n, v)| { + ( + n, + v.into_iter() + .filter(|(_, n)| n.node.ty != DependencyType::Dev) + .collect(), + ) + }) + .collect() + } else { + downloaded_graph.clone() + }; println!("{} πŸ—ΊοΈ linking dependencies", job(4)); project - .link_dependencies(&downloaded_graph) + .link_dependencies(&filtered_graph) .context("failed to link dependencies")?; let bin_folder = bin_dir()?; - for versions in downloaded_graph.values() { + for versions in filtered_graph.values() { for node in versions.values() { if node.target.bin_path().is_none() { continue; @@ -248,7 +255,7 @@ impl InstallCommand { println!("{} 🩹 applying patches", job(5)); project - .apply_patches(&downloaded_graph) + .apply_patches(&filtered_graph) .context("failed to apply patches")?; } @@ -260,48 +267,12 @@ 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::>() - .context("failed to install workspace member's dependencies")?, - }, graph: downloaded_graph, + + workspace: run_on_workspace_members(&project, |project| { + self.run(project, multi.clone(), reqwest.clone()) + })?, }) .context("failed to write lockfile")?; diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index 44481bd..b699db4 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -17,6 +17,7 @@ mod publish; mod run; mod self_install; mod self_upgrade; +mod update; #[derive(Debug, clap::Subcommand)] pub enum Subcommand { @@ -57,8 +58,8 @@ pub enum Subcommand { /// Adds a dependency to the project Add(add::AddCommand), - /// Updates the project's lockfile. note: this command is just an alias for `install --unlocked` - Update(install::InstallCommand), + /// Updates the project's lockfile. Run install to apply changes + Update(update::UpdateCommand), /// Checks for outdated dependencies Outdated(outdated::OutdatedCommand), @@ -90,10 +91,7 @@ impl Subcommand { Subcommand::PatchCommit(patch_commit) => patch_commit.run(project), Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest), Subcommand::Add(add) => add.run(project), - Subcommand::Update(mut update) => { - update.unlocked = true; - update.run(project, multi, reqwest) - } + Subcommand::Update(update) => update.run(project, multi, reqwest), Subcommand::Outdated(outdated) => outdated.run(project), #[cfg(any(feature = "lune", feature = "luau"))] Subcommand::Execute(execute) => execute.run(project, reqwest), diff --git a/src/cli/commands/update.rs b/src/cli/commands/update.rs new file mode 100644 index 0000000..1da5a13 --- /dev/null +++ b/src/cli/commands/update.rs @@ -0,0 +1,60 @@ +use crate::cli::{download_graph, run_on_workspace_members}; +use anyhow::Context; +use clap::Args; +use indicatif::MultiProgress; +use pesde::{lockfile::Lockfile, Project}; +use std::collections::HashSet; + +#[derive(Debug, Args, Copy, Clone)] +pub struct UpdateCommand { + /// The amount of threads to use for downloading + #[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))] + threads: u64, +} + +impl UpdateCommand { + pub fn run( + self, + project: Project, + multi: MultiProgress, + reqwest: reqwest::blocking::Client, + ) -> anyhow::Result<()> { + let mut refreshed_sources = HashSet::new(); + + let manifest = project + .deser_manifest() + .context("failed to read manifest")?; + + let graph = project + .dependency_graph(None, &mut refreshed_sources) + .context("failed to build dependency graph")?; + + project + .write_lockfile(Lockfile { + name: manifest.name, + version: manifest.version, + target: manifest.target.kind(), + overrides: manifest.overrides, + + graph: download_graph( + &project, + &mut refreshed_sources, + &graph, + &multi, + &reqwest, + self.threads as usize, + false, + false, + "πŸ“₯ downloading dependencies".to_string(), + "πŸ“₯ downloaded dependencies".to_string(), + )?, + + workspace: run_on_workspace_members(&project, |project| { + self.run(project, multi.clone(), reqwest.clone()) + })?, + }) + .context("failed to write lockfile")?; + + Ok(()) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1b1f93a..b6651b9 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,17 +1,22 @@ use crate::cli::auth::get_token; use anyhow::Context; use gix::bstr::BStr; +use indicatif::MultiProgress; use pesde::{ - lockfile::{DownloadedGraph, Lockfile}, + lockfile::{DependencyGraph, DownloadedGraph, Lockfile}, names::{PackageName, PackageNames}, - source::{version_id::VersionId, workspace::specifier::VersionType}, + source::{version_id::VersionId, workspace::specifier::VersionType, PackageSources}, Project, }; +use relative_path::RelativePathBuf; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer}; use std::{ collections::{BTreeMap, HashSet}, fs::create_dir_all, + path::PathBuf, str::FromStr, + sync::Arc, + time::Duration, }; pub mod auth; @@ -23,13 +28,13 @@ pub mod version; pub const HOME_DIR: &str = concat!(".", env!("CARGO_PKG_NAME")); -pub fn home_dir() -> anyhow::Result { +pub fn home_dir() -> anyhow::Result { Ok(dirs::home_dir() .context("failed to get home directory")? .join(HOME_DIR)) } -pub fn bin_dir() -> anyhow::Result { +pub fn bin_dir() -> anyhow::Result { let bin_dir = home_dir()?.join("bin"); create_dir_all(&bin_dir).context("failed to create bin folder")?; Ok(bin_dir) @@ -203,3 +208,90 @@ pub fn deserialize_string_url_map<'de, D: Deserializer<'de>>( }) .collect() } + +#[allow(clippy::too_many_arguments)] +pub fn download_graph( + project: &Project, + refreshed_sources: &mut HashSet, + graph: &DependencyGraph, + multi: &MultiProgress, + reqwest: &reqwest::blocking::Client, + threads: usize, + prod: bool, + write: bool, + progress_msg: String, + finish_msg: String, +) -> anyhow::Result { + let bar = multi.add( + indicatif::ProgressBar::new(graph.values().map(|versions| versions.len() as u64).sum()) + .with_style( + indicatif::ProgressStyle::default_bar() + .template("{msg} {bar:40.208/166} {pos}/{len} {percent}% {elapsed_precise}")?, + ) + .with_message(progress_msg), + ); + bar.enable_steady_tick(Duration::from_millis(100)); + + let (rx, downloaded_graph) = project + .download_graph(graph, refreshed_sources, reqwest, threads, prod, write) + .context("failed to download dependencies")?; + + while let Ok(result) = rx.recv() { + bar.inc(1); + + match result { + Ok(()) => {} + Err(e) => return Err(e.into()), + } + } + + bar.finish_with_message(finish_msg); + + Ok(Arc::into_inner(downloaded_graph) + .unwrap() + .into_inner() + .unwrap()) +} + +pub fn shift_project_dir(project: &Project, pkg_dir: PathBuf) -> Project { + Project::new( + pkg_dir, + Some(project.package_dir()), + project.data_dir(), + project.cas_dir(), + project.auth_config().clone(), + ) +} + +pub fn run_on_workspace_members( + project: &Project, + f: impl Fn(Project) -> anyhow::Result<()>, +) -> anyhow::Result> { + Ok(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)| { + f(shift_project_dir( + project, + path.to_path(project.package_dir()), + )) + .map(|_| (name, path)) + }) + .collect::>() + .context("failed to install workspace member's dependencies")?, + }) +} diff --git a/src/download.rs b/src/download.rs index 17bad39..fd22341 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,5 +1,6 @@ use crate::{ lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph}, + manifest::DependencyType, source::{ traits::{PackageRef, PackageSource}, PackageSources, @@ -27,6 +28,8 @@ impl Project { refreshed_sources: &mut HashSet, reqwest: &reqwest::blocking::Client, threads: usize, + prod: bool, + write: bool, ) -> Result { let manifest = self.deser_manifest()?; let downloaded_graph: MultithreadedGraph = Arc::new(Mutex::new(Default::default())); @@ -83,14 +86,20 @@ impl Project { log::debug!("downloaded {name}@{version_id}"); - match fs.write_to(container_folder, project.cas_dir(), true) { - Ok(_) => {} - Err(e) => { - tx.send(Err(errors::DownloadGraphError::WriteFailed(e))) - .unwrap(); - return; + if write { + if !prod || node.ty != DependencyType::Dev { + match fs.write_to(container_folder, project.cas_dir(), true) { + Ok(_) => {} + Err(e) => { + tx.send(Err(errors::DownloadGraphError::WriteFailed(e))) + .unwrap(); + return; + } + }; + } else { + log::debug!("skipping writing {name}@{version_id} to disk, dev dependency in prod mode"); } - }; + } let mut downloaded_graph = downloaded_graph.lock().unwrap(); downloaded_graph diff --git a/src/patches.rs b/src/patches.rs index b570c91..79c0793 100644 --- a/src/patches.rs +++ b/src/patches.rs @@ -85,7 +85,10 @@ impl Project { .get(&name) .and_then(|versions| versions.get(&version_id)) else { - return Err(errors::ApplyPatchesError::PackageNotFound(name, version_id)); + log::warn!( + "patch for {name}@{version_id} not applied because it is not in the graph" + ); + continue; }; let container_folder = node.node.container_folder( @@ -155,7 +158,6 @@ impl Project { pub mod errors { use std::path::PathBuf; - use crate::{names::PackageNames, source::version_id::VersionId}; use thiserror::Error; /// Errors that can occur when applying patches @@ -177,9 +179,5 @@ pub mod errors { /// Error removing the .git directory #[error("error removing .git directory")] GitDirectoryRemovalError(PathBuf, #[source] std::io::Error), - - /// Package not found in the graph - #[error("package {0}@{1} not found in graph")] - PackageNotFound(PackageNames, VersionId), } } diff --git a/src/resolver.rs b/src/resolver.rs index a09aaf6..524f823 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -4,7 +4,6 @@ use crate::{ names::PackageNames, source::{ pesde::PesdePackageSource, - refs::PackageRefs, specifiers::DependencySpecifiers, traits::{PackageRef, PackageSource}, version_id::VersionId, @@ -244,8 +243,8 @@ impl Project { target_version_id ); - if matches!(already_resolved.pkg_ref, PackageRefs::Git(_)) - != matches!(pkg_ref, PackageRefs::Git(_)) + if std::mem::discriminant(&already_resolved.pkg_ref) + != std::mem::discriminant(pkg_ref) { log::warn!( "resolved package {name}@{target_version_id} has a different source than the previously resolved one, this may cause issues",