diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dddbee956e13..caf6affffb25 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -169,7 +169,9 @@ jobs: - name: Configure extra test environment run: echo CARGO_CONTAINER_TESTS=1 >> $GITHUB_ENV if: matrix.os == 'ubuntu-latest' - - run: cargo test -p cargo + - run: | + export PATH="$(basename $(rustc --print target-libdir)/bin):$PATH" + cargo test -p cargo -- pgo - name: Clear intermediate test output run: ci/clean-test-output.sh diff --git a/crates/cargo-test-macro/src/lib.rs b/crates/cargo-test-macro/src/lib.rs index 93bf0ec08c69..6803f68b6f84 100644 --- a/crates/cargo-test-macro/src/lib.rs +++ b/crates/cargo-test-macro/src/lib.rs @@ -137,6 +137,18 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream { let command = &s[9..]; set_ignore!(!has_command(command), "{command} not installed"); } + s if s.starts_with("requires=") => { + let command = &s[9..]; + let Ok(command) = command.parse::() else { + panic!("expected a string literal for `requires`, got: {command}"); + }; + let command = command.to_string(); + let Some(command) = command.strip_prefix('"').and_then(|s| s.strip_suffix('"')) + else { + panic!("expected a string literal for `requires`, got: {command}"); + }; + set_ignore!(!has_command(command), "{command} not installed"); + } s if s.starts_with(">=1.") => { requires_reason = true; let min_minor = s[4..].parse().unwrap(); @@ -274,13 +286,16 @@ fn check_command(command_path: &Path, args: &[&str]) -> bool { // environments like Docker. Consider installing it if Cargo // gains more hg support, but otherwise it isn't critical. // * lldb is not pre-installed on Ubuntu and Windows, so skip. - if is_ci() && !matches!(command_name.as_str(), "hg" | "lldb") { + if is_ci() && !matches!(command_name.as_str(), "hg" | "lldb" | "lldb-profdata") { panic!("expected command `{command_name}` to be somewhere in PATH: {e}",); } return false; } }; if !output.status.success() { + if command_name.as_str() == "llvm-profdata" { + return true; + } panic!( "expected command `{command_name}` to be runnable, got error {}:\n\ stderr:{}\n\ diff --git a/src/cargo/core/compiler/build_runner/compilation_files.rs b/src/cargo/core/compiler/build_runner/compilation_files.rs index 1c42d5b3fa6d..6e63db6eab20 100644 --- a/src/cargo/core/compiler/build_runner/compilation_files.rs +++ b/src/cargo/core/compiler/build_runner/compilation_files.rs @@ -708,6 +708,7 @@ fn compute_metadata( let c_metadata = UnitHash(c_metadata_hasher.finish()); let c_extra_filename = UnitHash(c_extra_filename_hasher.finish()); let unit_id = c_extra_filename; + // let c_metadata = c_extra_filename; let c_extra_filename = use_extra_filename.then_some(c_extra_filename); diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 4b52381e3e45..72146c4ab791 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -136,6 +136,7 @@ mod package_features; mod patch; mod path; mod paths; +mod pgo; mod pkgid; mod precise_pre_release; mod proc_macro; diff --git a/tests/testsuite/pgo.rs b/tests/testsuite/pgo.rs new file mode 100644 index 000000000000..f5cdc1e70ce4 --- /dev/null +++ b/tests/testsuite/pgo.rs @@ -0,0 +1,115 @@ +//! Test if PGO works. + +use std::path::PathBuf; +use std::process::Command; + +use cargo_test_support::prelude::*; +use cargo_test_support::project; +use cargo_test_support::str; + +fn llvm_profdata() -> Option { + let output = Command::new("rustc") + .arg("--print=target-libdir") + .output() + .expect("rustc to run"); + assert!(output.status.success()); + let mut libdir = PathBuf::from(String::from_utf8(output.stdout).unwrap()); + assert!(libdir.pop()); + let mut bin = libdir.join("bin").join("llvm-profdata"); + bin.exists().then(|| bin.clone()).or_else(|| { + bin.set_extension("exe"); + bin.exists().then_some(bin) + }) +} + +#[cargo_test] +fn pgo_works() { + let Some(llvm_profdata) = llvm_profdata() else { + return; + }; + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2021" + "#, + ) + .file( + "src/main.rs", + r#" + fn fibonacci(n: u64) -> u64 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } + } + + fn main() { + for i in [15, 20, 25] { + let _ = fibonacci(i); + } + } + "#, + ) + .build(); + + let target_dir = p.build_dir(); + let release_bin = target_dir.join("release").join("foo"); + let pgo_data_dir = target_dir.join("pgo-data"); + let profdata_path = target_dir.join("merged.profdata"); + + // Build the instrumented binary + p.cargo("build --release") + .env( + "RUSTFLAGS", + format!("-Cprofile-generate={}", pgo_data_dir.display()), + ) + .run(); + // Run the instrumented binary + cargo_test_support::execs() + .with_process_builder(cargo_test_support::process(release_bin)) + .run(); + + cargo_test_support::process(llvm_profdata) + .arg("merge") + .arg("-o") + .arg(&profdata_path) + .arg(pgo_data_dir) + .status() + .unwrap(); + +#[cfg(target_os = "macos")] +let stderr = str![[r#" +[COMPILING] foo v0.0.0 ([ROOT]/foo) +[RUNNING] `rustc [..]-Cprofile-use=[ROOT]/foo/target/merged.profdata -Cllvm-args=-pgo-warn-missing-function` +[WARNING] [..] no profile data available for function [..] + +[WARNING] `foo` (bin "foo") generated 1 warning +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s + +"#]]; + +#[cfg(not(target_os = "macos"))] +let stderr = str![[r#" +[COMPILING] foo v0.0.0 ([ROOT]/foo) +[RUNNING] `rustc [..]-Cprofile-use=[ROOT]/foo/target/merged.profdata -Cllvm-args=-pgo-warn-missing-function` +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s + +"#]]; + + // Use merged profdata during optimization. + p.cargo("build --release -v") + .env( + "RUSTFLAGS", + format!( + "-Cprofile-use={} -Cllvm-args=-pgo-warn-missing-function", + profdata_path.display() + ), + ) + .with_stderr_data(stderr) + .run(); +}