From ae3313ea3245b52097ddf68dca98a2ad37b20837 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 22:33:04 +0100 Subject: [PATCH 01/36] wip --- src/lib.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 117 ++++--------------------------- src/query.rs | 18 ++--- 3 files changed, 219 insertions(+), 111 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..d0b7b30f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,195 @@ +mod baseline; +mod check_release; +mod config; +mod dump; +mod manifest; +mod query; +mod templating; +mod util; + +use config::GlobalConfig; +pub use dump::*; +pub use templating::*; + +use std::path::PathBuf; + +/// Test a release for semver violations. +#[derive(Default)] +pub struct Check { + /// Which packages to analyze. + scope: Scope, + current: Current, + baseline: Baseline, + log_level: Option, +} + +#[derive(Default)] +enum Baseline { + /// Version from registry to lookup for a baseline. E.g. "1.0.0". + Version(String), + /// Git revision to lookup for a baseline. + Revision(String), + /// Directory containing baseline crate source. + Root(PathBuf), + /// The rustdoc json file to use as a semver baseline. + RustDoc(PathBuf), + /// Latest version published to the cargo registry. + #[default] + LatestVersion, +} + +/// Current version of the project to analyze. +#[derive(Default)] +enum Current { + /// Path to the manifest of the current version of the project. + /// It can be a workspace or a single package. + Manifest(PathBuf), + /// The rustdoc json of the current version of the project. + RustDoc(PathBuf), + /// Use the manifest in the current directory. + #[default] + CurrentDir, +} + +/// Which packages to analyze. +#[derive(Default)] +enum Scope { + /// A subset of packages in the workspace. + Packages(Vec), + /// All packages in the workspace. + #[default] + Workspace, + /// All packages in the workspace, except these. + Exclude(Vec), +} + +impl Check { + pub fn new() -> Self { + Self::default() + } + + fn manifest_path(&self) -> anyhow::Result { + let path = match &self.current { + Current::Manifest(path) => path.clone(), + Current::RustDoc(_) => { + anyhow::bail!("error: RustDoc is not supported with these arguments.") + } + Current::CurrentDir => PathBuf::from("Cargo.toml"), + }; + Ok(path) + } + + fn manifest_metadata_no_deps(&self) -> anyhow::Result { + let mut command = cargo_metadata::MetadataCommand::new(); + let metadata = command + .manifest_path(self.manifest_path()?) + .no_deps() + .exec()?; + Ok(metadata) + } + + pub fn check_release(&self) -> anyhow::Result { + let mut config = GlobalConfig::new().set_level(self.log_level); + + let loader: Box = match self.baseline { + Baseline::Version(version) => { + let mut registry = self.registry_baseline(&mut config)?; + let version = semver::Version::parse(&version)?; + registry.set_version(version); + Box::new(registry) + } + Baseline::Revision(rev) => { + let metadata = self.manifest_metadata_no_deps()?; + let source = metadata.workspace_root.as_std_path(); + let slug = util::slugify(&rev); + let target = metadata + .target_directory + .as_std_path() + .join(util::SCOPE) + .join(format!("git-{slug}")); + Box::new(baseline::GitBaseline::with_rev( + source, + &target, + &rev, + &mut config, + )?) + } + Baseline::Root(root) => Box::new(baseline::PathBaseline::new(&root)?), + Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), + Baseline::LatestVersion => { + let metadata = self.manifest_metadata_no_deps()?; + let target = metadata.target_directory.as_std_path().join(util::SCOPE); + let mut registry = baseline::RegistryBaseline::new(&target, &mut config)?; + Box::new(registry) + } + }; + let rustdoc_cmd = dump::RustDocCommand::new() + .deps(false) + .silence(!config.is_verbose()); + + let rustdoc_paths = match self.current { + Current::Manifest(_) => todo!(), + Current::RustDoc(rustdoc_path) => { + + let name = ""; + let version = None; + vec![( + name.to_owned(), + loader.load_rustdoc(&mut config, &rustdoc_cmd, name, version)?, + rustdoc_path.to_owned(), + )] + } + Current::CurrentDir => todo!(), + } + let rustdoc_paths = + { + let metadata = args.manifest.metadata().exec()?; + let (selected, _) = args.workspace.partition_packages(&metadata); + let mut rustdoc_paths = Vec::with_capacity(selected.len()); + for selected in selected { + let manifest_path = selected.manifest_path.as_std_path(); + let crate_name = &selected.name; + let version = &selected.version; + + let is_implied = args.workspace.all || args.workspace.workspace; + if is_implied && selected.publish == Some(vec![]) { + config.verbose(|config| { + config.shell_status( + "Skipping", + format_args!("{crate_name} v{version} (current)"), + ) + })?; + continue; + } + + config + .shell_status("Parsing", format_args!("{crate_name} v{version} (current)"))?; + let rustdoc_path = rustdoc_cmd.dump(manifest_path, None, true)?; + let baseline_path = + loader.load_rustdoc(&mut config, &rustdoc_cmd, crate_name, Some(version))?; + rustdoc_paths.push((crate_name.clone(), baseline_path, rustdoc_path)); + } + rustdoc_paths + }; + let mut success = true; + for (crate_name, baseline_path, current_path) in rustdoc_paths { + let baseline_crate = load_rustdoc(&baseline_path)?; + let current_crate = load_rustdoc(¤t_path)?; + + if !run_check_release(&mut config, &crate_name, current_crate, baseline_crate)? { + success = false; + } + } + + Ok(Report {}) + } + + fn registry_baseline(&self, config: &mut GlobalConfig) -> Result { + let metadata = self.manifest_metadata_no_deps()?; + let target = metadata.target_directory.as_std_path().join(util::SCOPE); + let mut registry = baseline::RegistryBaseline::new(&target, config)?; + Ok(registry) + } +} + +pub struct Report {} diff --git a/src/main.rs b/src/main.rs index d5f0100b..a425b483 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,12 @@ #![forbid(unsafe_code)] -mod baseline; -mod check_release; -mod config; -mod dump; -mod manifest; -mod query; -mod templating; -mod util; - use std::path::PathBuf; +use cargo_semver_checks::{baseline, dump, query}; use clap::{Args, Parser, Subcommand}; use trustfall_rustdoc::load_rustdoc; -use crate::{check_release::run_check_release, config::GlobalConfig, util::slugify}; +use cargo_semver_checks::{check_release::run_check_release, config::GlobalConfig, util::slugify}; fn main() -> anyhow::Result<()> { human_panic::setup_panic!(); @@ -86,99 +78,14 @@ fn main() -> anyhow::Result<()> { match args.command { Some(SemverChecksCommands::CheckRelease(args)) => { - let mut config = GlobalConfig::new().set_level(args.verbosity.log_level()); - - let loader: Box = - if let Some(path) = args.baseline_rustdoc.as_deref() { - Box::new(baseline::RustdocBaseline::new(path.to_owned())) - } else if let Some(root) = args.baseline_root.as_deref() { - Box::new(baseline::PathBaseline::new(root)?) - } else if let Some(rev) = args.baseline_rev.as_deref() { - let metadata = args.manifest.metadata().no_deps().exec()?; - let source = metadata.workspace_root.as_std_path(); - let slug = slugify(rev); - let target = metadata - .target_directory - .as_std_path() - .join(util::SCOPE) - .join(format!("git-{slug}")); - Box::new(baseline::GitBaseline::with_rev( - source, - &target, - rev, - &mut config, - )?) - } else { - let metadata = args.manifest.metadata().no_deps().exec()?; - let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let mut registry = baseline::RegistryBaseline::new(&target, &mut config)?; - if let Some(version) = args.baseline_version.as_deref() { - let version = semver::Version::parse(version)?; - registry.set_version(version); - } - Box::new(registry) - }; - let rustdoc_cmd = dump::RustDocCommand::new() - .deps(false) - .silence(!config.is_verbose()); - - let rustdoc_paths = if let Some(current_rustdoc_path) = args.current_rustdoc.as_deref() - { - let name = ""; - let version = None; - vec![( - name.to_owned(), - loader.load_rustdoc(&mut config, &rustdoc_cmd, name, version)?, - current_rustdoc_path.to_owned(), - )] - } else { - let metadata = args.manifest.metadata().exec()?; - let (selected, _) = args.workspace.partition_packages(&metadata); - let mut rustdoc_paths = Vec::with_capacity(selected.len()); - for selected in selected { - let manifest_path = selected.manifest_path.as_std_path(); - let crate_name = &selected.name; - let version = &selected.version; - - let is_implied = args.workspace.all || args.workspace.workspace; - if is_implied && selected.publish == Some(vec![]) { - config.verbose(|config| { - config.shell_status( - "Skipping", - format_args!("{crate_name} v{version} (current)"), - ) - })?; - continue; - } - - config.shell_status( - "Parsing", - format_args!("{crate_name} v{version} (current)"), - )?; - let rustdoc_path = rustdoc_cmd.dump(manifest_path, None, true)?; - let baseline_path = loader.load_rustdoc( - &mut config, - &rustdoc_cmd, - crate_name, - Some(version), - )?; - rustdoc_paths.push((crate_name.clone(), baseline_path, rustdoc_path)); + let check: cargo_semver_checks::Check = args.into(); + match check.check_release() { + Ok(()) => std::process::exit(0), + Err(err) => { + let mut config = GlobalConfig::new().set_level(args.verbosity.log_level()); + config.shell_error(&err)?; + std::process::exit(1); } - rustdoc_paths - }; - let mut success = true; - for (crate_name, baseline_path, current_path) in rustdoc_paths { - let baseline_crate = load_rustdoc(&baseline_path)?; - let current_crate = load_rustdoc(¤t_path)?; - - if !run_check_release(&mut config, &crate_name, current_crate, baseline_crate)? { - success = false; - } - } - if success { - std::process::exit(0); - } else { - std::process::exit(1); } } None => { @@ -282,6 +189,12 @@ struct CheckRelease { verbosity: clap_verbosity_flag::Verbosity, } +impl From for cargo_semver_checks::Check { + fn from(value: CheckRelease) -> Self { + Self::default() + } +} + #[test] fn verify_cli() { use clap::CommandFactory; diff --git a/src/query.rs b/src/query.rs index 6153518c..ce5f0f4e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -4,13 +4,13 @@ use serde::{Deserialize, Serialize}; use trustfall_core::ir::TransparentValue; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) enum RequiredSemverUpdate { +pub enum RequiredSemverUpdate { Major, Minor, } impl RequiredSemverUpdate { - pub(crate) fn as_str(&self) -> &'static str { + pub fn as_str(&self) -> &'static str { match self { Self::Major => "major", Self::Minor => "minor", @@ -40,20 +40,20 @@ impl ActualSemverUpdate { /// A query that can be executed on a pair of rustdoc output files, /// returning instances of a particular kind of semver violation. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct SemverQuery { - pub(crate) id: String, +pub struct SemverQuery { + pub id: String, pub(crate) human_readable_name: String, - pub(crate) description: String, + pub description: String, - pub(crate) required_update: RequiredSemverUpdate, + pub required_update: RequiredSemverUpdate, #[serde(default)] - pub(crate) reference: Option, + pub reference: Option, #[serde(default)] - pub(crate) reference_link: Option, + pub reference_link: Option, pub(crate) query: String, @@ -72,7 +72,7 @@ pub(crate) struct SemverQuery { } impl SemverQuery { - pub(crate) fn all_queries() -> BTreeMap { + pub fn all_queries() -> BTreeMap { let mut queries = BTreeMap::default(); for query_text in get_query_text_contents() { let query: SemverQuery = ron::from_str(query_text).unwrap_or_else(|e| { From d3dc47be1a9263b6838312f94bc39a996f85996b Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 23:22:24 +0100 Subject: [PATCH 02/36] wip --- src/config.rs | 2 +- src/lib.rs | 179 ++++++++++++++++++++++++++++++++++---------------- src/main.rs | 22 +++---- 3 files changed, 132 insertions(+), 71 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6cb7de6a..0574c788 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use termcolor::{ColorChoice, StandardStream}; use crate::templating::make_handlebars_registry; #[allow(dead_code)] -pub(crate) struct GlobalConfig { +pub struct GlobalConfig { level: Option, is_stderr_tty: bool, stdout: StandardStream, diff --git a/src/lib.rs b/src/lib.rs index d0b7b30f..871da841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,11 +7,13 @@ mod query; mod templating; mod util; -use config::GlobalConfig; -pub use dump::*; -pub use templating::*; +pub use query::*; +pub use config::*; -use std::path::PathBuf; +use check_release::run_check_release; +use trustfall_rustdoc::load_rustdoc; + +use std::{collections::HashSet, path::PathBuf}; /// Test a release for semver violations. #[derive(Default)] @@ -51,16 +53,60 @@ enum Current { CurrentDir, } -/// Which packages to analyze. #[derive(Default)] -enum Scope { - /// A subset of packages in the workspace. +struct Scope { + selection: ScopeSelection, + excluded_packages: Vec, +} + +/// Which packages to analyze. +#[derive(Default, PartialEq, Eq)] +enum ScopeSelection { + /// Package to process (see `cargo help pkgid`) Packages(Vec), - /// All packages in the workspace. - #[default] + /// All packages in the workspace. Equivalent to `--workspace`. Workspace, - /// All packages in the workspace, except these. - Exclude(Vec), + /// Default members of the workspace. + #[default] + DefaultMembers, +} + +impl Scope { + fn selected_packages<'m>( + &self, + meta: &'m cargo_metadata::Metadata, + ) -> Vec<&'m cargo_metadata::Package> { + let workspace_members: HashSet<_> = meta.workspace_members.iter().collect(); + let base_ids: HashSet<_> = match &self.selection { + ScopeSelection::DefaultMembers => { + // Deviating from cargo because Metadata doesn't have default members + let resolve = meta.resolve.as_ref().expect("no-deps is unsupported"); + match &resolve.root { + Some(root) => { + let mut base_ids = HashSet::new(); + base_ids.insert(root); + base_ids + } + None => workspace_members, + } + } + ScopeSelection::Workspace => workspace_members, + ScopeSelection::Packages(patterns) => { + meta.packages + .iter() + // Deviating from cargo by not supporting patterns + // Deviating from cargo by only checking workspace members + .filter(|p| workspace_members.contains(&p.id) && patterns.contains(&p.name)) + .map(|p| &p.id) + .collect() + } + }; + + meta.packages + .iter() + .filter(|p| base_ids.contains(&p.id) && !self.excluded_packages.contains(&p.name)) + .collect() + } } impl Check { @@ -79,6 +125,12 @@ impl Check { Ok(path) } + fn manifest_metadata(&self) -> anyhow::Result { + let mut command = cargo_metadata::MetadataCommand::new(); + let metadata = command.manifest_path(self.manifest_path()?).exec()?; + Ok(metadata) + } + fn manifest_metadata_no_deps(&self) -> anyhow::Result { let mut command = cargo_metadata::MetadataCommand::new(); let metadata = command @@ -91,7 +143,7 @@ impl Check { pub fn check_release(&self) -> anyhow::Result { let mut config = GlobalConfig::new().set_level(self.log_level); - let loader: Box = match self.baseline { + let loader: Box = match &self.baseline { Baseline::Version(version) => { let mut registry = self.registry_baseline(&mut config)?; let version = semver::Version::parse(&version)?; @@ -119,7 +171,7 @@ impl Check { Baseline::LatestVersion => { let metadata = self.manifest_metadata_no_deps()?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let mut registry = baseline::RegistryBaseline::new(&target, &mut config)?; + let registry = baseline::RegistryBaseline::new(&target, &mut config)?; Box::new(registry) } }; @@ -127,49 +179,51 @@ impl Check { .deps(false) .silence(!config.is_verbose()); - let rustdoc_paths = match self.current { - Current::Manifest(_) => todo!(), - Current::RustDoc(rustdoc_path) => { - - let name = ""; - let version = None; - vec![( - name.to_owned(), - loader.load_rustdoc(&mut config, &rustdoc_cmd, name, version)?, - rustdoc_path.to_owned(), - )] - } - Current::CurrentDir => todo!(), + let rustdoc_paths = match &self.current { + Current::RustDoc(rustdoc_path) => { + let name = ""; + let version = None; + vec![( + name.to_owned(), + loader.load_rustdoc(&mut config, &rustdoc_cmd, name, version)?, + rustdoc_path.to_owned(), + )] } - let rustdoc_paths = - { - let metadata = args.manifest.metadata().exec()?; - let (selected, _) = args.workspace.partition_packages(&metadata); - let mut rustdoc_paths = Vec::with_capacity(selected.len()); - for selected in selected { - let manifest_path = selected.manifest_path.as_std_path(); - let crate_name = &selected.name; - let version = &selected.version; - - let is_implied = args.workspace.all || args.workspace.workspace; - if is_implied && selected.publish == Some(vec![]) { - config.verbose(|config| { - config.shell_status( - "Skipping", - format_args!("{crate_name} v{version} (current)"), - ) - })?; - continue; - } + _ => { + let metadata = self.manifest_metadata()?; + let selected = self.scope.selected_packages(&metadata); + let mut rustdoc_paths = Vec::with_capacity(selected.len()); + for selected in selected { + let manifest_path = selected.manifest_path.as_std_path(); + let crate_name = &selected.name; + let version = &selected.version; - config - .shell_status("Parsing", format_args!("{crate_name} v{version} (current)"))?; - let rustdoc_path = rustdoc_cmd.dump(manifest_path, None, true)?; - let baseline_path = - loader.load_rustdoc(&mut config, &rustdoc_cmd, crate_name, Some(version))?; - rustdoc_paths.push((crate_name.clone(), baseline_path, rustdoc_path)); + let is_implied = self.scope.selection == ScopeSelection::Workspace; + if is_implied && selected.publish == Some(vec![]) { + config.verbose(|config| { + config.shell_status( + "Skipping", + format_args!("{crate_name} v{version} (current)"), + ) + })?; + continue; + } + + config.shell_status( + "Parsing", + format_args!("{crate_name} v{version} (current)"), + )?; + let rustdoc_path = rustdoc_cmd.dump(manifest_path, None, true)?; + let baseline_path = loader.load_rustdoc( + &mut config, + &rustdoc_cmd, + crate_name, + Some(version), + )?; + rustdoc_paths.push((crate_name.clone(), baseline_path, rustdoc_path)); + } + rustdoc_paths } - rustdoc_paths }; let mut success = true; for (crate_name, baseline_path, current_path) in rustdoc_paths { @@ -181,15 +235,26 @@ impl Check { } } - Ok(Report {}) + Ok(Report { success }) } - fn registry_baseline(&self, config: &mut GlobalConfig) -> Result { + fn registry_baseline( + &self, + config: &mut GlobalConfig, + ) -> Result { let metadata = self.manifest_metadata_no_deps()?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let mut registry = baseline::RegistryBaseline::new(&target, config)?; + let registry = baseline::RegistryBaseline::new(&target, config)?; Ok(registry) } } -pub struct Report {} +pub struct Report { + success: bool, +} + +impl Report { + pub fn success(&self) -> bool { + self.success + } +} diff --git a/src/main.rs b/src/main.rs index a425b483..a42c01ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,9 @@ use std::path::PathBuf; -use cargo_semver_checks::{baseline, dump, query}; +use cargo_semver_checks::GlobalConfig; +use cargo_semver_checks::SemverQuery; use clap::{Args, Parser, Subcommand}; -use trustfall_rustdoc::load_rustdoc; - -use cargo_semver_checks::{check_release::run_check_release, config::GlobalConfig, util::slugify}; fn main() -> anyhow::Result<()> { human_panic::setup_panic!(); @@ -23,7 +21,7 @@ fn main() -> anyhow::Result<()> { .print::(); std::process::exit(0); } else if args.list { - let queries = query::SemverQuery::all_queries(); + let queries = SemverQuery::all_queries(); let mut rows = vec![["id", "type", "description"], ["==", "====", "==========="]]; for query in queries.values() { rows.push([ @@ -53,7 +51,7 @@ fn main() -> anyhow::Result<()> { config.shell_note("Use `--explain ` to see more details")?; std::process::exit(0); } else if let Some(id) = args.explain.as_deref() { - let queries = query::SemverQuery::all_queries(); + let queries = SemverQuery::all_queries(); let query = queries.get(id).ok_or_else(|| { let ids = queries.keys().cloned().collect::>(); anyhow::format_err!( @@ -79,13 +77,11 @@ fn main() -> anyhow::Result<()> { match args.command { Some(SemverChecksCommands::CheckRelease(args)) => { let check: cargo_semver_checks::Check = args.into(); - match check.check_release() { - Ok(()) => std::process::exit(0), - Err(err) => { - let mut config = GlobalConfig::new().set_level(args.verbosity.log_level()); - config.shell_error(&err)?; - std::process::exit(1); - } + let report = check.check_release()?; + if report.success() { + std::process::exit(0) + } else { + std::process::exit(1); } } None => { From edda25a69165b7b79fa074f43526f049cd59d2b7 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 23:42:54 +0100 Subject: [PATCH 03/36] wip --- src/lib.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 871da841..2db5b62b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,8 @@ mod query; mod templating; mod util; -pub use query::*; pub use config::*; +pub use query::*; use check_release::run_check_release; use trustfall_rustdoc::load_rustdoc; @@ -114,6 +114,56 @@ impl Check { Self::default() } + pub fn with_manifest(&mut self, path: PathBuf) -> &mut Self { + self.current = Current::Manifest(path); + self + } + + pub fn with_workspace(&mut self) -> &mut Self { + self.scope.selection = ScopeSelection::Workspace; + self + } + + pub fn with_packages(&mut self, packages: Vec) -> &mut Self { + self.scope.selection = ScopeSelection::Packages(packages); + self + } + + pub fn with_excluded_packages(&mut self, excluded_packages: Vec) -> &mut Self { + self.scope.excluded_packages = excluded_packages; + self + } + + pub fn with_current_rustdoc(&mut self, rustdoc: PathBuf) -> &mut Self { + self.current = Current::RustDoc(rustdoc); + self + } + + pub fn with_baseline_version(&mut self, version: String) -> &mut Self { + self.baseline = Baseline::Version(version); + self + } + + pub fn with_baseline_revision(&mut self, revision: String) -> &mut Self { + self.baseline = Baseline::Revision(revision); + self + } + + pub fn with_baseline_root(&mut self, root: PathBuf) -> &mut Self { + self.baseline = Baseline::Root(root); + self + } + + pub fn with_baseline_rustdoc(&mut self, rustdoc: PathBuf) -> &mut Self { + self.baseline = Baseline::RustDoc(rustdoc); + self + } + + pub fn with_log_level(&mut self, log_level: log::Level) -> &mut Self { + self.log_level = Some(log_level); + self + } + fn manifest_path(&self) -> anyhow::Result { let path = match &self.current { Current::Manifest(path) => path.clone(), diff --git a/src/main.rs b/src/main.rs index a42c01ea..00b44df9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -187,7 +187,37 @@ struct CheckRelease { impl From for cargo_semver_checks::Check { fn from(value: CheckRelease) -> Self { - Self::default() + let mut check = Self::default(); + if let Some(manifest) = value.manifest.manifest_path { + check.with_manifest(manifest); + } + if value.workspace.all || value.workspace.workspace { + check.with_workspace(); + } else if !value.workspace.package.is_empty() { + check.with_packages(value.workspace.package); + } + if !value.workspace.exclude.is_empty() { + check.with_excluded_packages(value.workspace.exclude); + } + if let Some(current_rustdoc) = value.current_rustdoc { + check.with_current_rustdoc(current_rustdoc); + } + if let Some(baseline_version) = value.baseline_version { + check.with_baseline_version(baseline_version); + } + if let Some(baseline_rev) = value.baseline_rev { + check.with_baseline_revision(baseline_rev); + } + if let Some(baseline_root) = value.baseline_root { + check.with_baseline_root(baseline_root); + } + if let Some(baseline_rustdoc) = value.baseline_rustdoc { + check.with_baseline_rustdoc(baseline_rustdoc); + } + if let Some(log_level) = value.verbosity.log_level() { + check.with_log_level(log_level); + } + check } } From 62286f26cede02fd9ee11995c1808f01fe15e864 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 23:53:19 +0100 Subject: [PATCH 04/36] expand match --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2db5b62b..da4ecab4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,7 +239,7 @@ impl Check { rustdoc_path.to_owned(), )] } - _ => { + Current::CurrentDir | Current::Manifest(_) => { let metadata = self.manifest_metadata()?; let selected = self.scope.selected_packages(&metadata); let mut rustdoc_paths = Vec::with_capacity(selected.len()); From 3777e0a75b9ab3ea15904d4ac84ba74efe750c98 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 23:56:54 +0100 Subject: [PATCH 05/36] clippy --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index da4ecab4..b41e0aa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,14 +196,14 @@ impl Check { let loader: Box = match &self.baseline { Baseline::Version(version) => { let mut registry = self.registry_baseline(&mut config)?; - let version = semver::Version::parse(&version)?; + let version = semver::Version::parse(version)?; registry.set_version(version); Box::new(registry) } Baseline::Revision(rev) => { let metadata = self.manifest_metadata_no_deps()?; let source = metadata.workspace_root.as_std_path(); - let slug = util::slugify(&rev); + let slug = util::slugify(rev); let target = metadata .target_directory .as_std_path() @@ -212,11 +212,11 @@ impl Check { Box::new(baseline::GitBaseline::with_rev( source, &target, - &rev, + rev, &mut config, )?) } - Baseline::Root(root) => Box::new(baseline::PathBaseline::new(&root)?), + Baseline::Root(root) => Box::new(baseline::PathBaseline::new(root)?), Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), Baseline::LatestVersion => { let metadata = self.manifest_metadata_no_deps()?; From 1d72ef7478a3698091f2527d39b93c94ae75a911 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 21 Jan 2023 23:59:22 +0100 Subject: [PATCH 06/36] clippy --- src/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config.rs b/src/config.rs index 0574c788..fde84a5e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,6 +11,12 @@ pub struct GlobalConfig { handlebars: handlebars::Handlebars<'static>, } +impl Default for GlobalConfig { + fn default() -> Self { + Self::new() + } +} + impl GlobalConfig { pub fn new() -> Self { let is_stdout_tty = atty::is(atty::Stream::Stdout); From c1ba81a34a50b147d8bd0528112cc9747bd21268 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:07:38 +0100 Subject: [PATCH 07/36] move match --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b41e0aa9..618b6b1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -194,6 +194,8 @@ impl Check { let mut config = GlobalConfig::new().set_level(self.log_level); let loader: Box = match &self.baseline { + Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), + Baseline::Root(root) => Box::new(baseline::PathBaseline::new(root)?), Baseline::Version(version) => { let mut registry = self.registry_baseline(&mut config)?; let version = semver::Version::parse(version)?; @@ -216,8 +218,6 @@ impl Check { &mut config, )?) } - Baseline::Root(root) => Box::new(baseline::PathBaseline::new(root)?), - Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), Baseline::LatestVersion => { let metadata = self.manifest_metadata_no_deps()?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); From fb3f9d29e2e5026fe68ae06af50342fb18bfd9d7 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:08:09 +0100 Subject: [PATCH 08/36] move rev --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 618b6b1a..15c214c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,12 +196,6 @@ impl Check { let loader: Box = match &self.baseline { Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), Baseline::Root(root) => Box::new(baseline::PathBaseline::new(root)?), - Baseline::Version(version) => { - let mut registry = self.registry_baseline(&mut config)?; - let version = semver::Version::parse(version)?; - registry.set_version(version); - Box::new(registry) - } Baseline::Revision(rev) => { let metadata = self.manifest_metadata_no_deps()?; let source = metadata.workspace_root.as_std_path(); @@ -218,6 +212,12 @@ impl Check { &mut config, )?) } + Baseline::Version(version) => { + let mut registry = self.registry_baseline(&mut config)?; + let version = semver::Version::parse(version)?; + registry.set_version(version); + Box::new(registry) + } Baseline::LatestVersion => { let metadata = self.manifest_metadata_no_deps()?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); From 4a495fd76a01e262382c3900394a2ba82dc3ba4a Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 22 Jan 2023 00:12:10 +0100 Subject: [PATCH 09/36] easier --- src/lib.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 15c214c6..81a551cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,19 +25,22 @@ pub struct Check { log_level: Option, } -#[derive(Default)] enum Baseline { /// Version from registry to lookup for a baseline. E.g. "1.0.0". - Version(String), + /// If `None`, it uses the version published to the cargo registry. + Version(Option), /// Git revision to lookup for a baseline. Revision(String), /// Directory containing baseline crate source. Root(PathBuf), /// The rustdoc json file to use as a semver baseline. RustDoc(PathBuf), - /// Latest version published to the cargo registry. - #[default] - LatestVersion, +} + +impl Default for Baseline { + fn default() -> Self { + Self::Version(None) + } } /// Current version of the project to analyze. @@ -140,7 +143,7 @@ impl Check { } pub fn with_baseline_version(&mut self, version: String) -> &mut Self { - self.baseline = Baseline::Version(version); + self.baseline = Baseline::Version(Some(version)); self } @@ -214,14 +217,10 @@ impl Check { } Baseline::Version(version) => { let mut registry = self.registry_baseline(&mut config)?; - let version = semver::Version::parse(version)?; - registry.set_version(version); - Box::new(registry) - } - Baseline::LatestVersion => { - let metadata = self.manifest_metadata_no_deps()?; - let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let registry = baseline::RegistryBaseline::new(&target, &mut config)?; + if let Some(ver) = version { + let semver = semver::Version::parse(ver)?; + registry.set_version(semver); + } Box::new(registry) } }; From 2ef3486639c956430d5279765a604225a7fe5170 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 22 Jan 2023 09:36:03 +0100 Subject: [PATCH 10/36] Update src/lib.rs Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 81a551cb..14e4a71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,9 @@ pub struct Check { enum Baseline { /// Version from registry to lookup for a baseline. E.g. "1.0.0". - /// If `None`, it uses the version published to the cargo registry. + /// If `None`, uses the largest-numbered non-yanked non-prerelease version + /// published to the cargo registry. If no such version, uses + /// the largest-numbered version including yanked and prerelease versions. Version(Option), /// Git revision to lookup for a baseline. Revision(String), From 94bd75fce09a9f5ba9642dcba8bc8ab5b62526bf Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Wed, 25 Jan 2023 18:25:40 +0100 Subject: [PATCH 11/36] introduce Rustdoc struct --- src/lib.rs | 150 +++++++++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 621a0b6b..3c436f63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,46 +22,74 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; /// Test a release for semver violations. -#[derive(Default)] pub struct Check { /// Which packages to analyze. scope: Scope, - current: Current, - baseline: Baseline, + current: Rustdoc, + baseline: Rustdoc, log_level: Option, } -enum Baseline { - /// Version from registry to lookup for a baseline. E.g. "1.0.0". - /// If `None`, uses the largest-numbered non-yanked non-prerelease version +pub struct Rustdoc { + source: RustdocSource, +} + +impl Rustdoc { + /// Use an existing rustdoc file. + pub fn from_path(rustdoc_path: impl Into) -> Self { + Self { + source: RustdocSource::Rustdoc(rustdoc_path.into()), + } + } + + /// Generate the rustdoc file from the project root directory, i.e. the directory containing the crate source. + /// It can be a workspace or a single package. + /// Same as `from_git_revision`, but with the current git revision. + pub fn from_root(project_root: impl Into) -> Self { + Self { + source: RustdocSource::Root(project_root.into()), + } + } + + /// Generate the rustdoc file from the project at a given git revision. + pub fn from_git_revision( + project_root: impl Into, + revision: impl Into, + ) -> Self { + Self { + source: RustdocSource::Revision(project_root.into(), revision.into()), + } + } + + /// Generate the rustdoc file from the largest-numbered non-yanked non-prerelease version /// published to the cargo registry. If no such version, uses /// the largest-numbered version including yanked and prerelease versions. - Version(Option), - /// Git revision to lookup for a baseline. - Revision(String), - /// Directory containing baseline crate source. - Root(PathBuf), - /// The rustdoc json file to use as a semver baseline. - RustDoc(PathBuf), -} + pub fn from_latest_version() -> Self { + Self { + source: RustdocSource::Version(None), + } + } -impl Default for Baseline { - fn default() -> Self { - Self::Version(None) + pub fn from_version(version: impl Into) -> Self { + Self { + source: RustdocSource::Version(Some(version.into())), + } } } -/// Current version of the project to analyze. -#[derive(Default)] -enum Current { - /// Path to the manifest of the current version of the project. +enum RustdocSource { + /// Path to the Rustdoc json file. Use this option when you have already generated the rustdoc file. + Rustdoc(PathBuf), + /// Project root directory, i.e. the directory containing the crate source. /// It can be a workspace or a single package. - Manifest(PathBuf), - /// The rustdoc json of the current version of the project. - RustDoc(PathBuf), - /// Use the manifest in the current directory. - #[default] - CurrentDir, + Root(PathBuf), + /// Project root directory and Git Revision. + Revision(PathBuf, String), + /// Version from cargo registry to lookup. E.g. "1.0.0". + /// If `None`, uses the largest-numbered non-yanked non-prerelease version + /// published to the cargo registry. If no such version, uses + /// the largest-numbered version including yanked and prerelease versions. + Version(Option), } #[derive(Default)] @@ -121,13 +149,13 @@ impl Scope { } impl Check { - pub fn new() -> Self { - Self::default() - } - - pub fn with_manifest(&mut self, path: PathBuf) -> &mut Self { - self.current = Current::Manifest(path); - self + pub fn new(current: Rustdoc) -> Self { + Self { + scope: Scope::default(), + current, + baseline: Rustdoc::from_latest_version(), + log_level: Default::default(), + } } pub fn with_workspace(&mut self) -> &mut Self { @@ -145,28 +173,8 @@ impl Check { self } - pub fn with_current_rustdoc(&mut self, rustdoc: PathBuf) -> &mut Self { - self.current = Current::RustDoc(rustdoc); - self - } - - pub fn with_baseline_version(&mut self, version: String) -> &mut Self { - self.baseline = Baseline::Version(Some(version)); - self - } - - pub fn with_baseline_revision(&mut self, revision: String) -> &mut Self { - self.baseline = Baseline::Revision(revision); - self - } - - pub fn with_baseline_root(&mut self, root: PathBuf) -> &mut Self { - self.baseline = Baseline::Root(root); - self - } - - pub fn with_baseline_rustdoc(&mut self, rustdoc: PathBuf) -> &mut Self { - self.baseline = Baseline::RustDoc(rustdoc); + pub fn with_baseline(&mut self, baseline: Rustdoc) -> &mut Self { + self.baseline = baseline; self } @@ -175,13 +183,13 @@ impl Check { self } - fn manifest_path(&self) -> anyhow::Result { - let path = match &self.current { - Current::Manifest(path) => path.clone(), - Current::RustDoc(_) => { + fn manifest_path(&self) -> anyhow::Result<&Path> { + let path = match &self.current.source { + RustdocSource::Rustdoc(path) | RustdocSource::Revision(path, _) => path, + RustdocSource::Root(_) | RustdocSource::Version(_) => { + // TODO: this shouldn't happen anyhow::bail!("error: RustDoc is not supported with these arguments.") } - Current::CurrentDir => PathBuf::from("Cargo.toml"), }; Ok(path) } @@ -204,10 +212,13 @@ impl Check { pub fn check_release(&self) -> anyhow::Result { let mut config = GlobalConfig::new().set_level(self.log_level); - let loader: Box = match &self.baseline { - Baseline::RustDoc(path) => Box::new(baseline::RustdocBaseline::new(path.to_owned())), - Baseline::Root(root) => Box::new(baseline::PathBaseline::new(root)?), - Baseline::Revision(rev) => { + let loader: Box = match &self.baseline.source { + RustdocSource::Rustdoc(path) => { + Box::new(baseline::RustdocBaseline::new(path.to_owned())) + } + RustdocSource::Root(root) => Box::new(baseline::PathBaseline::new(root)?), + // TODO: _root is unused + RustdocSource::Revision(_root, rev) => { let metadata = self.manifest_metadata_no_deps()?; let source = metadata.workspace_root.as_std_path(); let slug = util::slugify(rev); @@ -223,7 +234,7 @@ impl Check { &mut config, )?) } - Baseline::Version(version) => { + RustdocSource::Version(version) => { let mut registry = self.registry_baseline(&mut config)?; if let Some(ver) = version { let semver = semver::Version::parse(ver)?; @@ -236,8 +247,8 @@ impl Check { .deps(false) .silence(!config.is_verbose()); - let all_outcomes: Vec> = match &self.current { - Current::RustDoc(current_rustdoc_path) => { + let all_outcomes: Vec> = match &self.current.source { + RustdocSource::Rustdoc(current_rustdoc_path) => { let name = ""; let version = None; let (current_crate, baseline_crate) = generate_versioned_crates( @@ -252,7 +263,7 @@ impl Check { let success = run_check_release(&mut config, name, current_crate, baseline_crate)?; vec![Ok(success)] } - Current::CurrentDir | Current::Manifest(_) => { + RustdocSource::Root(_project_root) => { let metadata = self.manifest_metadata()?; let selected = self.scope.selected_packages(&metadata); selected @@ -296,6 +307,7 @@ impl Check { }) .collect() } + RustdocSource::Revision(_, _) | RustdocSource::Version(_) => todo!(), }; let success = all_outcomes .into_iter() From 9e9d3c6b07115b9fd0eef9e59032fca676f555ff Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Wed, 25 Jan 2023 22:01:32 +0100 Subject: [PATCH 12/36] adapt code to rustdoc refactor --- src/lib.rs | 81 ++++++++++++++++++++++++++--------------------------- src/main.rs | 47 ++++++++++++++++++------------- 2 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3c436f63..1ba37f87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,22 @@ impl Rustdoc { source: RustdocSource::Version(Some(version.into())), } } + + fn registry(&self, config: &mut GlobalConfig) -> anyhow::Result { + let manifest_dir = match &self.source { + RustdocSource::Root(manifest_dir) | RustdocSource::Revision(manifest_dir, _) => { + manifest_dir + } + RustdocSource::Version(_) | RustdocSource::Rustdoc(_) => { + anyhow::bail!("not supported yet") + } + }; + let manifest = manifest_from_dir(&manifest_dir); + let metadata = manifest_metadata_no_deps(&manifest)?; + let target = metadata.target_directory.as_std_path().join(util::SCOPE); + let registry = baseline::RegistryBaseline::new(&target, config)?; + Ok(registry) + } } enum RustdocSource { @@ -183,32 +199,6 @@ impl Check { self } - fn manifest_path(&self) -> anyhow::Result<&Path> { - let path = match &self.current.source { - RustdocSource::Rustdoc(path) | RustdocSource::Revision(path, _) => path, - RustdocSource::Root(_) | RustdocSource::Version(_) => { - // TODO: this shouldn't happen - anyhow::bail!("error: RustDoc is not supported with these arguments.") - } - }; - Ok(path) - } - - fn manifest_metadata(&self) -> anyhow::Result { - let mut command = cargo_metadata::MetadataCommand::new(); - let metadata = command.manifest_path(self.manifest_path()?).exec()?; - Ok(metadata) - } - - fn manifest_metadata_no_deps(&self) -> anyhow::Result { - let mut command = cargo_metadata::MetadataCommand::new(); - let metadata = command - .manifest_path(self.manifest_path()?) - .no_deps() - .exec()?; - Ok(metadata) - } - pub fn check_release(&self) -> anyhow::Result { let mut config = GlobalConfig::new().set_level(self.log_level); @@ -217,9 +207,8 @@ impl Check { Box::new(baseline::RustdocBaseline::new(path.to_owned())) } RustdocSource::Root(root) => Box::new(baseline::PathBaseline::new(root)?), - // TODO: _root is unused - RustdocSource::Revision(_root, rev) => { - let metadata = self.manifest_metadata_no_deps()?; + RustdocSource::Revision(root, rev) => { + let metadata = manifest_metadata_no_deps(root)?; let source = metadata.workspace_root.as_std_path(); let slug = util::slugify(rev); let target = metadata @@ -235,7 +224,7 @@ impl Check { )?) } RustdocSource::Version(version) => { - let mut registry = self.registry_baseline(&mut config)?; + let mut registry = self.current.registry(&mut config)?; if let Some(ver) = version { let semver = semver::Version::parse(ver)?; registry.set_version(semver); @@ -263,8 +252,8 @@ impl Check { let success = run_check_release(&mut config, name, current_crate, baseline_crate)?; vec![Ok(success)] } - RustdocSource::Root(_project_root) => { - let metadata = self.manifest_metadata()?; + RustdocSource::Root(project_root) => { + let metadata = manifest_metadata(project_root)?; let selected = self.scope.selected_packages(&metadata); selected .iter() @@ -315,16 +304,6 @@ impl Check { Ok(Report { success }) } - - fn registry_baseline( - &self, - config: &mut GlobalConfig, - ) -> Result { - let metadata = self.manifest_metadata_no_deps()?; - let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let registry = baseline::RegistryBaseline::new(&target, config)?; - Ok(registry) - } } pub struct Report { @@ -369,3 +348,21 @@ fn generate_versioned_crates( Ok((current_crate, baseline_crate)) } + +fn manifest_from_dir(manifest_dir: &Path) -> PathBuf { + manifest_dir.join("Cargo.toml") +} + +fn manifest_metadata(manifest_dir: &Path) -> anyhow::Result { + let manifest_path = manifest_from_dir(manifest_dir); + let mut command = cargo_metadata::MetadataCommand::new(); + let metadata = command.manifest_path(manifest_path).exec()?; + Ok(metadata) +} + +fn manifest_metadata_no_deps(manifest_dir: &Path) -> anyhow::Result { + let manifest_path = manifest_from_dir(manifest_dir); + let mut command = cargo_metadata::MetadataCommand::new(); + let metadata = command.manifest_path(manifest_path).no_deps().exec()?; + Ok(metadata) +} diff --git a/src/main.rs b/src/main.rs index 00b44df9..b85549b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use cargo_semver_checks::GlobalConfig; +use cargo_semver_checks::Rustdoc; use cargo_semver_checks::SemverQuery; use clap::{Args, Parser, Subcommand}; @@ -187,10 +188,15 @@ struct CheckRelease { impl From for cargo_semver_checks::Check { fn from(value: CheckRelease) -> Self { - let mut check = Self::default(); - if let Some(manifest) = value.manifest.manifest_path { - check.with_manifest(manifest); - } + let current = if let Some(current_rustdoc) = value.current_rustdoc { + Rustdoc::from_path(current_rustdoc) + } else if let Some(manifest) = value.manifest.manifest_path { + Rustdoc::from_root(manifest.parent().expect("manifest path is not a directory")) + } else { + let project_root = std::env::current_dir().expect("can't determine current directory"); + Rustdoc::from_root(project_root) + }; + let mut check = Self::new(current); if value.workspace.all || value.workspace.workspace { check.with_workspace(); } else if !value.workspace.package.is_empty() { @@ -199,21 +205,24 @@ impl From for cargo_semver_checks::Check { if !value.workspace.exclude.is_empty() { check.with_excluded_packages(value.workspace.exclude); } - if let Some(current_rustdoc) = value.current_rustdoc { - check.with_current_rustdoc(current_rustdoc); - } - if let Some(baseline_version) = value.baseline_version { - check.with_baseline_version(baseline_version); - } - if let Some(baseline_rev) = value.baseline_rev { - check.with_baseline_revision(baseline_rev); - } - if let Some(baseline_root) = value.baseline_root { - check.with_baseline_root(baseline_root); - } - if let Some(baseline_rustdoc) = value.baseline_rustdoc { - check.with_baseline_rustdoc(baseline_rustdoc); - } + let baseline = { + let baseline_root = if let Some(baseline_root) = value.baseline_root { + baseline_root + } else { + std::env::current_dir().expect("can't determine current directory") + }; + + if let Some(baseline_version) = value.baseline_version { + Rustdoc::from_version(baseline_version) + } else if let Some(baseline_rev) = value.baseline_rev { + Rustdoc::from_git_revision(baseline_root, baseline_rev) + } else if let Some(baseline_rustdoc) = value.baseline_rustdoc { + Rustdoc::from_path(baseline_rustdoc) + } else { + Rustdoc::from_root(baseline_root) + } + }; + check.with_baseline(baseline); if let Some(log_level) = value.verbosity.log_level() { check.with_log_level(log_level); } From b4e9aa6f8d2821516c60d9c9009c63b10e4cf853 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Wed, 25 Jan 2023 22:07:13 +0100 Subject: [PATCH 13/36] clippy --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1ba37f87..128134d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ impl Rustdoc { anyhow::bail!("not supported yet") } }; - let manifest = manifest_from_dir(&manifest_dir); + let manifest = manifest_from_dir(manifest_dir); let metadata = manifest_metadata_no_deps(&manifest)?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); let registry = baseline::RegistryBaseline::new(&target, config)?; From b27b7ab42767f660019b4e6e03e1c3aa66406eb3 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Wed, 25 Jan 2023 22:19:51 +0100 Subject: [PATCH 14/36] fix --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 128134d4..e10c5b57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,8 +85,7 @@ impl Rustdoc { anyhow::bail!("not supported yet") } }; - let manifest = manifest_from_dir(manifest_dir); - let metadata = manifest_metadata_no_deps(&manifest)?; + let metadata = manifest_metadata_no_deps(manifest_dir)?; let target = metadata.target_directory.as_std_path().join(util::SCOPE); let registry = baseline::RegistryBaseline::new(&target, config)?; Ok(registry) From daa84262e86d8abb55f64e278bf040e678b5ca49 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Fri, 27 Jan 2023 22:10:46 +0100 Subject: [PATCH 15/36] inline function --- src/lib.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e10c5b57..0831bc39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,21 +75,6 @@ impl Rustdoc { source: RustdocSource::Version(Some(version.into())), } } - - fn registry(&self, config: &mut GlobalConfig) -> anyhow::Result { - let manifest_dir = match &self.source { - RustdocSource::Root(manifest_dir) | RustdocSource::Revision(manifest_dir, _) => { - manifest_dir - } - RustdocSource::Version(_) | RustdocSource::Rustdoc(_) => { - anyhow::bail!("not supported yet") - } - }; - let metadata = manifest_metadata_no_deps(manifest_dir)?; - let target = metadata.target_directory.as_std_path().join(util::SCOPE); - let registry = baseline::RegistryBaseline::new(&target, config)?; - Ok(registry) - } } enum RustdocSource { @@ -223,7 +208,18 @@ impl Check { )?) } RustdocSource::Version(version) => { - let mut registry = self.current.registry(&mut config)?; + let mut registry = { + let manifest_dir = match &self.current.source { + RustdocSource::Root(manifest_dir) + | RustdocSource::Revision(manifest_dir, _) => manifest_dir, + RustdocSource::Version(_) | RustdocSource::Rustdoc(_) => { + anyhow::bail!("this combination of current and baseline sources isn't supported yet") + } + }; + let metadata = manifest_metadata_no_deps(manifest_dir)?; + let target = metadata.target_directory.as_std_path().join(util::SCOPE); + baseline::RegistryBaseline::new(&target, &mut config)? + }; if let Some(ver) = version { let semver = semver::Version::parse(ver)?; registry.set_version(semver); From 11907704dbc638177eb653ecb1f19b1b22db4b53 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:44:51 +0100 Subject: [PATCH 16/36] avoid mixing including and excluded packages --- src/lib.rs | 103 +++++++++++++++++++++++++++++++++++++--------------- src/main.rs | 9 +++-- 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0831bc39..9238d43b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ mod query; mod templating; mod util; +use cargo_metadata::PackageId; pub use config::*; pub use query::*; @@ -92,17 +93,47 @@ enum RustdocSource { Version(Option), } +/// Which packages to analyze. #[derive(Default)] struct Scope { + mode: ScopeMode, +} + +enum ScopeMode { + /// All packages except the excluded ones. + DenyList(PackageSelection), + /// Packages to process (see `cargo help pkgid`) + AllowList(Vec), +} + +impl Default for ScopeMode { + fn default() -> Self { + Self::DenyList(PackageSelection::default()) + } +} + +#[derive(Default, Clone)] +pub struct PackageSelection { selection: ScopeSelection, excluded_packages: Vec, } -/// Which packages to analyze. -#[derive(Default, PartialEq, Eq)] -enum ScopeSelection { - /// Package to process (see `cargo help pkgid`) - Packages(Vec), +impl PackageSelection { + pub fn new(selection: ScopeSelection) -> Self { + Self { + selection, + excluded_packages: vec![], + } + } + + pub fn with_excluded_packages(&mut self, packages: Vec) -> &mut Self { + self.excluded_packages = packages; + self + } +} + +#[derive(Default, PartialEq, Eq, Clone)] +pub enum ScopeSelection { /// All packages in the workspace. Equivalent to `--workspace`. Workspace, /// Default members of the workspace. @@ -115,22 +146,35 @@ impl Scope { &self, meta: &'m cargo_metadata::Metadata, ) -> Vec<&'m cargo_metadata::Package> { - let workspace_members: HashSet<_> = meta.workspace_members.iter().collect(); - let base_ids: HashSet<_> = match &self.selection { - ScopeSelection::DefaultMembers => { - // Deviating from cargo because Metadata doesn't have default members - let resolve = meta.resolve.as_ref().expect("no-deps is unsupported"); - match &resolve.root { - Some(root) => { - let mut base_ids = HashSet::new(); - base_ids.insert(root); - base_ids + let workspace_members: HashSet<&PackageId> = meta.workspace_members.iter().collect(); + let base_ids: HashSet<&PackageId> = match &self.mode { + ScopeMode::DenyList(PackageSelection { + selection, + excluded_packages, + }) => { + let packages = match selection { + ScopeSelection::Workspace => workspace_members, + ScopeSelection::DefaultMembers => { + // Deviating from cargo because Metadata doesn't have default members + let resolve = meta.resolve.as_ref().expect("no-deps is unsupported"); + match &resolve.root { + Some(root) => { + let mut base_ids = HashSet::new(); + base_ids.insert(root); + base_ids + } + None => workspace_members, + } } - None => workspace_members, - } + }; + + packages + .iter() + .filter(|p| !excluded_packages.contains(&meta[p].name)) + .map(|p| *p) + .collect() } - ScopeSelection::Workspace => workspace_members, - ScopeSelection::Packages(patterns) => { + ScopeMode::AllowList(patterns) => { meta.packages .iter() // Deviating from cargo by not supporting patterns @@ -143,7 +187,7 @@ impl Scope { meta.packages .iter() - .filter(|p| base_ids.contains(&p.id) && !self.excluded_packages.contains(&p.name)) + .filter(|&p| base_ids.contains(&p.id)) .collect() } } @@ -158,18 +202,13 @@ impl Check { } } - pub fn with_workspace(&mut self) -> &mut Self { - self.scope.selection = ScopeSelection::Workspace; + pub fn with_package_selection(&mut self, selection: PackageSelection) -> &mut Self { + self.scope.mode = ScopeMode::DenyList(selection); self } pub fn with_packages(&mut self, packages: Vec) -> &mut Self { - self.scope.selection = ScopeSelection::Packages(packages); - self - } - - pub fn with_excluded_packages(&mut self, excluded_packages: Vec) -> &mut Self { - self.scope.excluded_packages = excluded_packages; + self.scope.mode = ScopeMode::AllowList(packages); self } @@ -257,7 +296,13 @@ impl Check { let crate_name = &selected.name; let version = &selected.version; - let is_implied = self.scope.selection == ScopeSelection::Workspace; + let is_implied = matches!( + self.scope.mode, + ScopeMode::DenyList(PackageSelection { + selection: ScopeSelection::Workspace, + .. + }) + ) && selected.publish == Some(vec![]); if is_implied && selected.publish == Some(vec![]) { config.verbose(|config| { config.shell_status( diff --git a/src/main.rs b/src/main.rs index b85549b1..a239a85b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use cargo_semver_checks::GlobalConfig; +use cargo_semver_checks::PackageSelection; use cargo_semver_checks::Rustdoc; +use cargo_semver_checks::ScopeSelection; use cargo_semver_checks::SemverQuery; use clap::{Args, Parser, Subcommand}; @@ -198,13 +200,12 @@ impl From for cargo_semver_checks::Check { }; let mut check = Self::new(current); if value.workspace.all || value.workspace.workspace { - check.with_workspace(); + let mut selection = PackageSelection::new(ScopeSelection::Workspace); + selection.with_excluded_packages(value.workspace.exclude); + check.with_package_selection(selection); } else if !value.workspace.package.is_empty() { check.with_packages(value.workspace.package); } - if !value.workspace.exclude.is_empty() { - check.with_excluded_packages(value.workspace.exclude); - } let baseline = { let baseline_root = if let Some(baseline_root) = value.baseline_root { baseline_root From 9d3550542e89d14bc833d9950bc0ae93b8058b8b Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:47:01 +0100 Subject: [PATCH 17/36] clippy --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9238d43b..464e3220 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,7 @@ impl Scope { packages .iter() .filter(|p| !excluded_packages.contains(&meta[p].name)) - .map(|p| *p) + .copied() .collect() } ScopeMode::AllowList(patterns) => { From ad9d81c287414584d70bd84ba115b25e7350a815 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:49:11 +0100 Subject: [PATCH 18/36] add derive debug --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 464e3220..e05a5e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; /// Test a release for semver violations. +#[derive(Debug)] pub struct Check { /// Which packages to analyze. scope: Scope, @@ -31,6 +32,7 @@ pub struct Check { log_level: Option, } +#[derive(Debug)] pub struct Rustdoc { source: RustdocSource, } @@ -78,6 +80,7 @@ impl Rustdoc { } } +#[derive(Debug)] enum RustdocSource { /// Path to the Rustdoc json file. Use this option when you have already generated the rustdoc file. Rustdoc(PathBuf), @@ -94,11 +97,12 @@ enum RustdocSource { } /// Which packages to analyze. -#[derive(Default)] +#[derive(Default, Debug)] struct Scope { mode: ScopeMode, } +#[derive(Debug)] enum ScopeMode { /// All packages except the excluded ones. DenyList(PackageSelection), @@ -112,7 +116,7 @@ impl Default for ScopeMode { } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct PackageSelection { selection: ScopeSelection, excluded_packages: Vec, @@ -132,7 +136,7 @@ impl PackageSelection { } } -#[derive(Default, PartialEq, Eq, Clone)] +#[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum ScopeSelection { /// All packages in the workspace. Equivalent to `--workspace`. Workspace, From 8363bd8a1aa2a70ef645eea9385ced47d9ab2583 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:49:41 +0100 Subject: [PATCH 19/36] Update src/lib.rs Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e05a5e44..88b8a52e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,8 @@ impl Rustdoc { } } - /// Generate the rustdoc file from the project root directory, i.e. the directory containing the crate source. + /// Generate the rustdoc file from the project root directory, + /// i.e. the directory containing the crate source. /// It can be a workspace or a single package. /// Same as `from_git_revision`, but with the current git revision. pub fn from_root(project_root: impl Into) -> Self { From 368ee5b0c0e866e8dedc00d079ff4ac63cd764e1 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:50:35 +0100 Subject: [PATCH 20/36] add non_exhaustive --- src/query.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/query.rs b/src/query.rs index 8431fd3b..1aa8abe4 100644 --- a/src/query.rs +++ b/src/query.rs @@ -39,6 +39,7 @@ impl ActualSemverUpdate { /// A query that can be executed on a pair of rustdoc output files, /// returning instances of a particular kind of semver violation. +#[non_exhaustive] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SemverQuery { pub id: String, From f626d7ebaed9becbf30edaaf24ff273a75fa0830 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:52:12 +0100 Subject: [PATCH 21/36] non_exhaustive --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 88b8a52e..cc92ee34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,6 +137,7 @@ impl PackageSelection { } } +#[non_exhaustive] #[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum ScopeSelection { /// All packages in the workspace. Equivalent to `--workspace`. From 19ba58fa9e58a17d12a3d8c7da693df5a2d912c3 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:53:24 +0100 Subject: [PATCH 22/36] Update src/lib.rs Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cc92ee34..df115d11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,8 @@ impl Rustdoc { #[derive(Debug)] enum RustdocSource { - /// Path to the Rustdoc json file. Use this option when you have already generated the rustdoc file. + /// Path to the Rustdoc json file. + /// Use this option when you have already generated the rustdoc file. Rustdoc(PathBuf), /// Project root directory, i.e. the directory containing the crate source. /// It can be a workspace or a single package. From ab2dd0a6c1e2d77bfefafa35a2343e6ad3f72db4 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:54:40 +0100 Subject: [PATCH 23/36] Update src/lib.rs Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index df115d11..645ce0b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -353,6 +353,8 @@ impl Check { } } +#[non_exhaustive] +#[derive(Debug) pub struct Report { success: bool, } From eac165e05fbd2df4d43e09843d8cbd6449836faa Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:55:10 +0100 Subject: [PATCH 24/36] non_exhaustive --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 645ce0b7..ea276f15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; /// Test a release for semver violations. +#[non_exhaustive] #[derive(Debug)] pub struct Check { /// Which packages to analyze. @@ -32,6 +33,7 @@ pub struct Check { log_level: Option, } +#[non_exhaustive] #[derive(Debug)] pub struct Rustdoc { source: RustdocSource, @@ -118,6 +120,7 @@ impl Default for ScopeMode { } } +#[non_exhaustive] #[derive(Default, Clone, Debug)] pub struct PackageSelection { selection: ScopeSelection, From 64509d76c96e09ff9c2daac8e25c38e9ee02a159 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:55:42 +0100 Subject: [PATCH 25/36] fix --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ea276f15..0cd9ac99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -357,7 +357,7 @@ impl Check { } #[non_exhaustive] -#[derive(Debug) +#[derive(Debug)] pub struct Report { success: bool, } From 9990b69121245674c17b8f7d93cedd9880a5dab7 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:57:52 +0100 Subject: [PATCH 26/36] debug --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 0cd9ac99..1e3cce4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -369,6 +369,7 @@ impl Report { } // Argument to the generate_versioned_crates function. +#[derive(Debug)] enum CurrentCratePath<'a> { CurrentRustdocPath(&'a Path), // If rustdoc is passed, it is just loaded into the memory. ManifestPath(&'a Path), // Otherwise, the function generates the rustdoc. From 5a334bac2269731663c2c0f377911b61a1bc8b83 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 20:01:11 +0100 Subject: [PATCH 27/36] doc comment --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 1e3cce4b..a52a993b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,7 @@ impl Rustdoc { } } + /// Generate the rustdoc file from a specific crate version. pub fn from_version(version: impl Into) -> Self { Self { source: RustdocSource::Version(Some(version.into())), From a496d420ecd96a1fe338bdc96a92f8960350a7a5 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sun, 5 Feb 2023 20:07:05 +0100 Subject: [PATCH 28/36] rustdoc --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a52a993b..05507246 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ impl Rustdoc { /// Generate the rustdoc file from the project root directory, /// i.e. the directory containing the crate source. /// It can be a workspace or a single package. - /// Same as `from_git_revision`, but with the current git revision. + /// Same as [`Rustdoc::from_git_revision()`], but with the current git revision. pub fn from_root(project_root: impl Into) -> Self { Self { source: RustdocSource::Root(project_root.into()), From b0f7102d05000a31792bf69a8e70eb39faa8c086 Mon Sep 17 00:00:00 2001 From: Tomasz Nowak Date: Wed, 8 Feb 2023 17:17:34 +0100 Subject: [PATCH 29/36] Path handling --- src/lib.rs | 35 +++++++++++++++++++++----- src/main.rs | 10 +++++++- tests/passing_manifest_or_directory.rs | 30 ++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 tests/passing_manifest_or_directory.rs diff --git a/src/lib.rs b/src/lib.rs index 23796747..fc91e73b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -416,19 +416,42 @@ fn generate_versioned_crates( Ok((current_crate, baseline_crate)) } -fn manifest_from_dir(manifest_dir: &Path) -> PathBuf { - manifest_dir.join("Cargo.toml") +fn manifest_path(project_root: &Path) -> anyhow::Result { + if project_root.is_dir() { + let manifest_path = project_root.join("Cargo.toml"); + // Checking whether the file exists here is not necessary + // (it will nevertheless be checked while parsing the manifest), + // but it should give a nicer error message for the user. + if manifest_path.exists() { + Ok(manifest_path) + } else { + anyhow::bail!( + "couldn't find Cargo.toml in directory {}", + project_root.display() + ) + } + } else if project_root.ends_with("Cargo.toml") { + // Even though the `project_root` should be a directory, + // someone could by accident directly pass the path to the manifest + // and we're kind enough to accept it. + Ok(project_root.to_path_buf()) + } else { + anyhow::bail!( + "path {} is not a directory or a manifest", + project_root.display() + ) + } } -fn manifest_metadata(manifest_dir: &Path) -> anyhow::Result { - let manifest_path = manifest_from_dir(manifest_dir); +fn manifest_metadata(project_root: &Path) -> anyhow::Result { + let manifest_path = manifest_path(project_root)?; let mut command = cargo_metadata::MetadataCommand::new(); let metadata = command.manifest_path(manifest_path).exec()?; Ok(metadata) } -fn manifest_metadata_no_deps(manifest_dir: &Path) -> anyhow::Result { - let manifest_path = manifest_from_dir(manifest_dir); +fn manifest_metadata_no_deps(project_root: &Path) -> anyhow::Result { + let manifest_path = manifest_path(project_root)?; let mut command = cargo_metadata::MetadataCommand::new(); let metadata = command.manifest_path(manifest_path).no_deps().exec()?; Ok(metadata) diff --git a/src/main.rs b/src/main.rs index a239a85b..026cd3fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -193,7 +193,15 @@ impl From for cargo_semver_checks::Check { let current = if let Some(current_rustdoc) = value.current_rustdoc { Rustdoc::from_path(current_rustdoc) } else if let Some(manifest) = value.manifest.manifest_path { - Rustdoc::from_root(manifest.parent().expect("manifest path is not a directory")) + let project_root = if manifest.is_dir() { + manifest + } else { + manifest + .parent() + .expect("manifest path doesn't have a parent") + .to_path_buf() + }; + Rustdoc::from_root(project_root) } else { let project_root = std::env::current_dir().expect("can't determine current directory"); Rustdoc::from_root(project_root) diff --git a/tests/passing_manifest_or_directory.rs b/tests/passing_manifest_or_directory.rs new file mode 100644 index 00000000..c76e62b1 --- /dev/null +++ b/tests/passing_manifest_or_directory.rs @@ -0,0 +1,30 @@ +use assert_cmd::Command; + +fn check_paths(current_path: &'static str, baseline_path: &'static str) { + let mut cmd = Command::cargo_bin("cargo-semver-checks").unwrap(); + cmd.args([ + "semver-checks", + "check-release", + format!("--manifest-path={current_path}").as_str(), + format!("--baseline-root={baseline_path}").as_str(), + ]) + .assert() + .success(); +} + +#[test] +fn both_passing_manifest_path_and_directory_works() { + check_paths("test_crates/template/new/", "test_crates/template/old/"); + check_paths( + "test_crates/template/new/", + "test_crates/template/old/Cargo.toml", + ); + check_paths( + "test_crates/template/new/Cargo.toml", + "test_crates/template/old/", + ); + check_paths( + "test_crates/template/new/Cargo.toml", + "test_crates/template/old/Cargo.toml", + ); +} From 371ccabd22e9253d6aa0ef85edd7774fbe327f42 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:08:02 +0100 Subject: [PATCH 30/36] remove useless check --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fc91e73b..e3848fc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -324,7 +324,7 @@ impl Check { .. }) ) && selected.publish == Some(vec![]); - if is_implied && selected.publish == Some(vec![]) { + if is_implied { config.verbose(|config| { config.shell_status( "Skipping", From 72ee617440769ea51bcfe1e146acb0763e34bd43 Mon Sep 17 00:00:00 2001 From: Tomasz Nowak Date: Wed, 8 Feb 2023 18:24:47 +0100 Subject: [PATCH 31/36] Fix baseline_root when no baseline-path given and baseline-rev passed --- src/main.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 026cd3fe..81a4664a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -190,8 +190,8 @@ struct CheckRelease { impl From for cargo_semver_checks::Check { fn from(value: CheckRelease) -> Self { - let current = if let Some(current_rustdoc) = value.current_rustdoc { - Rustdoc::from_path(current_rustdoc) + let (current, current_project_root) = if let Some(current_rustdoc) = value.current_rustdoc { + (Rustdoc::from_path(current_rustdoc), None) } else if let Some(manifest) = value.manifest.manifest_path { let project_root = if manifest.is_dir() { manifest @@ -201,10 +201,10 @@ impl From for cargo_semver_checks::Check { .expect("manifest path doesn't have a parent") .to_path_buf() }; - Rustdoc::from_root(project_root) + (Rustdoc::from_root(&project_root), Some(project_root)) } else { let project_root = std::env::current_dir().expect("can't determine current directory"); - Rustdoc::from_root(project_root) + (Rustdoc::from_root(&project_root), Some(project_root)) }; let mut check = Self::new(current); if value.workspace.all || value.workspace.workspace { @@ -215,20 +215,26 @@ impl From for cargo_semver_checks::Check { check.with_packages(value.workspace.package); } let baseline = { - let baseline_root = if let Some(baseline_root) = value.baseline_root { - baseline_root - } else { - std::env::current_dir().expect("can't determine current directory") - }; - if let Some(baseline_version) = value.baseline_version { Rustdoc::from_version(baseline_version) } else if let Some(baseline_rev) = value.baseline_rev { - Rustdoc::from_git_revision(baseline_root, baseline_rev) + let root = if let Some(baseline_root) = value.baseline_root { + baseline_root + } else if let Some(current_root) = current_project_root { + current_root + } else { + std::env::current_dir().expect("can't determine current directory") + }; + Rustdoc::from_git_revision(root, baseline_rev) } else if let Some(baseline_rustdoc) = value.baseline_rustdoc { Rustdoc::from_path(baseline_rustdoc) } else { - Rustdoc::from_root(baseline_root) + let root = if let Some(baseline_root) = value.baseline_root { + baseline_root + } else { + std::env::current_dir().expect("can't determine current directory") + }; + Rustdoc::from_root(root) } }; check.with_baseline(baseline); From 2449acd377ed1792127a10c0ffed211094664d1b Mon Sep 17 00:00:00 2001 From: Tomasz Nowak Date: Wed, 8 Feb 2023 18:46:57 +0100 Subject: [PATCH 32/36] Fallback to XDG cache dir --- Cargo.lock | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 36 ++++++++++++++++++++++-------------- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 803840d2..1b05e04d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,6 +219,7 @@ dependencies = [ "toml 0.5.11", "trustfall_core", "trustfall_rustdoc", + "xdg", ] [[package]] @@ -510,6 +511,26 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1179,6 +1200,26 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.7.1" @@ -1897,3 +1938,12 @@ name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "xdg" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" +dependencies = [ + "dirs", +] diff --git a/Cargo.toml b/Cargo.toml index d6945010..1a39db36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ bugreport = "0.5.0" itertools = "0.10.5" cargo_toml = "0.13.0" toml = "0.5.9" +xdg = "2.4.1" [dev-dependencies] assert_cmd = "2.0" diff --git a/src/lib.rs b/src/lib.rs index e3848fc2..339e2889 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,18 +233,25 @@ impl Check { self } + /// Some `RustdocSource`s don't contain a path to the project root, + /// so they don't have a target directory. We try to deduce the target directory + /// on a "best effort" basis -- when the source contains a target dir, + /// we use it, otherwise when the other source contains one, we use it, + /// otherwise we just use a standard cache folder as specified by XDG. + /// We cannot use a temporary directory, because the rustdocs from registry + /// are being cached in the target directory. fn get_target_dir(&self, source: &RustdocSource) -> anyhow::Result { - Ok(if let Some(path) = get_target_dir_from_config() { - path - } else if let Some(path) = get_target_dir_from_project_root(source)? { - path - } else if let Some(path) = get_target_dir_from_project_root(&self.current.source)? { - path - } else if let Some(path) = get_target_dir_from_project_root(&self.baseline.source)? { - path - } else { - std::env::current_dir()?.join("target") - }) + Ok( + if let Some(path) = get_target_dir_from_project_root(source)? { + path + } else if let Some(path) = get_target_dir_from_project_root(&self.current.source)? { + path + } else if let Some(path) = get_target_dir_from_project_root(&self.baseline.source)? { + path + } else { + get_xdg_cache_dir() + }, + ) } fn get_rustdoc_generator( @@ -457,9 +464,10 @@ fn manifest_metadata_no_deps(project_root: &Path) -> anyhow::Result Option { - // TODO: I'll implement this function probably tomorrow - None +fn get_xdg_cache_dir() -> PathBuf { + xdg::BaseDirectories::with_prefix("cargo-semver-checks") + .expect("failed to retrieve XDG base directories") + .get_cache_home() } fn get_target_dir_from_project_root(source: &RustdocSource) -> anyhow::Result> { From 8485cc5dfe51a91ba2e8fe2b071c631f24f67cfb Mon Sep 17 00:00:00 2001 From: Tomasz Nowak Date: Thu, 9 Feb 2023 10:12:57 +0100 Subject: [PATCH 33/36] Renamed registry/version items --- src/lib.rs | 18 +++++++++--------- src/main.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 339e2889..95a684d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,16 +70,16 @@ impl Rustdoc { /// Generate the rustdoc file from the largest-numbered non-yanked non-prerelease version /// published to the cargo registry. If no such version, uses /// the largest-numbered version including yanked and prerelease versions. - pub fn from_latest_version() -> Self { + pub fn from_registry_latest_crate_version() -> Self { Self { - source: RustdocSource::Version(None), + source: RustdocSource::VersionFromRegistry(None), } } /// Generate the rustdoc file from a specific crate version. - pub fn from_version(version: impl Into) -> Self { + pub fn from_registry(crate_version: impl Into) -> Self { Self { - source: RustdocSource::Version(Some(version.into())), + source: RustdocSource::VersionFromRegistry(Some(crate_version.into())), } } } @@ -98,7 +98,7 @@ enum RustdocSource { /// If `None`, uses the largest-numbered non-yanked non-prerelease version /// published to the cargo registry. If no such version, uses /// the largest-numbered version including yanked and prerelease versions. - Version(Option), + VersionFromRegistry(Option), } /// Which packages to analyze. @@ -208,7 +208,7 @@ impl Check { Self { scope: Scope::default(), current, - baseline: Rustdoc::from_latest_version(), + baseline: Rustdoc::from_registry_latest_crate_version(), log_level: Default::default(), } } @@ -277,7 +277,7 @@ impl Check { config, )?) } - RustdocSource::Version(version) => { + RustdocSource::VersionFromRegistry(version) => { let mut registry = rustdoc_gen::RustdocFromRegistry::new(&target_dir, config)?; if let Some(ver) = version { let semver = semver::Version::parse(ver)?; @@ -300,7 +300,7 @@ impl Check { let all_outcomes: Vec> = match &self.current.source { RustdocSource::Rustdoc(_) | RustdocSource::Revision(_, _) - | RustdocSource::Version(_) => { + | RustdocSource::VersionFromRegistry(_) => { let name = ""; let version = None; let (current_crate, baseline_crate) = generate_versioned_crates( @@ -484,6 +484,6 @@ fn get_target_dir_from_project_root(source: &RustdocSource) -> anyhow::Result None, - RustdocSource::Version(_version) => None, + RustdocSource::VersionFromRegistry(_version) => None, }) } diff --git a/src/main.rs b/src/main.rs index 81a4664a..4781a6c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -216,7 +216,7 @@ impl From for cargo_semver_checks::Check { } let baseline = { if let Some(baseline_version) = value.baseline_version { - Rustdoc::from_version(baseline_version) + Rustdoc::from_registry(baseline_version) } else if let Some(baseline_rev) = value.baseline_rev { let root = if let Some(baseline_root) = value.baseline_root { baseline_root From e5da9d8c2bdceb2cc7db9f57e51479677fa57c6d Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 18 Feb 2023 11:04:54 +0100 Subject: [PATCH 34/36] fmt --- src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 04d124d9..e9ebc0e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,7 +332,13 @@ impl Check { version, )?; - let success = run_check_release(&mut config, name, current_crate, baseline_crate, self.release_type)?; + let success = run_check_release( + &mut config, + name, + current_crate, + baseline_crate, + self.release_type, + )?; vec![Ok(success)] } RustdocSource::Root(project_root) => { From 22c0081de3ba92aede64657e38b2595ccb7a1235 Mon Sep 17 00:00:00 2001 From: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Date: Sat, 18 Feb 2023 11:16:49 +0100 Subject: [PATCH 35/36] use directories crate --- Cargo.lock | 17 ++++------------- Cargo.toml | 2 +- src/lib.rs | 15 ++++++++++----- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7890216f..10bb91f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ dependencies = [ "clap-cargo", "clap-verbosity-flag", "crates-index", + "directories", "git2", "handlebars", "human-panic", @@ -219,7 +220,6 @@ dependencies = [ "toml 0.5.11", "trustfall_core", "trustfall_rustdoc", - "xdg", ] [[package]] @@ -512,10 +512,10 @@ dependencies = [ ] [[package]] -name = "dirs" -version = "4.0.0" +name = "directories" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ "dirs-sys", ] @@ -1972,12 +1972,3 @@ name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "xdg" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" -dependencies = [ - "dirs", -] diff --git a/Cargo.toml b/Cargo.toml index 6cb50d4b..323733bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ bugreport = "0.5.0" itertools = "0.10.5" cargo_toml = "0.15" toml = "0.5.9" -xdg = "2.4.1" +directories = "4.0.1" [dev-dependencies] assert_cmd = "2.0" diff --git a/src/lib.rs b/src/lib.rs index e9ebc0e9..71236952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,11 @@ mod rustdoc_gen; mod templating; mod util; +use anyhow::Context; use cargo_metadata::PackageId; use clap::ValueEnum; pub use config::*; +use directories::ProjectDirs; pub use query::*; use check_release::run_check_release; @@ -21,6 +23,7 @@ use itertools::Itertools; use rustdoc_cmd::RustdocCommand; use semver::Version; use std::collections::HashSet; +use std::fs; use std::path::{Path, PathBuf}; /// Test a release for semver violations. @@ -269,7 +272,7 @@ impl Check { } else if let Some(path) = get_target_dir_from_project_root(&self.baseline.source)? { path } else { - get_xdg_cache_dir() + get_cache_dir()? }, ) } @@ -491,10 +494,12 @@ fn manifest_metadata_no_deps(project_root: &Path) -> anyhow::Result PathBuf { - xdg::BaseDirectories::with_prefix("cargo-semver-checks") - .expect("failed to retrieve XDG base directories") - .get_cache_home() +fn get_cache_dir() -> anyhow::Result { + let project_dirs = + ProjectDirs::from("", "", "cargo-semver-checks").context("can't determine project dirs")?; + let cache_dir = project_dirs.cache_dir(); + fs::create_dir_all(cache_dir).context("can't create cache dir")?; + Ok(cache_dir.to_path_buf()) } fn get_target_dir_from_project_root(source: &RustdocSource) -> anyhow::Result> { From 7516ab0929ebb3dc57351a082455ea1d7ef8c4c8 Mon Sep 17 00:00:00 2001 From: Tomasz Nowak Date: Mon, 20 Feb 2023 14:43:08 +0100 Subject: [PATCH 36/36] Added panic in 'unknown' branch --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 71236952..87d2da0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -324,25 +324,38 @@ impl Check { RustdocSource::Rustdoc(_) | RustdocSource::Revision(_, _) | RustdocSource::VersionFromRegistry(_) => { - let name = ""; - let version = None; - let (current_crate, baseline_crate) = generate_versioned_crates( - &mut config, - &rustdoc_cmd, - &*current_loader, - &*baseline_loader, - name, - version, - )?; - - let success = run_check_release( - &mut config, - name, - current_crate, - baseline_crate, - self.release_type, - )?; - vec![Ok(success)] + let names = match &self.scope.mode { + ScopeMode::DenyList(_) => + match &self.current.source { + RustdocSource::Rustdoc(_) => + vec!["the-name-doesnt-matter-here".to_string()], + _ => panic!("couldn't deduce crate name, specify one through the package allow list") + } + ScopeMode::AllowList(lst) => lst.clone(), + }; + names + .iter() + .map(|name| { + let version = None; + let (current_crate, baseline_crate) = generate_versioned_crates( + &mut config, + &rustdoc_cmd, + &*current_loader, + &*baseline_loader, + name, + version, + )?; + + let success = run_check_release( + &mut config, + name, + current_crate, + baseline_crate, + self.release_type, + )?; + Ok(success) + }) + .collect() } RustdocSource::Root(project_root) => { let metadata = manifest_metadata(project_root)?;