feat: proper update command

This commit is contained in:
daimond113 2024-09-04 19:48:37 +02:00
parent c08dfb9965
commit 702153d81b
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
8 changed files with 245 additions and 118 deletions

View file

@ -19,8 +19,8 @@ fn script_contents(path: &Path) -> String {
r#"local process = require("@lune/process") r#"local process = require("@lune/process")
local home_dir = if process.os == "windows" then process.env.userprofile else process.env.HOME local home_dir = if process.os == "windows" then process.env.userprofile else process.env.HOME
require(home_dir .. "/{HOME_DIR}/scripts/{}"#, require(home_dir .. {:?})"#,
path.display() format!("/{HOME_DIR}/scripts/{}", path.display())
) )
} }

View file

@ -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 anyhow::Context;
use clap::Args; use clap::Args;
use colored::{ColoredString, Colorize}; use colored::{ColoredString, Colorize};
use indicatif::MultiProgress; use indicatif::MultiProgress;
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project, MANIFEST_FILE_NAME}; use pesde::{
use relative_path::RelativePathBuf; lockfile::Lockfile,
use std::{ manifest::{target::TargetKind, DependencyType},
collections::{BTreeSet, HashSet}, Project, MANIFEST_FILE_NAME,
sync::Arc,
time::Duration,
}; };
use std::collections::{BTreeSet, HashSet};
#[derive(Debug, Args)] #[derive(Debug, Args, Copy, Clone)]
pub struct InstallCommand { pub struct InstallCommand {
/// The amount of threads to use for downloading /// The amount of threads to use for downloading
#[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))] #[arg(short, long, default_value_t = 6, value_parser = clap::value_parser!(u64).range(1..=128))]
threads: u64, threads: u64,
/// Whether to ignore the lockfile, refreshing it /// Whether to error on changes in the lockfile
#[arg(short, long)] #[arg(long)]
pub unlocked: bool, locked: bool,
/// Whether to not install dev dependencies
#[arg(long)]
prod: bool,
} }
fn bin_link_file(alias: &str) -> String { fn bin_link_file(alias: &str) -> String {
@ -92,8 +97,16 @@ impl InstallCommand {
.deser_manifest() .deser_manifest()
.context("failed to read manifest")?; .context("failed to read manifest")?;
let lockfile = if self.unlocked { let lockfile = if self.locked {
None 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 { } else {
match project.deser_lockfile() { match project.deser_lockfile() {
Ok(lockfile) => { Ok(lockfile) => {
@ -166,51 +179,45 @@ impl InstallCommand {
.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")?;
let bar = multi.add( let downloaded_graph = download_graph(
indicatif::ProgressBar::new(graph.values().map(|versions| versions.len() as u64).sum()) &project,
.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 (rx, downloaded_graph) = project
.download_graph(
&graph,
&mut refreshed_sources, &mut refreshed_sources,
&graph,
&multi,
&reqwest, &reqwest,
self.threads as usize, self.threads as usize,
self.prod,
true,
format!("{} 📥 downloading dependencies", job(3)),
format!("{} 📥 downloaded dependencies", job(3)),
)?;
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(),
) )
.context("failed to download dependencies")?; })
.collect()
while let Ok(result) = rx.recv() { } else {
bar.inc(1); downloaded_graph.clone()
};
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();
println!("{} 🗺️ linking dependencies", job(4)); println!("{} 🗺️ linking dependencies", job(4));
project project
.link_dependencies(&downloaded_graph) .link_dependencies(&filtered_graph)
.context("failed to link dependencies")?; .context("failed to link dependencies")?;
let bin_folder = bin_dir()?; let bin_folder = bin_dir()?;
for versions in downloaded_graph.values() { for versions in filtered_graph.values() {
for node in versions.values() { for node in versions.values() {
if node.target.bin_path().is_none() { if node.target.bin_path().is_none() {
continue; continue;
@ -248,7 +255,7 @@ impl InstallCommand {
println!("{} 🩹 applying patches", job(5)); println!("{} 🩹 applying patches", job(5));
project project
.apply_patches(&downloaded_graph) .apply_patches(&filtered_graph)
.context("failed to apply patches")?; .context("failed to apply patches")?;
} }
@ -260,48 +267,12 @@ impl InstallCommand {
version: manifest.version, version: manifest.version,
target: manifest.target.kind(), target: manifest.target.kind(),
overrides: manifest.overrides, 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, graph: downloaded_graph,
workspace: run_on_workspace_members(&project, |project| {
self.run(project, multi.clone(), reqwest.clone())
})?,
}) })
.context("failed to write lockfile")?; .context("failed to write lockfile")?;

View file

@ -17,6 +17,7 @@ mod publish;
mod run; mod run;
mod self_install; mod self_install;
mod self_upgrade; mod self_upgrade;
mod update;
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum Subcommand { pub enum Subcommand {
@ -57,8 +58,8 @@ pub enum Subcommand {
/// Adds a dependency to the project /// Adds a dependency to the project
Add(add::AddCommand), Add(add::AddCommand),
/// Updates the project's lockfile. note: this command is just an alias for `install --unlocked` /// Updates the project's lockfile. Run install to apply changes
Update(install::InstallCommand), Update(update::UpdateCommand),
/// Checks for outdated dependencies /// Checks for outdated dependencies
Outdated(outdated::OutdatedCommand), Outdated(outdated::OutdatedCommand),
@ -90,10 +91,7 @@ impl Subcommand {
Subcommand::PatchCommit(patch_commit) => patch_commit.run(project), Subcommand::PatchCommit(patch_commit) => patch_commit.run(project),
Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest), Subcommand::SelfUpgrade(self_upgrade) => self_upgrade.run(reqwest),
Subcommand::Add(add) => add.run(project), Subcommand::Add(add) => add.run(project),
Subcommand::Update(mut update) => { Subcommand::Update(update) => update.run(project, multi, reqwest),
update.unlocked = true;
update.run(project, multi, reqwest)
}
Subcommand::Outdated(outdated) => outdated.run(project), Subcommand::Outdated(outdated) => outdated.run(project),
#[cfg(any(feature = "lune", feature = "luau"))] #[cfg(any(feature = "lune", feature = "luau"))]
Subcommand::Execute(execute) => execute.run(project, reqwest), Subcommand::Execute(execute) => execute.run(project, reqwest),

View file

@ -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(())
}
}

View file

@ -1,17 +1,22 @@
use crate::cli::auth::get_token; use crate::cli::auth::get_token;
use anyhow::Context; use anyhow::Context;
use gix::bstr::BStr; use gix::bstr::BStr;
use indicatif::MultiProgress;
use pesde::{ use pesde::{
lockfile::{DownloadedGraph, Lockfile}, lockfile::{DependencyGraph, DownloadedGraph, Lockfile},
names::{PackageName, PackageNames}, names::{PackageName, PackageNames},
source::{version_id::VersionId, workspace::specifier::VersionType}, source::{version_id::VersionId, workspace::specifier::VersionType, PackageSources},
Project, Project,
}; };
use relative_path::RelativePathBuf;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer}; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
fs::create_dir_all, fs::create_dir_all,
path::PathBuf,
str::FromStr, str::FromStr,
sync::Arc,
time::Duration,
}; };
pub mod auth; pub mod auth;
@ -23,13 +28,13 @@ pub mod version;
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<PathBuf> {
Ok(dirs::home_dir() Ok(dirs::home_dir()
.context("failed to get home directory")? .context("failed to get home directory")?
.join(HOME_DIR)) .join(HOME_DIR))
} }
pub fn bin_dir() -> anyhow::Result<std::path::PathBuf> { pub fn bin_dir() -> anyhow::Result<PathBuf> {
let bin_dir = home_dir()?.join("bin"); let bin_dir = home_dir()?.join("bin");
create_dir_all(&bin_dir).context("failed to create bin folder")?; create_dir_all(&bin_dir).context("failed to create bin folder")?;
Ok(bin_dir) Ok(bin_dir)
@ -203,3 +208,90 @@ pub fn deserialize_string_url_map<'de, D: Deserializer<'de>>(
}) })
.collect() .collect()
} }
#[allow(clippy::too_many_arguments)]
pub fn download_graph(
project: &Project,
refreshed_sources: &mut HashSet<PackageSources>,
graph: &DependencyGraph,
multi: &MultiProgress,
reqwest: &reqwest::blocking::Client,
threads: usize,
prod: bool,
write: bool,
progress_msg: String,
finish_msg: String,
) -> anyhow::Result<DownloadedGraph> {
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<BTreeMap<PackageName, RelativePathBuf>> {
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::<Result<_, _>>()
.context("failed to install workspace member's dependencies")?,
})
}

View file

@ -1,5 +1,6 @@
use crate::{ use crate::{
lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph}, lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph},
manifest::DependencyType,
source::{ source::{
traits::{PackageRef, PackageSource}, traits::{PackageRef, PackageSource},
PackageSources, PackageSources,
@ -27,6 +28,8 @@ impl Project {
refreshed_sources: &mut HashSet<PackageSources>, refreshed_sources: &mut HashSet<PackageSources>,
reqwest: &reqwest::blocking::Client, reqwest: &reqwest::blocking::Client,
threads: usize, threads: usize,
prod: bool,
write: bool,
) -> Result<MultithreadDownloadJob, errors::DownloadGraphError> { ) -> Result<MultithreadDownloadJob, errors::DownloadGraphError> {
let manifest = self.deser_manifest()?; let manifest = self.deser_manifest()?;
let downloaded_graph: MultithreadedGraph = Arc::new(Mutex::new(Default::default())); let downloaded_graph: MultithreadedGraph = Arc::new(Mutex::new(Default::default()));
@ -83,6 +86,8 @@ impl Project {
log::debug!("downloaded {name}@{version_id}"); log::debug!("downloaded {name}@{version_id}");
if write {
if !prod || node.ty != DependencyType::Dev {
match fs.write_to(container_folder, project.cas_dir(), true) { match fs.write_to(container_folder, project.cas_dir(), true) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
@ -91,6 +96,10 @@ impl Project {
return; return;
} }
}; };
} else {
log::debug!("skipping writing {name}@{version_id} to disk, dev dependency in prod mode");
}
}
let mut downloaded_graph = downloaded_graph.lock().unwrap(); let mut downloaded_graph = downloaded_graph.lock().unwrap();
downloaded_graph downloaded_graph

View file

@ -85,7 +85,10 @@ impl Project {
.get(&name) .get(&name)
.and_then(|versions| versions.get(&version_id)) .and_then(|versions| versions.get(&version_id))
else { 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( let container_folder = node.node.container_folder(
@ -155,7 +158,6 @@ impl Project {
pub mod errors { pub mod errors {
use std::path::PathBuf; use std::path::PathBuf;
use crate::{names::PackageNames, source::version_id::VersionId};
use thiserror::Error; use thiserror::Error;
/// Errors that can occur when applying patches /// Errors that can occur when applying patches
@ -177,9 +179,5 @@ pub mod errors {
/// Error removing the .git directory /// Error removing the .git directory
#[error("error removing .git directory")] #[error("error removing .git directory")]
GitDirectoryRemovalError(PathBuf, #[source] std::io::Error), GitDirectoryRemovalError(PathBuf, #[source] std::io::Error),
/// Package not found in the graph
#[error("package {0}@{1} not found in graph")]
PackageNotFound(PackageNames, VersionId),
} }
} }

View file

@ -4,7 +4,6 @@ use crate::{
names::PackageNames, names::PackageNames,
source::{ source::{
pesde::PesdePackageSource, pesde::PesdePackageSource,
refs::PackageRefs,
specifiers::DependencySpecifiers, specifiers::DependencySpecifiers,
traits::{PackageRef, PackageSource}, traits::{PackageRef, PackageSource},
version_id::VersionId, version_id::VersionId,
@ -244,8 +243,8 @@ impl Project {
target_version_id target_version_id
); );
if matches!(already_resolved.pkg_ref, PackageRefs::Git(_)) if std::mem::discriminant(&already_resolved.pkg_ref)
!= matches!(pkg_ref, PackageRefs::Git(_)) != std::mem::discriminant(pkg_ref)
{ {
log::warn!( log::warn!(
"resolved package {name}@{target_version_id} has a different source than the previously resolved one, this may cause issues", "resolved package {name}@{target_version_id} has a different source than the previously resolved one, this may cause issues",