Skip to content

Commit

Permalink
Initial implementation of --locked (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
u-train authored Nov 3, 2024
1 parent 5692586 commit ffa2e76
Show file tree
Hide file tree
Showing 16 changed files with 484 additions and 175 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Wally Changelog

## Unreleased Changes
* Added --locked flag for the install subcommand ([#119])

[#119]: https://github.com/UpliftGames/wally/pull/119

## 0.3.2 (2023-06-05)
* Added private field to package manifest ([#9])([#76])
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,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
9 changes: 7 additions & 2 deletions src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,20 @@ impl InitSubcommand {

match fs_err::metadata(&manifest_path) {
Ok(_) => bail!(
"There is already a Wally project in this directory. Manifest file ({}) already exists.",
"There is already a Wally project in this directory. Manifest file ({}) already \
exists.",
MANIFEST_FILE_NAME
),

Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
// Perfect! This is the state that we want
} else {
bail!("Error accessing manifest file ({}): {}", MANIFEST_FILE_NAME, err);
bail!(
"Error accessing manifest file ({}): {}",
MANIFEST_FILE_NAME,
err
);
}
}
}
Expand Down
90 changes: 68 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::io::Write;
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 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_dependency_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,67 @@ 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_dependency_changes(old_dependencies, &latest_graph.activated);
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)
)?;

anyhow::bail!(String::from_utf8(error_output)
.expect("output from render_update_difference should always be utf-8"));
}
}

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...{}",
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 +122,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/commands/utils.rs
expression: "String::from_utf8(writer).unwrap()"
---
 No Dependency changes

140 changes: 8 additions & 132 deletions src/commands/update.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::collections::BTreeSet;
use std::convert::TryInto;

use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
Expand All @@ -16,6 +16,8 @@ use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor};
use indicatif::{ProgressBar, ProgressStyle};
use structopt::StructOpt;

use super::utils::{generate_dependency_changes, render_update_difference};

/// Update all of the dependencies of this project.
#[derive(Debug, StructOpt)]
pub struct UpdateSubcommand {
Expand Down Expand Up @@ -98,9 +100,11 @@ impl UpdateSubcommand {

progress.enable_steady_tick(Duration::from_millis(100));
progress.suspend(|| {
let dependency_changes =
generate_depedency_changes(&lockfile.as_ids().collect(), &resolved_graph.activated);
render_update_difference(&dependency_changes);
let dependency_changes = generate_dependency_changes(
&lockfile.as_ids().collect(),
&resolved_graph.activated,
);
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 +182,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

0 comments on commit ffa2e76

Please sign in to comment.