From 9e2790f828ddfe28c57024b6122ebaa6f31d9202 Mon Sep 17 00:00:00 2001 From: Tejas Sanap Date: Sat, 26 Jun 2021 17:51:55 +0530 Subject: [PATCH] Ability to specify the output name for a bin target different from the crate name --- .../compiler/build_context/target_info.rs | 15 +- src/cargo/core/compiler/compilation.rs | 7 +- .../compiler/context/compilation_files.rs | 3 + src/cargo/core/compiler/mod.rs | 5 +- src/cargo/core/features.rs | 3 + src/cargo/core/manifest.rs | 14 +- src/cargo/util/toml/mod.rs | 2 + src/cargo/util/toml/targets.rs | 17 +- src/doc/src/reference/unstable.md | 27 ++ tests/testsuite/binary_name.rs | 291 ++++++++++++++++++ tests/testsuite/main.rs | 1 + 11 files changed, 376 insertions(+), 9 deletions(-) create mode 100644 tests/testsuite/binary_name.rs diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 5d1259b5233..e8ed0a07381 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -103,11 +103,18 @@ impl FileType { /// The filename for this FileType that Cargo should use when "uplifting" /// it to the destination directory. pub fn uplift_filename(&self, target: &Target) -> String { - let name = if self.should_replace_hyphens { - target.crate_name() - } else { - target.name().to_string() + let name = match target.binary_filename() { + Some(name) => name, + None => { + // For binary crate type, `should_replace_hyphens` will always be false. + if self.should_replace_hyphens { + target.crate_name() + } else { + target.name().to_string() + } + } }; + format!("{}{}{}", self.prefix, name, self.suffix) } diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index f47a4e86680..3b21e4f43fd 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -365,7 +365,12 @@ impl<'cfg> Compilation<'cfg> { /// that are only relevant in a context that has a unit fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder { if unit.target.is_bin() { - cmd.env("CARGO_BIN_NAME", unit.target.name()); + let name = unit + .target + .binary_filename() + .unwrap_or(unit.target.name().to_string()); + + cmd.env("CARGO_BIN_NAME", name); } cmd.env("CARGO_CRATE_NAME", unit.target.crate_name()); cmd diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs index 3dd42f25c32..2cbc5f23861 100644 --- a/src/cargo/core/compiler/context/compilation_files.rs +++ b/src/cargo/core/compiler/context/compilation_files.rs @@ -467,6 +467,9 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { let meta = &self.metas[unit]; let meta_opt = meta.use_extra_filename.then(|| meta.meta_hash.to_string()); let path = out_dir.join(file_type.output_filename(&unit.target, meta_opt.as_deref())); + + // If, the `different_binary_name` feature is enabled, the name of the hardlink will + // be the name of the binary provided by the user in `Cargo.toml`. let hardlink = self.uplift_to(unit, &file_type, &path); let export_path = if unit.target.is_custom_build() { None diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 7007d0dd8c2..d1bd08e8204 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -985,7 +985,10 @@ fn build_base_args( let exe_path = cx .files() .bin_link_for_target(bin_target, unit.kind, cx.bcx)?; - let key = format!("CARGO_BIN_EXE_{}", bin_target.name()); + let name = bin_target + .binary_filename() + .unwrap_or(bin_target.name().to_string()); + let key = format!("CARGO_BIN_EXE_{}", name); cmd.env(&key, exe_path); } } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 31fe062ca0d..a1a3e0797e8 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -405,6 +405,9 @@ features! { // Allow to specify which codegen backend should be used. (unstable, codegen_backend, "", "reference/unstable.html#codegen-backend"), + + // Allow specifying different binary name apart from the crate name + (unstable, different_binary_name, "", "reference/unstable.html#different-binary-name"), } pub struct Feature { diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 49011ad2a24..90029e93e20 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -194,6 +194,8 @@ pub struct Target { struct TargetInner { kind: TargetKind, name: String, + // Note that `bin_name` is used for the cargo-feature `different_binary_name` + bin_name: Option, // Note that the `src_path` here is excluded from the `Hash` implementation // as it's absolute currently and is otherwise a little too brittle for // causing rebuilds. Instead the hash for the path that we send to the @@ -350,6 +352,7 @@ compact_debug! { [debug_the_fields( kind name + bin_name src_path required_features tested @@ -627,6 +630,7 @@ impl Target { inner: Arc::new(TargetInner { kind: TargetKind::Bin, name: String::new(), + bin_name: None, src_path, required_features: None, doc: false, @@ -662,6 +666,7 @@ impl Target { pub fn bin_target( name: &str, + bin_name: Option, src_path: PathBuf, required_features: Option>, edition: Edition, @@ -670,6 +675,7 @@ impl Target { target .set_kind(TargetKind::Bin) .set_name(name) + .set_binary_name(bin_name) .set_required_features(required_features) .set_doc(true); target @@ -911,11 +917,17 @@ impl Target { Arc::make_mut(&mut self.inner).name = name.to_string(); self } + pub fn set_binary_name(&mut self, bin_name: Option) -> &mut Target { + Arc::make_mut(&mut self.inner).bin_name = bin_name; + self + } pub fn set_required_features(&mut self, required_features: Option>) -> &mut Target { Arc::make_mut(&mut self.inner).required_features = required_features; self } - + pub fn binary_filename(&self) -> Option { + self.inner.bin_name.clone() + } pub fn description_named(&self) -> String { match self.kind() { TargetKind::Lib(..) => "lib".to_string(), diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 6b4f5eac154..ee5abeebac3 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1968,6 +1968,8 @@ struct TomlTarget { crate_type2: Option>, path: Option, + // Note that `filename` is used for the cargo-feature `different_binary_name` + filename: Option, test: Option, doctest: Option, bench: Option, diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index ffc72af65b1..51f367f1ed2 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -266,7 +266,14 @@ fn clean_bins( "autobins", ); + // This loop performs basic checks on each of the TomlTarget in `bins`. for bin in &bins { + // For each binary, check if the `filename` parameter is populated. If it is, + // check if the corresponding cargo feature has been activated. + if bin.filename.is_some() { + features.require(Feature::different_binary_name())?; + } + validate_target_name(bin, "binary", "bin", warnings)?; let name = bin.name(); @@ -321,8 +328,14 @@ fn clean_bins( Err(e) => anyhow::bail!("{}", e), }; - let mut target = - Target::bin_target(&bin.name(), path, bin.required_features.clone(), edition); + let mut target = Target::bin_target( + &bin.name(), + bin.filename.clone(), + path, + bin.required_features.clone(), + edition, + ); + configure(features, bin, &mut target)?; result.push(target); } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 6bdc2ac582e..52efd6b0575 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -74,6 +74,7 @@ Each new feature described below should explain how to use it. * Output behavior * [out-dir](#out-dir) — Adds a directory where artifacts are copied to. * [terminal-width](#terminal-width) — Tells rustc the width of the terminal so that long diagnostic messages can be truncated to be more readable. + * [Different binary name](#different-binary-name) — Assign a name to the built binary that is seperate from the crate name. * Compile behavior * [mtime-on-use](#mtime-on-use) — Updates the last-modified timestamp on every dependency every time it is used, to provide a mechanism to delete unused artifacts. * [doctest-xcompile](#doctest-xcompile) — Supports running doctests with the `--target` flag. @@ -1288,6 +1289,32 @@ The primary use case is to run `cargo rustc --print=cfg` to get config values for the appropriate target and influenced by any other RUSTFLAGS. +### Different binary name + +* Tracking Issue: [#9778](https://github.com/rust-lang/cargo/issues/9778) +* PR: [#9627](https://github.com/rust-lang/cargo/pull/9627) + +The `different-binary-name` feature allows setting the filename of the binary without having to obey the +restrictions placed on crate names. For example, the crate name must use only `alphanumeric` characters +or `-` or `_`, and cannot be empty. + +The `filename` parameter should **not** include the binary extension, `cargo` will figure out the appropriate +extension and use that for the binary on its own. + +The `filename` parameter is only available in the `[[bin]]` section of the manifest. + +```toml +cargo-features = ["different-binary-name"] + +[project] +name = "foo" +version = "0.0.1" + +[[bin]] +name = "foo" +filename = "007bar" +path = "src/main.rs" +``` ## Stabilized and removed features diff --git a/tests/testsuite/binary_name.rs b/tests/testsuite/binary_name.rs new file mode 100644 index 00000000000..65a4f75b534 --- /dev/null +++ b/tests/testsuite/binary_name.rs @@ -0,0 +1,291 @@ +use cargo_test_support::install::{ + assert_has_installed_exe, assert_has_not_installed_exe, cargo_home, +}; +use cargo_test_support::project; + +#[cargo_test] +fn gated() { + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + filename = "007bar" + path = "src/main.rs" + "#, + ) + .file("src/main.rs", "fn main() { assert!(true) }") + .build(); + + // Run cargo build. + p.cargo("build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]feature `different-binary-name` is required") + .run(); +} + +#[cargo_test] +// This test checks if: +// 1. The correct binary is produced +// 2. The deps file has the correct content +// 3. Fingerprinting works +// 4. `cargo clean` command works +fn binary_name1() { + // Create the project. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["different-binary-name"] + + [project] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + filename = "007bar" + path = "src/main.rs" + "#, + ) + .file("src/main.rs", "fn main() { assert!(true) }") + .build(); + + // Run cargo build. + p.cargo("build").masquerade_as_nightly_cargo().run(); + + // Check the name of the binary that cargo has generated. + // A binary with the name of the crate should NOT be created. + let foo_path = p.bin("foo"); + assert!(!foo_path.is_file()); + // A binary with the name provided in `filename` parameter should be created. + let bar_path = p.bin("007bar"); + assert!(bar_path.is_file()); + + // Check if deps file exists. + let deps_path = p.bin("007bar").with_extension("d"); + assert!(deps_path.is_file(), "{:?}", bar_path); + + let depinfo = p.read_file(deps_path.to_str().unwrap()); + + // Prepare what content we expect to be present in deps file. + let deps_exp = format!( + "{}: {}", + p.bin("007bar").to_str().unwrap(), + p.root().join("src").join("main.rs").to_str().unwrap() + ); + + // Compare actual deps content with expected deps content. + assert!( + depinfo.lines().any(|line| line == deps_exp), + "Content of `{}` is incorrect", + deps_path.to_string_lossy() + ); + + // Run cargo second time, to verify fingerprint. + p.cargo("build -p foo -v") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[FRESH] foo [..] +[FINISHED] [..] +", + ) + .run(); + + // Run cargo clean. + p.cargo("clean -p foo").masquerade_as_nightly_cargo().run(); + + // Check if the appropriate file was removed. + assert!( + !bar_path.is_file(), + "`cargo clean` did not remove the correct files" + ); +} + +#[cargo_test] +// This test checks if: +// 1. Check `cargo run` +// 2. Check `cargo test` +// 3. Check `cargo install/uninstall` +fn binary_name2() { + // Create the project. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["different-binary-name"] + + [project] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + filename = "007bar" + "#, + ) + .file( + "src/main.rs", + r#" + fn hello(name: &str) -> String { + format!("Hello, {}!", name) + } + + fn main() { + println!("{}", hello("crabs")); + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn check_crabs() { + assert_eq!(hello("crabs"), "Hello, crabs!"); + } + } + "#, + ) + .build(); + + // Run cargo build. + p.cargo("build").masquerade_as_nightly_cargo().run(); + + // Check the name of the binary that cargo has generated. + // A binary with the name of the crate should NOT be created. + let foo_path = p.bin("foo"); + assert!(!foo_path.is_file()); + // A binary with the name provided in `filename` parameter should be created. + let bar_path = p.bin("007bar"); + assert!(bar_path.is_file()); + + // Check if `cargo test` works + p.cargo("test") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] test [unoptimized + debuginfo] target(s) in [..] +[RUNNING] [..] (target/debug/deps/foo-[..][EXE])", + ) + .with_stdout_contains("test tests::check_crabs ... ok") + .run(); + + // Check if `cargo run` is able to execute the binary + p.cargo("run") + .masquerade_as_nightly_cargo() + .with_stdout("Hello, crabs!") + .run(); + + p.cargo("install").masquerade_as_nightly_cargo().run(); + + assert_has_installed_exe(cargo_home(), "007bar"); + + p.cargo("uninstall") + .with_stderr("[REMOVING] [ROOT]/home/.cargo/bin/007bar[EXE]") + .masquerade_as_nightly_cargo() + .run(); + + assert_has_not_installed_exe(cargo_home(), "007bar"); +} + +#[cargo_test] +fn check_env_vars() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["different-binary-name"] + + [project] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + filename = "007bar" + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + println!("{}", option_env!("CARGO_BIN_NAME").unwrap()); + } + "#, + ) + .file( + "tests/integration.rs", + r#" + #[test] + fn check_env_vars2() { + let value = option_env!("CARGO_BIN_EXE_007bar").expect("Could not find environment variable."); + assert!(value.contains("007bar")); + } + "# + ) + .build(); + + // Run cargo build. + p.cargo("build").masquerade_as_nightly_cargo().run(); + p.cargo("run") + .masquerade_as_nightly_cargo() + .with_stdout("007bar") + .run(); + p.cargo("test") + .masquerade_as_nightly_cargo() + .with_status(0) + .run(); +} + +#[cargo_test] +fn check_msg_format_json() { + // Create the project. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["different-binary-name"] + + [project] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + filename = "007bar" + path = "src/main.rs" + "#, + ) + .file("src/main.rs", "fn main() { assert!(true) }") + .build(); + + let output = r#" +{ + "reason": "compiler-artifact", + "package_id": "foo 0.0.1 [..]", + "manifest_path": "[CWD]/Cargo.toml", + "target": "{...}", + "profile": "{...}", + "features": [], + "filenames": "{...}", + "executable": "[ROOT]/foo/target/debug/007bar[EXE]", + "fresh": false +} + +{"reason":"build-finished", "success":true} +"#; + + // Run cargo build. + p.cargo("build --message-format=json") + .masquerade_as_nightly_cargo() + .with_json(output) + .run(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 763d66724e0..8c30bf929a8 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -13,6 +13,7 @@ mod alt_registry; mod bad_config; mod bad_manifest_path; mod bench; +mod binary_name; mod build; mod build_plan; mod build_script;