diff --git a/Cargo.toml b/Cargo.toml index 29bd1f5..7c8573e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,15 @@ homepage = "https://github.com/cat-in-136/cargo-generate-rpm" readme = "README.md" keywords = ["rpm", "package", "cargo", "subcommand"] repository = "https://github.com/cat-in-136/cargo-generate-rpm" -version = "0.14.0" +version = "0.15.0" edition = "2021" [dependencies] -glob = "0.3.0" -rpm = { version = "0.13.1", default-features = false } +glob = "0.3" +rpm = { version = "0.14", default-features = false } toml = "0.7" cargo_toml = "0.15" -clap = { version = "4.3", features = ["derive"] } +clap = { version = "~4.3", features = ["derive"] } color-print = "0.3" thiserror = "1" elf = "0.7" diff --git a/README.md b/README.md index 7478fa5..48fd20c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ using the [`rpm`](https://crates.io/crates/rpm) crate. ![Rust](https://github.com/cat-in-136/cargo-generate-rpm/workflows/Rust/badge.svg) [![cargo-generate-rpm at crates.io](https://img.shields.io/crates/v/cargo-generate-rpm.svg)](https://crates.io/crates/cargo-generate-rpm) +Legacy systems requiring RPMv3 (e.g. CentOS 7) are no longer supported due to rpm-rs compatibility. +Use versions prior to 0.15 for such a system. + ## Install ```sh @@ -27,7 +30,7 @@ Upon run `cargo generate-rpm` on your cargo project, a binary RPM package file w in `target/generate-rpm/XXX.rpm`. You can change the RPM package file location using `-o` option. -In advance, run `cargo run --release` and strip the debug symbols (`strip -s target/release/XXX`), because these are not +In advance, run `cargo build --release` and strip the debug symbols (`strip -s target/release/XXX`), because these are not run upon `cargo generate-rpm` as of now. ## Configuration @@ -88,6 +91,10 @@ from [the `Cargo.toml` file](https://doc.rust-lang.org/cargo/reference/manifest. * obsoletes: optional list of Obsoletes * conflicts: optional list of Conflicts * provides: optional list of Provides +* recommends: optional list of Recommends +* supplements: optional list of Supplements +* suggests: optional list of Suggests +* enhances: optional list of Enhances * vendor: optional string of Vendor Adding assets such as the binary file, ``.desktop`` file, or icons, shall be written in the following way. @@ -152,11 +159,12 @@ scripts. `[package.metadata.generate-rpm]` can be overwritten. The following command line options are used: * `--metadata-overwrite=TOML_FILE.toml` : Overwrite the `[package.metadata.generate-rpm]` options with the contents of - the specified TOML file. + the specified TOML file. Multiple files can be specified, separated by commas. * `--metadata-overwrite=TOML_FILE.toml#TOML.PATH` : Overwrites the `[package.metadata.generate-rpm]` options with the table specified in the TOML path of the TOML file. Only a sequence of bare keys connected by dots is acceptable for the TOML path. Path containing quoted keys (such as `metadata."παραλλαγή"`) cannot be acceptable. + Multiple files with TOML pathes can be specified, separated by commas. * `-s 'toml "text"'` or `--set-metadata='toml "text"'` : Overwrite the `[package.metadata.generate-rpm]` options with inline TOML text. The argument text --- inline TOML text must be enclosed in quotation marks since it contains spaces. @@ -165,10 +173,11 @@ scripts. It is a shortcut to `--metadata-overwrite=path/to/Cargo.toml#package.metadata.generate-rpm.variants.VARIANT`. It is intended for providing multiple variants of the metadata in a Cargo.toml and ability for the users to select the variant using --variant=name option. + Multiple variant names can be specified, separated by commas. -These options can be specified more than once, with the last written one specified being applied. -For example, the arguments -s 'release = "alpha"' `--metadata-overwrite=beta.toml` where beta.toml -contains `release = "beta"` gives `release = "beta"`. +These options may be specified multiple times, with the last one written being applied regardless of the kind of option. +For example, the arguments `-s 'release = "alpha"' --metadata-overwrite=beta.toml` where beta.toml +contains `release = "beta"`, then gives `release = "beta"`. ## Advanced Usage @@ -220,9 +229,6 @@ Similarly, if using a custom build profile with, for example, `--profile custom` The default payload compress type of the generated RPM file is zstd. You can specify the payload compress type with `--payload-compress TYPE`: none, gzip, or zstd. -For the legacy system (e.g. centos7), specify legacy compress type explicitly e.g. `--payload-compress none`. - - ### Scriptlet Flags and Prog Settings Scriptlet settings can be configured via `*_script_flags` and `*_script_prog` settings. diff --git a/src/cli.rs b/src/cli.rs index d9d6cca..e814520 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use clap::{ builder::{PathBufValueParser, PossibleValuesParser, TypedValueParser, ValueParserFactory}, Arg, ArgMatches, Command, CommandFactory, FromArgMatches, Parser, ValueEnum, }; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::path::PathBuf; /// Wrapper used when the application is executed as Cargo plugin @@ -80,7 +80,7 @@ pub struct Cli { pub metadata_overwrite: Vec, /// Overwrite metadata with TOML text. - #[arg(short, long, value_delimiter = ',')] + #[arg(short, long)] pub set_metadata: Vec, /// Shortcut to --metadata-overwrite=path/to/Cargo.toml#package.metadata.generate-rpm.variants.VARIANT @@ -89,17 +89,58 @@ pub struct Cli { } impl Cli { - pub fn get_matches_and_try_parse() -> Result<(Self, ArgMatches), clap::Error> { - let mut args = std::env::args(); - if let Some("generate-rpm") = args.nth(1).as_deref() { - let mut matches = ::command().get_matches(); + #[inline] + fn get_matches_and_try_parse_from( + args_fn: F, + ) -> Result<(Self, ArgMatches), clap::Error> + where + F: Fn() -> I, + I: IntoIterator + Iterator, + T: Into + Clone, + { + let mut args = args_fn(); + let matches = if args.nth(1) == Some(OsString::from("generate-rpm")) { + let mut matches = ::command().get_matches_from(args); let CargoWrapper::GenerateRpm(arg) = CargoWrapper::from_arg_matches_mut(&mut matches)?; Ok((arg, matches)) } else { - let mut matches = ::command().get_matches(); + let mut matches = ::command().get_matches_from(args); let arg = Self::from_arg_matches_mut(&mut matches)?; Ok((arg, matches)) + }; + } + + pub fn get_matches_and_try_parse() -> Result<(Self, ArgMatches), clap::Error> { + Self::get_matches_and_try_parse_from(std::env::args_os) + } + + pub fn extra_metadata(&self, matches: &ArgMatches) -> Vec { + let mut extra_metadata_args = Vec::new(); + + if let Some(indices) = matches.indices_of("metadata_overwrite") { + for (v, i) in self.metadata_overwrite.iter().zip(indices) { + let (file, branch) = match v.split_once('#') { + None => (PathBuf::from(v), None), + Some((file, branch)) => (PathBuf::from(file), Some(branch.to_string())), + }; + extra_metadata_args.push((i, ExtraMetadataSource::File(file, branch))); + } } + + if let Some(indices) = matches.indices_of("set_metadata") { + for (v, i) in self.set_metadata.iter().zip(indices) { + extra_metadata_args.push((i, ExtraMetadataSource::Text(v.to_string()))); + } + } + + if let Some(indices) = matches.indices_of("variant") { + for (v, i) in self.variant.iter().zip(indices) { + extra_metadata_args.push((i, ExtraMetadataSource::Variant(v.to_string()))); + } + } + + extra_metadata_args.sort_by_key(|v| v.0); + extra_metadata_args.drain(..).map(|v| v.1).collect() } } @@ -168,12 +209,7 @@ impl TypedValueParser for AutoReqModeParser { let inner = PossibleValuesParser::new(VALUES.iter().map(|(k, _v)| k)); match inner.parse_ref(cmd, arg, value) { - Ok(name) => Ok(VALUES - .iter() - .find(|(k, _v)| name.as_str() == (k.as_ref() as &str)) - .unwrap() - .1 - .clone()), + Ok(name) => Ok(VALUES.iter().find(|(k, _v)| name.eq(k)).unwrap().1.clone()), Err(e) if e.kind() == clap::error::ErrorKind::InvalidValue => { let inner = PathBufValueParser::new(); match inner.parse_ref(cmd, arg, value) { @@ -186,6 +222,13 @@ impl TypedValueParser for AutoReqModeParser { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ExtraMetadataSource { + File(PathBuf, Option), + Text(String), + Variant(String), +} + #[cfg(test)] mod tests { use super::*; @@ -199,6 +242,31 @@ mod tests { ::command().debug_assert() } + #[test] + fn test_get_matches_and_try_parse_from() { + let (args, matcher) = Cli::get_matches_and_try_parse_from(|| { + ["", "-o", "/dev/null"].map(&OsString::from).into_iter() + }) + .unwrap(); + assert_eq!(args.output, Some(PathBuf::from("/dev/null"))); + assert_eq!( + matcher.indices_of("output").unwrap().collect::>(), + &[2] + ); + + let (args, matcher) = Cli::get_matches_and_try_parse_from(|| { + ["generate-rpm", "-o", "/dev/null"] + .map(&OsString::from) + .into_iter() + }) + .unwrap(); + assert_eq!(args.output, Some(PathBuf::from("/dev/null"))); + assert_eq!( + matcher.indices_of("output").unwrap().collect::>(), + &[2] + ); + } + #[test] fn test_metadata_overwrite() { let args = Cli::try_parse_from([ @@ -228,6 +296,51 @@ mod tests { assert_eq!(args.set_metadata, vec!["toml \"text1\"", "toml \"text2\""]); } + #[test] + fn test_extrametadata() { + let (args, matches) = Cli::get_matches_and_try_parse_from(|| { + [ + "", + "--metadata-overwrite", + "TOML_FILE1.toml", + "-s", + "toml \"text1\"", + "--metadata-overwrite", + "TOML_FILE2.toml#TOML.PATH", + "--variant", + "VARIANT1,VARIANT2", + "--set-metadata", + "toml \"text2\"", + "--metadata-overwrite", + "TOML_FILE3.toml#TOML.PATH,TOML_FILE4.toml", + ] + .map(&OsString::from) + .into_iter() + }) + .unwrap(); + + let metadata = args.extra_metadata(&matches); + assert_eq!( + metadata, + vec![ + ExtraMetadataSource::File(PathBuf::from("TOML_FILE1.toml"), None), + ExtraMetadataSource::Text(String::from("toml \"text1\"")), + ExtraMetadataSource::File( + PathBuf::from("TOML_FILE2.toml"), + Some(String::from("TOML.PATH")) + ), + ExtraMetadataSource::Variant(String::from("VARIANT1")), + ExtraMetadataSource::Variant(String::from("VARIANT2")), + ExtraMetadataSource::Text(String::from("toml \"text2\"")), + ExtraMetadataSource::File( + PathBuf::from("TOML_FILE3.toml"), + Some(String::from("TOML.PATH")) + ), + ExtraMetadataSource::File(PathBuf::from("TOML_FILE4.toml"), None), + ] + ); + } + #[test] fn test_auto_req() { let args = Cli::try_parse_from([""]).unwrap(); diff --git a/src/config/file_info.rs b/src/config/file_info.rs index c520735..83dfae9 100644 --- a/src/config/file_info.rs +++ b/src/config/file_info.rs @@ -161,7 +161,7 @@ impl FileInfo<'_, '_, '_, '_, '_> { rpm_file_option = rpm_file_option.is_config(); } if self.config_noreplace { - rpm_file_option = rpm_file_option.is_no_replace(); + rpm_file_option = rpm_file_option.is_config_noreplace(); } if self.doc { rpm_file_option = rpm_file_option.is_doc(); @@ -302,7 +302,7 @@ mod test { #[test] fn test_new() { - let manifest = Manifest::from_path("Cargo.toml").unwrap(); + let manifest = Manifest::from_path("./Cargo.toml").unwrap(); let metadata = manifest.package.unwrap().metadata.unwrap(); let metadata = metadata .as_table() diff --git a/src/config/metadata.rs b/src/config/metadata.rs index 5f0a379..4fb0dee 100644 --- a/src/config/metadata.rs +++ b/src/config/metadata.rs @@ -1,5 +1,6 @@ +use crate::cli::ExtraMetadataSource; use crate::error::{ConfigError, FileAnnotatedError}; -use crate::{Error, ExtraMetadataSource}; +use crate::Error; use cargo_toml::Manifest; use rpm::Scriptlet; use std::fs; @@ -111,7 +112,10 @@ pub(crate) trait TomlValueHelper<'a> { pub(super) struct ExtraMetaData(Table, ExtraMetadataSource); impl ExtraMetaData { - pub(super) fn new(source: &ExtraMetadataSource) -> Result { + pub(super) fn new( + source: &ExtraMetadataSource, + package_manifest: &PathBuf, + ) -> Result { match source { ExtraMetadataSource::File(p, branch) => { let annot: Option = Some(p.clone()); @@ -131,6 +135,16 @@ impl ExtraMetaData { .map_err(|e| FileAnnotatedError(annot, e))?; Ok(Self(table.clone(), source.clone())) } + ExtraMetadataSource::Variant(variant) => { + let annot: Option = Some(package_manifest.clone()); + let toml = fs::read_to_string(package_manifest)? + .parse::() + .map_err(|e| FileAnnotatedError(annot.clone(), e))?; + let branch = format!("package.metadata.generate-rpm.variants.{variant}"); + let table = Self::convert_toml_txt_to_table(&toml, &Some(branch)) + .map_err(|e| FileAnnotatedError(annot, e))?; + Ok(Self(table.clone(), source.clone())) + } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index a903381..b57bb6a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,7 +7,7 @@ use toml::value::Table; use crate::auto_req::{find_requires, AutoReqMode}; use crate::build_target::BuildTarget; -use crate::cli::Cli; +use crate::cli::{Cli, ExtraMetadataSource}; use crate::error::{ConfigError, Error}; use file_info::FileInfo; use metadata::{CompoundMetadataConfig, ExtraMetaData, MetadataConfig, TomlValueHelper}; @@ -15,12 +15,6 @@ use metadata::{CompoundMetadataConfig, ExtraMetaData, MetadataConfig, TomlValueH mod file_info; mod metadata; -#[derive(Debug, Clone)] -pub enum ExtraMetadataSource { - File(PathBuf, Option), - Text(String), -} - #[derive(Debug)] pub struct BuilderConfig<'a> { build_target: &'a BuildTarget, @@ -44,9 +38,9 @@ impl Config { pub fn new( project_base_path: &Path, workspace_base_path: Option<&Path>, - extra_metadata: &[ExtraMetadataSource], + extra_metadata_src: &[ExtraMetadataSource], ) -> Result { - let manifest_path = Self::create_cargo_toml_path(project_base_path); + let manifest_path = Self::create_cargo_toml_path(project_base_path)?; let manifest = if let Some(p) = workspace_base_path { // HACK when workspace used, manifest is generated from slice directly instead of @@ -58,7 +52,7 @@ impl Config { .map_err(|e| Error::FileIo(manifest_path.clone(), e))?; let mut manifest = Manifest::from_slice_with_metadata(&cargo_toml_content)?; - let workspace_manifest_path = Self::create_cargo_toml_path(p); + let workspace_manifest_path = Self::create_cargo_toml_path(p)?; let workspace_manifest = Manifest::from_path(&workspace_manifest_path).map_err(|err| match err { CargoTomlError::Io(e) => { @@ -78,9 +72,9 @@ impl Config { })? }; - let extra_metadata = extra_metadata + let extra_metadata = extra_metadata_src .iter() - .map(ExtraMetaData::new) + .map(|v| ExtraMetaData::new(v, &manifest_path)) .collect::, _>>()?; Ok(Config { @@ -90,8 +84,9 @@ impl Config { }) } - pub(crate) fn create_cargo_toml_path>(base_path: P) -> PathBuf { - base_path.as_ref().join("Cargo.toml") + pub(crate) fn create_cargo_toml_path>(base_path: P) -> Result { + let path = base_path.as_ref().join("Cargo.toml"); + path.canonicalize().map_err(|e| Error::FileIo(path, e)) } fn table_to_dependencies(table: &Table) -> Result, ConfigError> { @@ -324,6 +319,26 @@ impl Config { builder = builder.provides(dependency); } } + if let Some(recommends) = metadata.get_table("recommends")? { + for dependency in Self::table_to_dependencies(recommends)? { + builder = builder.recommends(dependency); + } + } + if let Some(supplements) = metadata.get_table("supplements")? { + for dependency in Self::table_to_dependencies(supplements)? { + builder = builder.supplements(dependency); + } + } + if let Some(suggests) = metadata.get_table("suggests")? { + for dependency in Self::table_to_dependencies(suggests)? { + builder = builder.suggests(dependency); + } + } + if let Some(enhances) = metadata.get_table("enhances")? { + for dependency in Self::table_to_dependencies(enhances)? { + builder = builder.enhances(dependency); + } + } Ok(builder) } @@ -421,9 +436,15 @@ documentation.workspace = true #[test] fn test_new() { + let cargo_toml_path = std::env::current_dir().unwrap().join("Cargo.toml"); + let config = Config::new(Path::new(""), None, &[]).unwrap(); assert_eq!(config.manifest.package.unwrap().name, "cargo-generate-rpm"); - assert_eq!(config.manifest_path, PathBuf::from("Cargo.toml")); + assert_eq!(config.manifest_path, cargo_toml_path); + + let config = Config::new(std::env::current_dir().unwrap().as_path(), None, &[]).unwrap(); + assert_eq!(config.manifest.package.unwrap().name, "cargo-generate-rpm"); + assert_eq!(config.manifest_path, cargo_toml_path); } #[test] diff --git a/src/error.rs b/src/error.rs index b369fc2..97f4298 100644 --- a/src/error.rs +++ b/src/error.rs @@ -71,6 +71,7 @@ pub enum Error { #[error(transparent)] Config(#[from] ConfigError), #[error("Invalid value of enviroment variable {0}: {1}")] + #[allow(clippy::enum_variant_names)] // Allow bad terminology for compatibility EnvError(&'static str, String), #[error(transparent)] ParseTomlFile(#[from] FileAnnotatedError), diff --git a/src/main.rs b/src/main.rs index ada624e..08ef951 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,35 +11,9 @@ mod cli; mod config; mod error; -use config::{Config, ExtraMetadataSource}; +use config::Config; use error::Error; -fn collect_metadata(args: &Cli) -> Vec { - args.metadata_overwrite - .iter() - .map(|v| { - let (file, branch) = match v.split_once('#') { - None => (PathBuf::from(v), None), - Some((file, branch)) => (PathBuf::from(file), Some(branch.to_string())), - }; - ExtraMetadataSource::File(file, branch) - }) - .chain( - args.set_metadata - .iter() - .map(|v| ExtraMetadataSource::Text(v.to_string())), - ) - .chain(args.variant.iter().map(|v| { - let file = match &args.package { - Some(package) => Config::create_cargo_toml_path(package), - None => Config::create_cargo_toml_path(""), - }; - let branch = String::from("package.metadata.generate-rpm.variants.") + v; - ExtraMetadataSource::File(file, Some(branch)) - })) - .collect::>() -} - fn determine_output_dir( output: Option<&PathBuf>, file_name: &str, @@ -53,10 +27,10 @@ fn determine_output_dir( } fn run() -> Result<(), Error> { - let (args, matcher) = Cli::get_matches_and_try_parse().unwrap_or_else(|e| e.exit()); + let (args, matches) = Cli::get_matches_and_try_parse().unwrap_or_else(|e| e.exit()); let build_target = BuildTarget::new(&args); - let extra_metadata = collect_metadata(&args); + let extra_metadata = args.extra_metadata(&matches); let config = if let Some(p) = &args.package { Config::new(Path::new(p), Some(Path::new("")), &extra_metadata)?