Skip to content

Commit

Permalink
Completed --locked feature
Browse files Browse the repository at this point in the history
- Testing is now complete
- Locked works as intended now
- Refactored common code into a utils module for subcommands
  • Loading branch information
u-train committed Jun 15, 2023
1 parent ff5977d commit c10db32
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 235 deletions.
156 changes: 60 additions & 96 deletions src/commands/install.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
use std::collections::BTreeSet;

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

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

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::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 Down Expand Up @@ -51,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);
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());
}
}

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 @@ -84,77 +119,6 @@ impl InstallSubcommand {
resolved.activated.len() - 1
));

if self.locked && resolved.activated != try_to_use {
// We know at this point that these are not equal sets.
// Meaning, at least there was a new dependency being used or an old dependency that is no longer being used.
// We'll find either by taking the difference of the latest set of dependencies and the old set of dependencies.
let old_dependencies: Vec<_> = try_to_use.difference(&resolved.activated).collect();
let latest_dependencies: Vec<_> = resolved.activated.difference(&try_to_use).collect();

// If a dependency name is present in both sets, then it was updated.
let updated_dependencies = {
let mut updated_ids = Vec::new();

for old_id in old_dependencies.iter() {
for new_id in latest_dependencies.iter() {
if old_id.name() == new_id.name() {
updated_ids.push((old_id, new_id));
break;
}
}
}

updated_ids
};

// If there is a dependency in the latest set, but not in the old set, then it is a new dependency.
let gained_dependencies: Vec<_> = latest_dependencies
.iter()
.filter(|new_id| {
!old_dependencies
.iter()
.any(|old_id| old_id.name() == new_id.name())
})
.collect();

// If there is a dependency in the old set, but not in the latest, then it's a dependency no longer used.
let lost_dependencies: Vec<_> = old_dependencies
.iter()
.filter(|old_id| {
!latest_dependencies
.iter()
.any(|new_id| new_id.name() == old_id.name())
})
.collect();

let mut formatted_result: String = updated_dependencies
.iter()
.map(|(old, new)| format!("Updated {} to {}\n", old, new))
.chain(
gained_dependencies
.iter()
.map(|new| format!("Added {}\n", new)),
)
.chain(
lost_dependencies
.iter()
.map(|old| format!("Removed {}\n", old)),
)
.collect();

// There'll be an extra new-line at the end, we can pop it off for comestic reasons.
formatted_result.pop();

bail!(
indoc! {r#"
The dependencies and their versions being installed do not match with the lockfile! These are the conflicts:
{}
"#},
formatted_result
)
}

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

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};

/// 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

0 comments on commit c10db32

Please sign in to comment.