From bbaf5119ed75f08aa0df2dc7311d21c5ee4f6fdb Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Tue, 6 Aug 2024 16:50:53 -0700 Subject: [PATCH] Synthesize build scripts that Cargo failed to vendor Summary: This is a workaround for [rust-lang/cargo#14348](https://github.com/rust-lang/cargo/issues/14348), which is a regression in Rust 1.80's `cargo vendor`. 4 crates in fbsource/third-party/rust are currently impacted: - `cxx-build` - `cxxbridge-cmd` - `cxxbridge-macro` - `orjson` (https://github.com/ijl/orjson) The workaround works by writing a build.rs containing just `fn main() {}` for crates where Cargo has injected `build = "build.rs"` into their manifest without vendoring the corresponding build.rs. We can delete this workaround once the Cargo bug is fixed. Reviewed By: diliop Differential Revision: D60855541 fbshipit-source-id: 553c3e45e8f0998fb3091b1f1a82f33b2d7c0a6f --- Cargo.lock | 11 +++++++++ Cargo.toml | 1 + src/vendor.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 353c9454..bf49b4df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,16 @@ dependencies = [ "url", ] +[[package]] +name = "cargo_toml" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +dependencies = [ + "serde", + "toml 0.8.5", +] + [[package]] name = "cc" version = "1.0.90" @@ -1006,6 +1016,7 @@ version = "0.0.0" dependencies = [ "anyhow", "cached", + "cargo_toml", "clap", "dunce", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 4618eec9..faba3354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" [dependencies] anyhow = "1.0.75" cached = "0.39.0" +cargo_toml = "0.17.2" clap = { version = "4.5.11", features = ["derive", "env", "string", "unicode", "wrap_help"] } dunce = "1.0.2" env_logger = "0.10" diff --git a/src/vendor.rs b/src/vendor.rs index 3abde03f..ee8d5ce9 100644 --- a/src/vendor.rs +++ b/src/vendor.rs @@ -10,6 +10,7 @@ use std::io::ErrorKind; use std::path::Path; use anyhow::Context; +use cargo_toml::OptionalFile; use globset::GlobBuilder; use globset::GlobSetBuilder; use ignore::gitignore::GitignoreBuilder; @@ -70,6 +71,7 @@ pub(crate) fn cargo_vendor( if let Some(vendor_config) = &config.vendor { filter_checksum_files(&paths.third_party_dir, vendordir, vendor_config)?; + write_excluded_build_scripts(&paths.third_party_dir, vendordir)?; } if audit_sec { @@ -158,8 +160,8 @@ fn filter_checksum_files( for entry in fs::read_dir(third_party_dir.join(vendordir))? { let entry = entry?; - let path = entry.path(); // full/path/to/vendor/foo-1.2.3 - let checksum = path.join(".cargo-checksum.json"); // full/path/to/vendor/foo-1.2.3/.cargo-checksum.json + let manifest_dir = entry.path(); // full/path/to/vendor/foo-1.2.3 + let checksum = manifest_dir.join(".cargo-checksum.json"); // full/path/to/vendor/foo-1.2.3/.cargo-checksum.json log::trace!("Reading checksum {}", checksum.display()); @@ -181,7 +183,7 @@ fn filter_checksum_files( let mut changed = false; - let pkgdir = relative_path(third_party_dir, &path); // vendor/foo-1.2.3 + let pkgdir = relative_path(third_party_dir, &manifest_dir); // vendor/foo-1.2.3 checksums.files.retain(|k, _| { log::trace!("{}: checking {}", checksum.display(), k); @@ -204,3 +206,62 @@ fn filter_checksum_files( Ok(()) } + +// Work around https://github.com/rust-lang/cargo/issues/14348. +// +// This step can be deleted if that `cargo vendor` bug is fixed in a future +// version of Cargo. +fn write_excluded_build_scripts(third_party_dir: &Path, vendordir: &Path) -> anyhow::Result<()> { + type TomlManifest = cargo_toml::Manifest; + + let third_party_vendor = third_party_dir.join(vendordir); + if !third_party_vendor.try_exists()? { + // If there are no dependencies from a remote registry (because there + // are no dependencies whatsoever, or all dependencies are local path + // dependencies) then `cargo vendor` won't have created a "vendor" + // directory. + return Ok(()); + } + + for entry in fs::read_dir(third_party_vendor)? { + let entry = entry?; + let manifest_dir = entry.path(); // full/path/to/vendor/foo-1.2.3 + let cargo_toml = manifest_dir.join("Cargo.toml"); + + log::trace!("Reading manifest {}", cargo_toml.display()); + + let content = match fs::read_to_string(&cargo_toml) { + Ok(file) => file, + Err(err) => { + log::warn!("Failed to read {}: {}", cargo_toml.display(), err); + continue; + } + }; + + let manifest: TomlManifest = match toml::from_str(&content) { + Ok(cs) => cs, + Err(err) => { + log::warn!("Failed to deserialize {}: {}", cargo_toml.display(), err); + continue; + } + }; + + let Some(package) = &manifest.package else { + continue; + }; + let Some(OptionalFile::Path(build_script_path)) = &package.build else { + continue; + }; + + let expected_build_script = manifest_dir.join(build_script_path); + if !expected_build_script.try_exists()? { + log::trace!( + "Synthesizing build script {}", + expected_build_script.display(), + ); + fs::write(expected_build_script, "fn main() {}\n")?; + } + } + + Ok(()) +}