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

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 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::<Result<_, _>>()
.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")?;

View file

@ -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),

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 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<std::path::PathBuf> {
pub fn home_dir() -> anyhow::Result<PathBuf> {
Ok(dirs::home_dir()
.context("failed to get home directory")?
.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");
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<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::{
lockfile::{DependencyGraph, DownloadedDependencyGraphNode, DownloadedGraph},
manifest::DependencyType,
source::{
traits::{PackageRef, PackageSource},
PackageSources,
@ -27,6 +28,8 @@ impl Project {
refreshed_sources: &mut HashSet<PackageSources>,
reqwest: &reqwest::blocking::Client,
threads: usize,
prod: bool,
write: bool,
) -> Result<MultithreadDownloadJob, errors::DownloadGraphError> {
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

View file

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

View file

@ -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",