Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of --locked #119

Merged
merged 13 commits into from
Nov 3, 2024
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ Parity with:
Installs all packages.

`--locked` matches `cargo XXX --locked`, which will error if there is not an up-to-date lockfile. Intended for use on CI machines.
(locked is a planned feature and not yet implemented)

Parity with:
* `npm install` with no arguments
Expand Down
87 changes: 65 additions & 22 deletions src/commands/install.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
use std::collections::BTreeSet;

use std::path::PathBuf;
use std::time::Duration;

use crossterm::style::{Color, SetForegroundColor};
use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor};
use indicatif::{ProgressBar, ProgressStyle};

use std::io::Write;
use structopt::StructOpt;

use crate::installation::InstallationContext;
use crate::lockfile::{LockPackage, Lockfile};
use crate::lockfile::Lockfile;
use crate::manifest::Manifest;
use crate::package_id::PackageId;
use crate::package_source::{PackageSource, PackageSourceMap, Registry, TestRegistry};
use crate::resolution::resolve;

use super::utils::{generate_depedency_changes, render_update_difference};
use super::GlobalOptions;

/// Install all of the dependencies of this project.
Expand All @@ -21,6 +25,10 @@ pub struct InstallSubcommand {
/// Path to the project to install dependencies for.
#[structopt(long = "project-path", default_value = ".")]
pub project_path: PathBuf,

/// Flag to error if the lockfile does not match with the latest dependencies.
#[structopt(long = "locked")]
pub locked: bool,
}

impl InstallSubcommand {
Expand All @@ -43,29 +51,64 @@ impl InstallSubcommand {
let mut package_sources = PackageSourceMap::new(default_registry);
package_sources.add_fallbacks()?;

let mut try_to_use = BTreeSet::new();
for package in lockfile.packages {
match package {
LockPackage::Registry(registry_package) => {
try_to_use.insert(PackageId::new(
registry_package.name,
registry_package.version,
));
}
LockPackage::Git(_) => {}
let try_to_use = lockfile.as_ids().collect();

let progress = ProgressBar::new(0).with_style(
ProgressStyle::with_template("{spinner:.cyan}{wide_msg}")?.tick_chars("⠁⠈⠐⠠⠄⠂ "),
);

progress.enable_steady_tick(Duration::from_millis(100));

if self.locked {
progress.println(format!(
"{} Verifying {}lockfile is up-to-date...",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

let latest_graph = resolve(&manifest, &BTreeSet::new(), &package_sources)?;

if try_to_use != latest_graph.activated {
progress.finish_and_clear();

let old_dependencies = &try_to_use;

let changes = generate_depedency_changes(old_dependencies, &latest_graph.activated);
u-train marked this conversation as resolved.
Show resolved Hide resolved
let mut error_output = Vec::new();

writeln!(
error_output,
"{} The Lockfile is out of date and wasn't changed due to --locked{}",
SetForegroundColor(Color::Yellow),
SetForegroundColor(Color::Reset)
)?;
render_update_difference(&changes, &mut error_output)?;
writeln!(
error_output,
"{}{} Suggestion{}{} try running wally update",
SetAttribute(Attribute::Bold),
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset),
SetAttribute(Attribute::Reset)
)?;

// Should be safe since we're only getting valid utf-8.
anyhow::bail!(String::from_utf8(error_output).unwrap());
}
u-train marked this conversation as resolved.
Show resolved Hide resolved
}

let progress = ProgressBar::new(0)
.with_style(
ProgressStyle::with_template("{spinner:.cyan}{wide_msg}")?.tick_chars("⠁⠈⠐⠠⠄⠂ "),
)
.with_message(format!(
"{} Resolving {}packages...",
progress.println(format!(
"{} Verified {}lockfile is up-to-date...{}",
u-train marked this conversation as resolved.
Show resolved Hide resolved
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Green),
SetForegroundColor(Color::Reset)
));
progress.enable_steady_tick(Duration::from_millis(100));
}

progress.println(format!(
"{} Resolving {}packages...",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

let resolved = resolve(&manifest, &try_to_use, &package_sources)?;

Expand All @@ -76,8 +119,8 @@ impl InstallSubcommand {
resolved.activated.len() - 1
));

let lockfile = Lockfile::from_resolve(&resolved);
lockfile.save(&self.project_path)?;
let new_lockfile = Lockfile::from_resolve(&resolved);
new_lockfile.save(&self.project_path)?;

progress.println(format!(
"{} Generated {}lockfile",
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod package;
mod publish;
mod search;
mod update;
mod utils;

pub use init::InitSubcommand;
pub use install::InstallSubcommand;
Expand Down
133 changes: 3 additions & 130 deletions src/commands/update.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::BTreeSet;
use std::convert::TryInto;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
Expand All @@ -16,6 +15,8 @@ use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor};
use indicatif::{ProgressBar, ProgressStyle};
use structopt::StructOpt;

use super::utils::{generate_depedency_changes, render_update_difference};
u-train marked this conversation as resolved.
Show resolved Hide resolved

/// Update all of the dependencies of this project.
#[derive(Debug, StructOpt)]
pub struct UpdateSubcommand {
Expand Down Expand Up @@ -100,7 +101,7 @@ impl UpdateSubcommand {
progress.suspend(|| {
let dependency_changes =
generate_depedency_changes(&lockfile.as_ids().collect(), &resolved_graph.activated);
render_update_difference(&dependency_changes);
render_update_difference(&dependency_changes, &mut std::io::stdout()).unwrap();
});

Lockfile::from_resolve(&resolved_graph).save(&self.project_path)?;
Expand Down Expand Up @@ -178,131 +179,3 @@ impl FromStr for PackageSpec {
}
}
}

enum DependencyChange {
Added(PackageId),
Removed(PackageId),
Updated { from: PackageId, to: PackageId },
Downgrade { from: PackageId, to: PackageId },
}

fn generate_depedency_changes(
old_dependencies: &BTreeSet<PackageId>,
new_dependencies: &BTreeSet<PackageId>,
) -> Vec<DependencyChange> {
let added_packages = new_dependencies.difference(old_dependencies);
let removed_packages = old_dependencies.difference(new_dependencies);
let changed_dependencies: BTreeSet<&PackageName> = added_packages
.clone()
.chain(removed_packages.clone())
.map(|package| package.name())
.collect();

let mut dependency = Vec::new();

for dependency_name in changed_dependencies {
let matching_packages_removed = removed_packages
.clone()
.filter(|x| *x.name() == *dependency_name);
let matching_packages_added = added_packages
.clone()
.filter(|x| *x.name() == *dependency_name);

match (
matching_packages_added.clone().count(),
matching_packages_removed.clone().count(),
) {
(1, 1) => {
// We know for certain that there is only one item in the iterator.
let package_added = matching_packages_added.last().unwrap();
let package_removed = matching_packages_removed.last().unwrap();

if package_added.gt(package_removed) {
dependency.push(DependencyChange::Updated {
from: package_removed.clone(),
to: package_added.clone(),
});
} else {
dependency.push(DependencyChange::Downgrade {
from: package_added.clone(),
to: package_removed.clone(),
});
}
}
(0, 1) => {
// We know for certain that there is only one item in the iterator.
let package_removed = matching_packages_removed.last().unwrap();
dependency.push(DependencyChange::Removed(package_removed.clone()));
}
(1, 0) => {
// We know for certain that there is only one item in the iterator.
let package_added = matching_packages_added.last().unwrap();
dependency.push(DependencyChange::Added(package_added.clone()));
}
(0, 0) => panic!("Impossible for the package name {} to not be removed or added if found in earlier.", dependency_name),
(_, _) => {
dependency.extend(matching_packages_added.map(|package| DependencyChange::Added(package.clone())));
dependency.extend(matching_packages_removed.map(|package| DependencyChange::Removed(package.clone())));
}
}
}

dependency
}

fn render_update_difference(dependency_changes: &[DependencyChange]) {
if dependency_changes.is_empty() {
return println!(
"{} No Dependency changes{}",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
);
}

println!(
"{} Dependency changes{}",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
);

for dependency_change in dependency_changes {
match dependency_change {
DependencyChange::Added(package_id) => {
println!(
"{} Added {}{} v{}",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset),
package_id.name(),
package_id.version()
);
}
DependencyChange::Removed(package_id) => {
println!(
"{} Removed {}{} v{}",
SetForegroundColor(Color::DarkRed),
SetForegroundColor(Color::Reset),
package_id.name(),
package_id.version()
);
}
DependencyChange::Updated { from, to } => {
println!(
"{} Updated {}{} from v{} to v{}",
SetForegroundColor(Color::DarkCyan),
SetForegroundColor(Color::Reset),
from.name(),
from.version(),
to.version()
);
}
DependencyChange::Downgrade { from, to } => println!(
"{} Downgraded {}{} from v{} to v{}",
SetForegroundColor(Color::DarkYellow),
SetForegroundColor(Color::Reset),
from.name(),
from.version(),
to.version()
),
}
}
}
Loading