Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,19 @@
- Support has been added for using Tangled as a repository.
([Naomi Roberts](https://github.com/naomieow))

- The build tool now has a new `gleam deps outdated` command that shows outdated
versions for dependencies. For example:

```bash
$ gleam deps outdated
Package Current Latest
wibble v1.4.0 v1.4.1
wobble v1.0.1 v2.3.0
```

([Vladislav Shakitskiy](https://github.com/vshakitskiy))


### Language server

- The language server now offers a code action to remove all the unreachable
Expand Down
114 changes: 101 additions & 13 deletions compiler-cli/src/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod dependency_manager;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
io::Write,
process::Command,
rc::Rc,
time::Instant,
Expand Down Expand Up @@ -110,7 +111,7 @@ fn get_manifest_details(paths: &ProjectPaths) -> Result<(PackageConfig, Manifest
Ok((config, manifest))
}

fn list_manifest_packages<W: std::io::Write>(mut buffer: W, manifest: Manifest) -> Result<()> {
fn list_manifest_packages<W: Write>(mut buffer: W, manifest: Manifest) -> Result<()> {
manifest
.packages
.into_iter()
Expand All @@ -121,7 +122,7 @@ fn list_manifest_packages<W: std::io::Write>(mut buffer: W, manifest: Manifest)
})
}

fn list_package_and_dependencies_tree<W: std::io::Write>(
fn list_package_and_dependencies_tree<W: Write>(
mut buffer: W,
options: TreeOptions,
packages: Vec<ManifestPackage>,
Expand Down Expand Up @@ -214,6 +215,21 @@ fn list_dependencies_tree(
tree
}

pub fn outdated(paths: &ProjectPaths) -> Result<()> {
let (_, manifest) = get_manifest_details(paths)?;

let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
let package_fetcher = PackageFetcher::new(runtime.handle().clone());

let version_updates = dependency::check_for_version_updates(&manifest, &package_fetcher);

if !version_updates.is_empty() {
print!("{}", pretty_print_version_updates(version_updates));
}

Ok(())
}

#[derive(Debug, Clone, Copy)]
pub enum UseManifest {
Yes,
Expand Down Expand Up @@ -392,26 +408,37 @@ pub fn resolve_and_download<Telem: Telemetry>(
dependency_manager.resolve_and_download_versions(paths, new_package, packages_to_update)
}

fn pretty_print_major_versions_available(versions: dependency::PackageVersionDiffs) -> String {
let total_lines = versions.len() + 3;
fn format_versions_and_extract_longest_parts(
versions: dependency::PackageVersionDiffs,
) -> (
impl Iterator<Item = (String, String, String)>,
(usize, usize, usize),
) {
let versions = versions
.iter()
.map(|(name, (v1, v2))| (name, v1.to_string(), v2.to_string()))
.map(|(name, (v1, v2))| (name.to_string(), v1.to_string(), v2.to_string()))
.sorted();

let longest_parts = versions.clone().fold(
(0, 0, 0),
|(max_name, max_curr, max_major), (name, curr, major)| {
|(max_name, max_current, max_latest), (name, current, latest)| {
(
max_name.max(name.len()),
max_curr.max(curr.len()),
max_major.max(major.len()),
max_current.max(current.len()),
max_latest.max(latest.len()),
)
},
);

let (longest_package_name_length, longest_current_version_length, longest_major_version_length) =
longest_parts;
(versions, longest_parts)
}

fn pretty_print_major_versions_available(versions: dependency::PackageVersionDiffs) -> String {
let total_lines = versions.len() + 3;
let (
versions,
(longest_package_name_length, longest_current_version_length, longest_major_version_length),
) = format_versions_and_extract_longest_parts(versions);

let mut output_string = String::with_capacity(
(longest_package_name_length
Expand All @@ -424,15 +451,16 @@ fn pretty_print_major_versions_available(versions: dependency::PackageVersionDif
output_string.push_str("\nThe following dependencies have new major versions available:\n\n");
for (name, v1, v2) in versions {
let name_padding = " ".repeat(longest_package_name_length - name.len());
let curr_ver_padding = " ".repeat(longest_current_version_length - v1.to_string().len());
let current_version_padding =
" ".repeat(longest_current_version_length - v1.to_string().len());

output_string.push_str(
&[
name,
&name,
&name_padding,
" ",
&v1.to_string(),
&curr_ver_padding,
&current_version_padding,
" -> ",
&v2.to_string(),
"\n",
Expand All @@ -444,6 +472,66 @@ fn pretty_print_major_versions_available(versions: dependency::PackageVersionDif
output_string
}

fn pretty_print_version_updates(versions: dependency::PackageVersionDiffs) -> String {
let total_lines = versions.len() + 1;
let (
versions,
(
longest_package_name_length,
longest_current_version_length,
longest_latest_version_length,
),
) = format_versions_and_extract_longest_parts(versions);

let longest_package_name_length = longest_package_name_length.max(7);
let longest_current_version_length = (longest_current_version_length + 1).max(7);
let longest_latest_version_length = (longest_latest_version_length + 1).max(6);

let mut output_string = String::with_capacity(
(longest_package_name_length
+ longest_current_version_length
+ longest_latest_version_length
+ 3)
* total_lines,
);

let name_padding = " ".repeat(longest_package_name_length - 7);
let current_version_padding = " ".repeat(longest_current_version_length - 7);

output_string.push_str(
&[
"Package",
&name_padding,
" Current",
&current_version_padding,
" Latest\n",
]
.concat(),
);

for (name, v1, v2) in versions {
let name_padding = " ".repeat(longest_package_name_length - name.len());
let current_version_padding =
" ".repeat(longest_current_version_length - 1 - v1.to_string().len());

output_string.push_str(
&[
&name,
&name_padding,
" v",
&v1.to_string(),
&current_version_padding,
" v",
&v2.to_string(),
"\n",
]
.concat(),
);
}

output_string
}

async fn add_missing_packages<Telem: Telemetry>(
paths: &ProjectPaths,
fs: Box<ProjectIO>,
Expand Down
8 changes: 8 additions & 0 deletions compiler-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,9 @@ enum Dependencies {
/// Download all dependency packages
Download,

/// List all outdated dependencies
Outdated,

/// Update dependency packages to their latest versions
Update(UpdateOptions),

Expand Down Expand Up @@ -579,6 +582,11 @@ fn parse_and_run_command() -> Result<(), Error> {
download_dependencies(&paths)
}

Command::Deps(Dependencies::Outdated) => {
let paths = find_project_paths()?;
dependencies::outdated(&paths)
}

Command::Deps(Dependencies::Update(options)) => {
let paths = find_project_paths()?;
dependencies::update(&paths, options.packages)
Expand Down
47 changes: 38 additions & 9 deletions compiler-core/src/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,26 +65,36 @@ where
*/
pub type PackageVersionDiffs = HashMap<String, (Version, Version)>;

fn resolve_major_versions(
fn resolve_versions_diffs(
package_fetcher: &impl PackageFetcher,
versions: PackageVersions,
check_major_versions: bool,
) -> PackageVersionDiffs {
versions
.iter()
.filter_map(|(package, version)| {
let Ok(hexpackage) = package_fetcher.get_dependencies(package) else {
let Ok(hex_package) = package_fetcher.get_dependencies(package) else {
return None;
};

let latest = hexpackage
let latest = hex_package
.releases
.iter()
.map(|release| &release.version)
.filter(|version| !version.is_pre())
.max()?;

if latest.major <= version.major {
return None;
match check_major_versions {
true => {
if latest.major <= version.major {
return None;
}
}
false => {
if latest <= version {
return None;
}
}
}

Some((package.to_string(), (version.clone(), latest.clone())))
Expand All @@ -98,9 +108,7 @@ pub fn check_for_major_version_updates(
manifest: &manifest::Manifest,
package_fetcher: &impl PackageFetcher,
) -> PackageVersionDiffs {
// get the resolved versions of the direct dependencies to check for major
// version updates.
let versions = manifest
let versions: PackageVersions = manifest
.packages
.iter()
.filter(|manifest_package| {
Expand All @@ -112,7 +120,28 @@ pub fn check_for_major_version_updates(
.map(|manifest_pkg| (manifest_pkg.name.to_string(), manifest_pkg.version.clone()))
.collect();

resolve_major_versions(package_fetcher, versions)
resolve_versions_diffs(package_fetcher, versions, true)
}

/// Check for version updates for direct and transitive dependencies that are being blocked by some version
/// constraints.
pub fn check_for_version_updates(
manifest: &manifest::Manifest,
package_fetcher: &impl PackageFetcher,
) -> PackageVersionDiffs {
let versions = manifest
.packages
.iter()
.filter(|manifest_package| {
matches!(
manifest_package.source,
manifest::ManifestPackageSource::Hex { .. }
)
})
.map(|manifest_pkg| (manifest_pkg.name.to_string(), manifest_pkg.version.clone()))
.collect();

resolve_versions_diffs(package_fetcher, versions, false)
}

// If the string would parse to an exact version then return the version
Expand Down