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

Update subcommand #140

Merged
merged 27 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
22663f0
Refactored TempProject into its own module and started on tests
u-train Jun 9, 2023
959726f
Created test projects with a diamond-graph
u-train Jun 9, 2023
cd5f9bd
Added to primary-registry
u-train Jun 9, 2023
5932fc0
Built the update subcommand.
u-train Jun 9, 2023
7b82533
Lockfiles need to be written!
u-train Jun 9, 2023
d6d1c99
Fixed misspelling of dependency_changes
u-train Jun 10, 2023
0348913
Reverted test_registry arg back to invisible
u-train Jun 10, 2023
489fcf3
Changed target_packages to package_specs
u-train Jun 10, 2023
8f1419d
Downgrading was added as a concept
u-train Jun 10, 2023
44936fb
Added lockfile method to view packages as packageIds
u-train Jun 11, 2023
d898ebf
Installs the new packages now, forgor.
u-train Jun 11, 2023
778a954
No longer throws if missing lockfile
u-train Jun 11, 2023
f827188
Clarity on the filtering process for try_to_use
u-train Jun 11, 2023
62927d5
Added needed imports
u-train Jun 11, 2023
5659da2
Made the clippy happy and fixed grammar mistakes
u-train Jun 11, 2023
5be9f42
Delete root from registry
u-train Jun 11, 2023
bad89dd
Add new tests, almost done with them all so far.
u-train Jun 11, 2023
a32bfed
Added the rest of snapshots missing
u-train Jun 11, 2023
a702584
Testing now works, yay!
u-train Jun 12, 2023
f437589
Deleted testing code by mistake and forgot snap
u-train Jun 12, 2023
aeffc50
Appleasing the formatter
u-train Jun 12, 2023
839c713
Made it look... *pretty* and cleaned messes
u-train Jun 12, 2023
ef4f340
another blood sarciface for rust fmt
u-train Jun 12, 2023
f67687d
Doing final cleanups per comments upstream
u-train Jun 12, 2023
0812efe
The gods demand it, another formatter sacrifice
u-train Jun 12, 2023
a71211e
The coolness must be toned down.
u-train Jun 13, 2023
18fdfe0
A little silly mistake indeed
u-train Jun 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use manifest_to_json::ManifestToJsonSubcommand;
pub use package::PackageSubcommand;
pub use publish::PublishSubcommand;
pub use search::SearchSubcommand;
pub use update::UpdateSubcommand;
pub use update::{PackageSpec, UpdateSubcommand};

use structopt::StructOpt;

Expand All @@ -37,7 +37,7 @@ impl Args {
Subcommand::Init(subcommand) => subcommand.run(),
Subcommand::Login(subcommand) => subcommand.run(),
Subcommand::Logout(subcommand) => subcommand.run(),
Subcommand::Update(subcommand) => subcommand.run(),
Subcommand::Update(subcommand) => subcommand.run(self.global),
Subcommand::Search(subcommand) => subcommand.run(),
Subcommand::Package(subcommand) => subcommand.run(),
Subcommand::Install(subcommand) => subcommand.run(self.global),
Expand Down
327 changes: 324 additions & 3 deletions src/commands/update.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,332 @@
use std::collections::BTreeSet;
use std::convert::TryInto;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;

use crate::installation::InstallationContext;
use crate::lockfile::Lockfile;
use crate::manifest::Manifest;
use crate::package_id::PackageId;
use crate::package_name::PackageName;
use crate::package_req::PackageReq;
use crate::package_source::{PackageSource, PackageSourceMap, Registry, TestRegistry};
use crate::{resolution, GlobalOptions};
use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor};
use indicatif::{ProgressBar, ProgressStyle};
use structopt::StructOpt;

/// Update all of the dependencies of this project.
#[derive(Debug, StructOpt)]
pub struct UpdateSubcommand {}
pub struct UpdateSubcommand {
/// Path to the project to publish.
#[structopt(long = "project-path", default_value = ".")]
pub project_path: PathBuf,
u-train marked this conversation as resolved.
Show resolved Hide resolved

/// An optional list of dependencies to update.
/// They must be valid package name with an optional version requirement.
pub package_specs: Vec<PackageSpec>,
}

impl UpdateSubcommand {
pub fn run(self) -> anyhow::Result<()> {
todo!("The 'update' subcommand")
pub fn run(self, global: GlobalOptions) -> anyhow::Result<()> {
let manifest = Manifest::load(&self.project_path)?;

let lockfile = match Lockfile::load(&self.project_path)? {
Some(lockfile) => lockfile,
None => Lockfile::from_manifest(&manifest),
};

let default_registry: Box<PackageSource> = if global.test_registry {
Box::new(PackageSource::TestRegistry(TestRegistry::new(
&manifest.package.registry,
)))
} else {
Box::new(PackageSource::Registry(Registry::from_registry_spec(
&manifest.package.registry,
)?))
};

let mut package_sources = PackageSourceMap::new(default_registry);
package_sources.add_fallbacks()?;

// If the user didn't specify any targets, then update all of the packages.
// Otherwise, find the target packages to update.
let try_to_use = if self.package_specs.is_empty() {
BTreeSet::new()
} else {
let progress = ProgressBar::new(lockfile.packages.len().try_into().unwrap())
.with_style(
ProgressStyle::with_template(
"{spinner:.cyan.bold} {pos}/{len} [{wide_bar:.cyan/blue}]",
)
.unwrap()
.tick_chars("⠁⠈⠐⠠⠄⠂ ")
.progress_chars("#>-"),
)
.with_message(format!(
"{} Selecting {}packages ...",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

let try_to_use: BTreeSet<PackageId> = lockfile
.as_ids()
// We update the target packages by removing the package from the list of packages to try to keep.
.filter(|package_id| {
if self.given_package_id_satisifies_targets(package_id) {
progress.println(format!(
"{} Selected {}{}",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset),
&package_id
));
false
} else {
progress.tick();
u-train marked this conversation as resolved.
Show resolved Hide resolved
true
}
})
.collect();

progress.finish_and_clear();

println!(
"{} Selected {}{} dependencies to try update",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset),
lockfile.packages.len() - try_to_use.len(),
);

try_to_use
};

let progress = ProgressBar::new(0)
.with_style(
ProgressStyle::with_template("{spinner:.cyan}{wide_msg}")?.tick_chars("⠁⠈⠐⠠⠄⠂ "),
)
.with_message(format!(
"{} Resolving {}new dependencies...",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

let resolved_graph = resolution::resolve(&manifest, &try_to_use, &package_sources)?;

progress.println(format!(
"{} Resolved {}{} total dependencies",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset),
resolved_graph.activated.len() - 1
));

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);
u-train marked this conversation as resolved.
Show resolved Hide resolved
});

Lockfile::from_resolve(&resolved_graph).save(&self.project_path)?;

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

let root_package_id = manifest.package_id();
let installation_context = InstallationContext::new(
&self.project_path,
manifest.place.shared_packages,
manifest.place.server_packages,
);

progress.set_message(format!(
"{} Cleaning {}package destination...",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

installation_context.clean()?;

progress.println(format!(
"{} Cleaned {}package destination",
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

progress.finish_with_message(format!(
"{}{} Starting installation {}",
SetAttribute(Attribute::Bold),
SetForegroundColor(Color::DarkGreen),
SetForegroundColor(Color::Reset)
));

installation_context.install(package_sources, root_package_id, resolved_graph)?;

Ok(())
}

fn given_package_id_satisifies_targets(&self, package_id: &PackageId) -> bool {
self.package_specs
.iter()
.any(|target_package| match target_package {
PackageSpec::Named(named_target) => package_id.name() == named_target,
PackageSpec::Required(required_target) => {
required_target.matches(package_id.name(), package_id.version())
}
})
}
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub enum PackageSpec {
Named(PackageName),
Required(PackageReq),
}

impl FromStr for PackageSpec {
type Err = anyhow::Error;

fn from_str(value: &str) -> anyhow::Result<Self> {
if let Ok(package_req) = value.parse() {
Ok(PackageSpec::Required(package_req))
} else if let Ok(package_name) = value.parse() {
Ok(PackageSpec::Named(package_name))
} else {
anyhow::bail!(
"Was unable to parse {} into a package requirement or a package name!",
value
)
}
}
}

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()
),
}
}
}
9 changes: 9 additions & 0 deletions src/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ impl Lockfile {

Ok(())
}

pub fn as_ids(&self) -> impl Iterator<Item = PackageId> + '_ {
self.packages.iter().map(|lock_package| match lock_package {
LockPackage::Registry(lock_package) => {
PackageId::new(lock_package.name.clone(), lock_package.version.clone())
}
LockPackage::Git(_) => todo!(),
})
}
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "direct-dependency-a",
"tree": {
"Packages": {
"$path": "Packages"
},
"$path": "src"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local Indirect = require(script.Parent.Indirect)

return if Indirect == 420 then "Bingo!" else "Naw..."
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "diamond-graph/direct-dependency-a"
version = "0.1.0"
license = "MIT"
realm = "server"
registry = "test-registries/primary-registry"

[server-dependencies]
Indirect = "diamond-graph/[email protected]"
Loading