Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zepter"
version = "1.2.0"
version = "1.3.0"
edition = "2021"
authors = [ "Oliver Tale-Yazdi" ]
description = "Analyze, Fix and Format features in your Rust workspace."
Expand Down Expand Up @@ -28,6 +28,7 @@ env_logger = { version = "0.11.1", features = [ "auto-color", "humantime" ], opt
histo = { version = "1.0.0", optional = true }
itertools = "0.12.1"
log = { version = "0.4.20", optional = true }
regex = "1.10.3"
semver = "1"
serde = "1.0.196"
serde_json = { version = "1.0.113", optional = true }
Expand Down
10 changes: 6 additions & 4 deletions src/autofix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ pub struct AutoFixer {
}

impl AutoFixer {
pub fn from_manifest(manifest: &Path) -> Result<Self, String> {
let raw = std::fs::read_to_string(manifest)
pub fn from_manifest<P: AsRef<Path>>(manifest: P) -> Result<Self, String> {
let raw = std::fs::read_to_string(&manifest)
.map_err(|e| format!("Failed to read manifest: {e}"))?;
let doc = raw.parse::<Document>().map_err(|e| format!("Failed to parse manifest: {e}"))?;
Ok(Self { raw, manifest: Some(manifest.to_path_buf()), doc: Some(doc) })
Ok(Self { raw, manifest: Some(manifest.as_ref().to_path_buf()), doc: Some(doc) })
}

pub fn from_raw(raw: &str) -> Result<Self, String> {
Expand Down Expand Up @@ -567,7 +567,9 @@ impl AutoFixer {
}

t.insert("version", Value::String(Formatted::new(version_str)));
t.insert("default-features", Value::Boolean(Formatted::new(default_feats)));
if !default_feats {
t.insert("default-features", Value::Boolean(Formatted::new(default_feats)));
}

deps.insert(dep_name, Item::Value(Value::InlineTable(t)));

Expand Down
19 changes: 18 additions & 1 deletion src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ pub mod run;
pub mod trace;
pub mod transpose;

use crate::log;
use crate::{log, ErrToStr};

use cargo_metadata::{Dependency, Metadata, MetadataCommand, Package, Resolve};
use std::{fs::canonicalize, path::Path};

/// See out how Rust dependencies and features are enabled.
#[derive(Debug, clap::Parser)]
Expand Down Expand Up @@ -363,3 +364,19 @@ where
let pos = s.find(':').ok_or_else(|| format!("invalid KEY=value: no `:` found in `{s}`"))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}

pub(crate) fn check_can_modify<P: AsRef<Path>>(root: P, modify: P) -> Result<bool, String> {
let root = canonicalize(root).err_to_str()?;
let modify = canonicalize(modify).err_to_str()?;

if !modify.starts_with(&root) {
format!(
"Path is outside of the workspace: {:?} (not in {:?})",
modify.display(),
root.display()
);
Ok(false)
} else {
Ok(true)
}
}
130 changes: 93 additions & 37 deletions src/cmd/transpose/lift_to_workspace.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: Oliver Tale-Yazdi <[email protected]>

use crate::log;
use crate::{
cmd::{
transpose::{canonicalize, AutoFixer, Dep, Op, Version, VersionReq},
check_can_modify,
transpose::{AutoFixer, Dep, Op, Version, VersionReq},
CargoArgs, GlobalArgs,
},
grammar::{plural, plural_or},
log, ErrToStr,
};

use cargo_metadata::Package;
use itertools::Itertools;
use std::collections::{BTreeMap as Map, HashMap};
use std::collections::{BTreeMap as Map, BTreeSet, HashMap};

/// Lift up a dependency to the workspace and reference it from all packages.
#[derive(Debug, clap::Parser)]
Expand All @@ -34,6 +36,10 @@ pub struct LiftToWorkspaceCmd {
/// The exact version to use for the whole workspace.
#[clap(long)]
exact_version: Option<String>,

/// Ignore errors and continue with the next dependency.
#[clap(long)]
ignore_errors: bool,
}

/// How to determine which version to use for the whole workspace.
Expand All @@ -45,8 +51,8 @@ pub enum VersionResolveMode {
Exact,
/// The latest version that was seen in the workspace.
///
/// This is *not* the latest version from crates-io.
Latest,
/// The highest version number that it found.
Highest,
}

impl LiftToWorkspaceCmd {
Expand All @@ -57,8 +63,34 @@ impl LiftToWorkspaceCmd {
let meta = self.cargo_args.clone().with_workspace(true).load_metadata()?;
let mut fixers = Map::new();

for dep in &self.dependencies {
self.run_for_dependency(g, &meta, dep, &mut fixers)?;
// TODO optimize to not be O^3
let mut dependencies = BTreeSet::<&str>::new();
let mut regex_lookup = Map::new();
for filter in self.dependencies.iter() {
if let Some(regex) = filter.strip_prefix("regex:") {
regex_lookup.insert(regex, regex::Regex::new(regex).err_to_str()?);
}
}

for pkg in meta.packages.iter() {
for dep in pkg.dependencies.iter() {
if regex_lookup.values().any(|r| r.is_match(&dep.name)) ||
self.dependencies.contains(&dep.name)
{
dependencies.insert(&dep.name);
}
}
}

log::info!("Scanning for {} dependencies in the workspace.", dependencies.len());
for dep in &dependencies {
match self.run_for_dependency(g, &meta, dep, &mut fixers) {
Ok(()) => (),
Err(e) if self.ignore_errors => {
log::error!("Failed to lift up '{}': {}", dep, e);
},
Err(e) => return Err(format!("Failed to lift up '{}': {}", dep, e)),
}
}

self.try_apply_changes(&mut fixers)
Expand Down Expand Up @@ -96,6 +128,7 @@ impl LiftToWorkspaceCmd {
"Held back modifications to {modified} file{s}. Re-run with --fix to apply."
))
} else {
log::info!("Modified {} manifest{}.", modified, plural(modified));
Ok(())
}
}
Expand All @@ -104,52 +137,66 @@ impl LiftToWorkspaceCmd {
&self,
g: &GlobalArgs,
meta: &cargo_metadata::Metadata,
dep: &str,
name: &str,
fixers: &mut Map<String, (Option<Package>, AutoFixer)>,
) -> Result<(), String> {
let by_version = Self::build_version_index(meta, dep);
let by_version = Self::build_version_index(meta, name);
let versions = by_version.keys().collect::<Vec<_>>();
let best_version = self.find_best_version(g, dep, &versions, &by_version)?;
let best_version = self.find_best_version(g, name, &versions, &by_version)?;

log::info!(
"Selected '{} {}'", //: N={}, D={}, B={})",
dep,
&best_version,
);
let mut all_use_default_features = true;
for (pkg, dep) in by_version.values().flatten() {
if !check_can_modify(&meta.workspace_root, &pkg.manifest_path)? {
continue
}

all_use_default_features &= dep.uses_default_features;
}

// We default in the workspace to enabling them if all packages use them but otherwise turn
// them off.
let workspace_default_features_enabled = all_use_default_features;

for (pkg, dep) in by_version.values().flatten() {
let krate_path = canonicalize(pkg.manifest_path.clone().into_std_path_buf()).unwrap();
let allowed_dir = canonicalize(meta.workspace_root.as_std_path()).unwrap();

if !krate_path.starts_with(&allowed_dir) {
log::info!(
"Skipping path outside of the workspace: {:?} (not in {:?})",
krate_path.display(),
allowed_dir.display()
);
if !check_can_modify(&meta.workspace_root, &pkg.manifest_path)? {
continue
}

fixers.entry(pkg.name.clone()).or_insert_with(|| {
(Some(pkg.clone()), AutoFixer::from_manifest(&krate_path).unwrap())
(Some(pkg.clone()), AutoFixer::from_manifest(&pkg.manifest_path).unwrap())
});
let (_, fixer) = fixers.get_mut(&pkg.name).unwrap();

let default_feats = dep.uses_default_features.then_some(true);
fixer.lift_dependency(&dep.name, default_feats)?;
if dep.uses_default_features != workspace_default_features_enabled {
fixer.lift_dependency(&dep.name, Some(dep.uses_default_features))?;
} else {
fixer.lift_dependency(&dep.name, None)?;
}
}

// Now create fixer for the root package
let root_manifest_path = meta.workspace_root.join("Cargo.toml");
fixers.entry("workspace".to_string()).or_insert_with(|| {
(None, AutoFixer::from_manifest(&root_manifest_path.into_std_path_buf()).unwrap())
});
let (_, workspace_fixer) = fixers.get_mut("workspace").unwrap();
fixers
.entry("magic:workspace".to_string())
.or_insert_with(|| (None, AutoFixer::from_manifest(&root_manifest_path).unwrap()));
let (_, workspace_fixer) = fixers.get_mut("magic:workspace").unwrap();

let mut dep = by_version.values().next().unwrap().first().unwrap().1.clone();
dep.req = best_version.parse().unwrap();
// We always add `default-features = false` into the workspace:
workspace_fixer.add_workspace_dep(&dep, false)?;

workspace_fixer.add_workspace_dep(&dep, workspace_default_features_enabled)?;

#[cfg(feature = "logging")]
{
let total_changes = by_version.values().map(|v| v.len()).sum::<usize>();
let v = format!("{} {}", name, &best_version);
log::info!(
"Selected {} for lift up in {} crate{}.",
g.bold(&v),
total_changes,
plural(total_changes)
);
}

Ok(())
}
Expand Down Expand Up @@ -181,7 +228,7 @@ impl LiftToWorkspaceCmd {
) -> Result<String, String> {
let found = match self.version_resolver {
VersionResolveMode::Exact => self.exact_version.clone().expect("Checked by clippy"),
VersionResolveMode::Latest => try_find_latest(by_version.keys())?.to_string(),
VersionResolveMode::Highest => try_find_latest(by_version.keys())?,
VersionResolveMode::Unambiguous => {
if versions.len() > 1 {
let str_width = versions.iter().map(|v| v.to_string().len()).max().unwrap();
Expand All @@ -208,7 +255,7 @@ impl LiftToWorkspaceCmd {
}

let version_hint = match try_find_latest(by_version.keys()) {
Ok(latest) => latest.to_string(),
Ok(latest) => latest,
Err(_e) => {
log::warn!("Could not find determine latest common version: {}", _e);
"version".to_string()
Expand All @@ -231,7 +278,16 @@ impl LiftToWorkspaceCmd {
}
}

fn try_find_latest<'a, I: Iterator<Item = &'a VersionReq>>(reqs: I) -> Result<Version, String> {
fn try_find_latest<'a, I: Iterator<Item = &'a VersionReq>>(reqs: I) -> Result<String, String> {
let reqs = reqs.collect::<Vec<_>>();

if reqs.is_empty() {
return Err("No versions found".to_string())
}
if reqs.iter().all(|r| r == &reqs[0]) {
return Ok(reqs[0].to_string())
}

let mut versions = Vec::<Version>::new();

// Try to convert each to a version. This is done as best-effort:
Expand All @@ -257,5 +313,5 @@ fn try_find_latest<'a, I: Iterator<Item = &'a VersionReq>>(reqs: I) -> Result<Ve
}

let latest = versions.iter().max().ok_or_else(|| "No versions found".to_string())?;
Ok(latest.clone())
Ok(latest.clone().to_string())
}
16 changes: 8 additions & 8 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,7 @@ fn deduplicate_feature_works(#[case] input: &str, #[case] modify: Result<Option<
r#"[workspace]

[workspace.dependencies]
log = { version = "0.4.20", default-features = true }
log = { version = "0.4.20" }
"#
))
)]
Expand All @@ -1178,14 +1178,14 @@ log = { version = "0.4.20", default-features = false }
r#"[workspace]

[workspace.dependencies]
log = { version = "0.4.20", default-features = true }
log = { version = "0.4.20" }
"#,
true,
Ok(Some(
r#"[workspace]

[workspace.dependencies]
log = { version = "0.4.20", default-features = true }
log = { version = "0.4.20" }
"#
))
)]
Expand All @@ -1200,7 +1200,7 @@ log = { default-features = true }
r#"[workspace]

[workspace.dependencies]
log = { default-features = true, version = "0.4.20" }
log = { default-features = true , version = "0.4.20" }
"#
))
)]
Expand All @@ -1215,7 +1215,7 @@ log = { version = "0.4.20" }
r#"[workspace]

[workspace.dependencies]
log = { version = "0.4.20", default-features = true }
log = { version = "0.4.20" }
"#
))
)]
Expand Down Expand Up @@ -1246,7 +1246,7 @@ log = { random = "321", version = "0.4.20", hey = true, git = "123" }
r#"[workspace]

[workspace.dependencies]
log = { random = "321", version = "0.4.20", hey = true, git = "123" , default-features = true }
log = { random = "321", version = "0.4.20", hey = true, git = "123" }
"#
))
)]
Expand All @@ -1261,7 +1261,7 @@ log = { random = "321", hey = true, git = "123" }
r#"[workspace]

[workspace.dependencies]
log = { random = "321", hey = true, git = "123" , version = "0.4.20", default-features = true }
log = { random = "321", hey = true, git = "123" , version = "0.4.20" }
"#
))
)]
Expand Down Expand Up @@ -1291,7 +1291,7 @@ fn inject_workspace_dep_works(
match output {
Ok(modify) => {
res.unwrap();
pretty_assertions::assert_str_eq!(fixer.to_string(), modify.unwrap_or(input));
pretty_assertions::assert_str_eq!(modify.unwrap_or(input), fixer.to_string());
},
Err(modify) => {
assert_eq!(res, Err(modify.into()));
Expand Down
Loading