Squashed 'third_party/rules_rust/' changes from 078c6908f..bf9ddeb7c
bf9ddeb7c Release 0.25.1 (#2049)
db5b2fd65 Update tinyjson (#2050)
6a7872ae3 Fix prost proto packages not sanitizing to valid module names (#2044)
c080d7bfa Moved legacy protobuf rules to `proto/protobuf` (#2043)
1281cc051 Remove debug code. (#2048)
cd126be1f Fix build failure finding crate_roots when mixed with generated sources (#2041)
7f751cddd Consolidate rust_prost_library and fix extension-only proto generation. (#2047)
6118c81f2 Release 0.25.0 (#2042)
a6f29fd07 Add Prost and Tonic rules. (#2033)
9442aed8c fix: `crate_type` more accurately corresponds to CC linking actions (#1975)
4f4e2b17b Re-enable zig example on CI (#2030)
2ded0c2f5 Fix flaky coverage test in CI (#2028)
36f8251f9 Exclude .tmp_git_root from globs (#1948)
ca750fa83 Eliminate Rustfmt action in Bindgen rules. Bindgen can run rustfmt (#2025)
c55ec0cfb Allow sysroots from cc_toolchains to be added to bindgen actions (#2024)
9314b1b0c Release 0.24.1 (#2023)
92ea74ade Making rust_std attr in rust_toolchain mandatory (#1984)
a54b8e14b Update `rust_library_group` to use `DepVariantInfo` (#2022)
47644346b Release v0.24.0 (#2020)
a6b0a7f39 Rust library group (#1848)
bc43f4841 Fix crate_universe's `all_crate_deps` and `aliases` functions failing in case the crate's Cargo.toml has condtional dependencies (#2018)
8f27ec7c5 fix: load cargo manifest without resolving abs path of deps (#2017)
23f99bb63 feature: `target_compatible_with` added to `CommonAttrs` (#1976)
11f8c9875 Make `rust_doc_test` inherit it's crate aliases attribute (#2007)
8e848414d Regenerated crate_universe outputs for all packages (#2011)
1b6365131 Don't use startup:windows (#2012)
e80582e75 Fix thumbv* platform resolution (#2010)
367f90ef0 Update bindgen version to 0.65.1 (#2008)
e6ed5bf90 Release 0.23.0 (#2003)
93b230bb8 Fix code coverage collection. (#2001)
0a14bfbb0 Minor CI and test cleanup (#2004)
3e2ee941a Update bindgen rules to build clang from source. (#1998)
5a1a7577d Split up cargo_build_script tests (#2002)
eb6413e83 Update various bash scripts to pipe errors to stderr (#1999)
affe947ac Update stardoc version (#1997)
7073146f8 Add support for armv8-m (#1993)
73a06f130 Added Rust 1.70.0 (#1991)
23c20a93f Fixes crates_vendor workspace name detection when using bzlmod (#1990)
f5813fa08 Set windows flags in platform-specific bazelrc (#1988)
c1632b5b5 Fix up anchor link (#1987)
56e760487 Fix typo in crate_universe-generated defs.bzl comment (#1981)
94cbe4c2c Symlink in the exec-root so that relative paths will work, unchanged. (#1781)
af8ef62eb Release 0.22.0 (#1974)
4aaa6de30 Allow specifying exec and target compatibility constraints (#1971)
f1b19c394 Update rules_apple in tests (#1972)
937e63399 Add T2 support for x86_64-unknown-none (#1967)
66b1bf165 fix: lld-link (MSVC) fix flags including `-l` prefix (#1958)
285dcbbb9 feature: expose `extra_rustc_flags` and `extra_exec_rustc_flags` at `rust_register_toolchains` (#1959)
0f25cb462 Removed `rust_toolchain.os` in favor of `rust_toolchain.exec_triple`. (#1960)
a2a1109dc Add T2 support for thumbv7em-none-eabi (#1957)
80f0eb488 Support for `no_std` mode (#1934)
99aaf0830 Rename crates_vendor_manifests to cvm to shorten windows path lengths (#1944)
0a57da049 Added tests for build script dependencies to crate_universe (#1943)
caffb0a08 Release 0.21.1 (#1936)
c869a17c7 Fix regression in building zlib (#1935)
24b9dea4f Release 0.21.0 (#1933)
7677c617e Add support for rustc flags to `rust_proto_library` (#1932)
fa304ae48 Updated zlib BUILD file to support darwin-arm64 (#1931)
a86313282 Added Rust 1.69.0 (#1930)
f0e12c707 Make BuildInfo provider public (#1920)
c6ad23aba Respect `#[global_allocator]` in `cc_common.link` builds (#1926)
d78752504 Exclude target directory from release tars (#1922)
0339cd18a [wasm-bindgen] Update to v0.2.84 (#1919)
07af5678e Handle corner case for windows architecture detection (#1915)
c56e7660d Fix optional deps by platform (#1911)
4663ff6a3 cc_common_link: also respect --custom_malloc if set (#1912)
dab425760 Add Rust 1.68.2 (#1908)
e4bd39f95 Add empty rustfmt.toml (#1907)
eaf513865 Support bzlmod (#1528)
1074ecbab Release v0.20.0 (#1900)
44aec0a79 ci: fix test config in cc_common_link_ubuntu2004 (#1904)
6571cde64 Adds per_crate_rustc_flag build setting. (#1827)
7a47449df Added Rust 1.68.1 (#1898)
e3bcdbad1 Fixed rustdoc warnings in crate_universe (#1897)
529f45900 Added `rustdoc_flags` attribute to rust_doc rule (#1867)
9e3499405 Have rustdoc return its output directory instead of zip default. (#1868)
9d6741f40 Implement support for optional crates enabled with dep: features (#1885)
fd10963ea Skip adding -lstatic to libtest and libstd on Darwin (#1620)
b3314b45e Release 0.19.1 (#1895)
c1a9fd86f Accumulate all features from cargo tree output (#1884)
206f71c95 Disable zig example (#1893)
1a5d07cd2 Add runfiles support to rust_stdlib_filegroup (#1890)
6996cd550 Deleted unused targets and cleanup docs (#1889)
a85e24e20 Fix triple constraints for iOS and watchOS (#1888)
e13fd3bad Release rules_rust and cargo-bazel (#1882)
9e9853d63 Add support for thumbv7em with hard float (#1871)
b3cd5962e Added Rust 1.68.0 (#1866)
f1b7aedf5 Support sparse indexes (#1857)
7f2dd433a Make fetch_shas work with mktemp from coreutils 8.32 (#1870)
a9cc01230 Update crate_universe dependencies (#1872)
c038e94ae Pipe stderr from cargo tree processes (#1879)
222d60322 Parallelize cargo tree calls (#1874)
cdbbf7131 Add Fuchsia platform support (#1833)
17e5b04c2 Use `_make_link_flags_darwin` when target os is `ios`. (#1843)
d9ecc8df4 crate_universe: Support fetching crates with git branch, tag or rev (#1846)
1c694cd60 Forward `toolchains` to `cargo_build_script` targets (#1862)
9affcbfa7 Skip detecting abi for empty values (#1830)
6193fe823 Re-enable crate_universe MacOS tests (#1861)
c25db95ae Updated Rust to 1.67.1 (#1864)
7b8fd06be Support `[patch]` in crate_universe when using multiple `Cargo.toml`s (#1856)
c645fe399 Silence windows build failure (#1863)
75bba7b50 Make rust_clippy providers match rustfmt_test (#1806)
f09d72755 Fix test assertion for arm64 macs (#1845)
f4113bfe1 Fix tests for new Apple toolchain (#1844)
20ce44e30 fix: use target_triple struct instead of string (#1835)
bdbded181 Fix code example in doc (#1838)
4f4014052 Fix typo: plced -> placed (#1834)
baeb0664d Remove ios/android/wasm support for gen_rust_project deps (#1684)
02557a47a Add `render_config` attribute to `crates_vendor`. (#1832)
4357fb154 Updated rules_rust to version 0.18.0 (#1829)
9adfdca9b Various cleanups (#1828)
4fa412385 Added update known shas to include T1-T2 triples (#1824)
905731ad9 Instructions on how to perform `rustfmt` check (#1822) (#1823)
108b1a187 Encapsulate running cargo into a struct (#1815)
57a099b63 Fixes resolver issue with root packages and another dependency format (#1819)
78ca9ba0a Use env method recently added to cargo_metadata (#1813)
92834930f Updated `rust_toolchain.target_json` to take encoded json strings (#1810)
84f1d0657 support `resolver = "2"` target-specific features (#1710)
a5853fd37 Use correct dynamic link args fro proc-macro crates (#1803)
b656e2553 Added tests for the `triple` constructor (#1811)
ea4a79ad9 Disable job in CI to avoid infrastructure failure. (#1816)
2fc02f030 Delete `rust_toolchain.rusrc_srcs` (#1807)
804d5fc1f Convert `rust_toolchain` attrs `exec_triple` and `target_triple` to structs (#1808)
499a2ca38 Updated platform triple values from strings to structs ("triple") (#1804)
aae1dbdcb Unify functions for computing constraint values for platform triple abi (#1805)
0d6d2b1eb Updated rules_rust version to `0.17.0` (#1800)
88e83f2df Added Rust 1.67.0 (#1799)
6922b5012 rustdoc_test: fix and test OUT_DIR (#1779)
ad01d1b0e [crate_universe] add an annotation to disable pipelining (#1733)
f651cd18f Add `CARGO_BAZEL_REPIN_ONLY` repinning allowlist (#1798)
d7f0debb0 Revert "Disable broken clang and ldd CI jobs (#1785)" (#1796)
96f82aaad Fix `cc_common.link` file output name (#1795)
5079b64d5 Fix use of `rustfmt_toolchain` when `rustc` is not provided (#1794)
23c650f35 Have `--experimental_use_cc_common_link` cover `rust_shared_library` (#1792)
ba0fb5956 Added support for `--nolegacy_external_runfiles` to `rust_doc_test` (#1790)
112242bb7 Prevent crates_vendor from restarting bazel. (#1791)
52231ef9f Added compatibility flags to `.bazelrc` to prevent regressions (#1789)
91cd399a0 Add "crate-name={}" tag to Crate Universe targets (#1787)
1b1dae196 Added Rust 1.66.1 (#1767)
fe17e8b8e Add file:// prefix to env var in docs (#1788)
0fe742bff Updated `rust_bindgen` to use `rustfmt_toolchain` (#1770)
042fd6c1c Update docs on setting Rust versions (#1786)
dddd8a0d4 Updated crate_universe dependencies (#1775)
a1330a71f Download `rustc` in `rustfmt_toolchain_repository` (#1769)
e96aad9aa Updated the ios_build example to use `crates_vendor` (#1778)
e315007df Disable broken clang and ldd CI jobs (#1785)
4e89d52a9 rustdoc_test: substitute the root of the current crate (#1777)
a52041fb5 Support `target_settings` in `rust_repository_set` and `rust_toolchain_repository` (#1758)
49906eb29 Update clippy and rustfmt aspects to require CrateInfo providers (#1772)
85564208e Updated rules_rust version to `0.16.1` (#1761)
614499a5b Fixed inability to deserialize crate_universe lockfiles (#1760)
9803d3034 Fix data and compile_data for rust_doc (#1741)
927a364cb Update Release github pipeline to trigger automatically (#1757)
7d03e05f8 Fix release pipeline (#1756)
cf7ca5dfd Updated rules_rust to version `0.16.0` (#1750)
203fe4b9a Remove unnecessary binary file (#1755)
941c7cca9 Don't propagate `compatible_with` to the underlying `cargo_build_script` `rust_binary` target (#1754)
a31490d9a Make loads from @rules_rust//rust:defs.bzl come out on one line (#1753)
7ebad4d50 Generate only the needed subset of binaries for bindgen and proto (#1751)
4ef3d4aaa Repin examples/crate_universe_unnamed (#1752)
d6e300359 Regenerate BUILD files using serde_starlark renderer (#1746)
e7c8a97d1 Convert BUILD.$name-$version.bazel to serde_starlark (#1743)
c09818d3b Exclude generated files from language stats and collapse in code review (#1747)
26a24f030 Added CI for single toolchain channel workspaces (#1712)
caed7d814 Report context on error failing to get version (#1744)
36b57af7b Add gen_binaries annotation to control which bins to make target for (#1718)
d916a6f52 crate_universe re-pinning now defaults to "workspace" (#1723)
f34661ee1 Propagate `compatible_with` attribute to the underlying `_build_script_run` target (#1745)
92977d1bf Re-pinned all dependencies managed by crate_universe (#1735)
d5289ad1c Added `rustfmt_toolchain` and refactored toolchain repository rules (#1719)
532e60ff0 Collect targets in a deterministic order (#1736)
52e02c25b Eliminate all use of hash-based collections from crate_universe (#1737)
31073ff8e Replace tera template with serde_starlark (#1734)
d4e5586d0 Support the RUNFILES_DIR environment variable. (#1732)
1357b85b1 Addressed clippy warnings from `clippy 0.1.67 (ec56537c 2022-12-15)` (#1717)
8bc9f788d Support dsym_folder output group in tests (#1703)
90c5b6eb7 Added CI for minimum supported Rust version (#1720)
be82ff8bd Match prerelease versions with annotation wildcard (#1716)
36c7f285b Arm Thumb Embedded Targets. (#1721)
5ef52e465 Update current_toolchain_files tests to use a dedicated test rule (#1714)
c75ea6f9e Add `Runfiles::current_repository` to runfiles library (#1713)
2f0511782 Updated rules_rust to version `0.15.0` (#1706)
019f87178 Added Rust 1.66.0 (#1705)
1469cd7cb Fix labels to work with canonical label literals. (#1700)
5826a500a Add riscv32imc and riscv64gc to the known sha targets (#1698)
40dee95ce Fixed typos: normla -> normal (#1699)
8f08e77ac load_arbitrary_tool uses tool_suburl to look up sha256 (#1695)
8faec3060 Fix typos in crate_universe rendered comments (#1691)
bd64711ff Silence flaky test (#1693)
46b7ea5af Added a build setting for toolchain channels (#1671)
70b272aad Updated rules_rust to version `0.14.0` (#1669)
91e597dd1 Updated all crates_vendor outputs (#1687)
9a047b0b9 Updated crate_universe dependencies (#1686)
3a91d2f5b Add RV64GC target (#1683)
d9e752ab4 Add per-toolchain `rustc_flags` (#1635)
56237415e stardoc: Use backtick not `<code>` for attr default values (#1682)
d4b31a494 Allow passing a bazel path to vendor explicitly (#1661)
d51bf9ce0 Updated crate_universe to work with `--nolegacy_external_runfiles` (#1680)
7f40636d1 crate_universe/private/crates_vendor.bzl typo fix (#1678)
025bf7db8 Merge cc toolchain flags into build script env (#1675)
b7c36c051 Fix confusing/misleading crate_universe docs (#1677)
29233e354 Revert #1564 (#1663)
ed32b6de2 Common glob excludes (#1673)
61b99cdd1 fix: add space to crate data exclude list (#1665)
8bb25b8b7 Support Windows ARM64 (aarch64-pc-windows-msvc) (#1664)
ddf2a4c23 Re-render crate BUILD files after #1647 (#1655)
44c7e1588 Group deps and aliases by platform triple rather than by cfg string when generating BUILD files. This avoid bazel errors due to duplicate keys/deps. (#1647)
de18d8bb6 Allow `buildifier` attribute to be a file (#1660)
aa0815dc9 Fix naming of ambiguous libs (#1625)
ff314d4ab Also pass -c opt to tests in opt mode CI (#1626)
ff4e90515 Reenable windows job (#1658)
c45b8e91f Updated rules_rust to version `0.13.0` (#1644)
87d6b6c37 Update `//util/label` to support `+` in packages (#1654)
ab6959db5 fix: Fix issue with wasi-0.11.0+wasi-snapshot-preview1 (#1632)
28c090ed0 Replaced custom platform constraint values with aliases to `@platforms` (#1652)
dfbea4f52 Deprecated `rust_toolchain.rustc_srcs` (#1653)
fd1db4391 Remove deprecated attributes from rust_toolchain and cargo_bootstrap (#1651)
c8ab970c4 Generated rust-project.json files now include sysroot paths (#1641)
0a3e04cf9 Fix vendoring when not in a package (#1646)
aece1e37d Deduplicate expand_location targets in rust-project.json crate creation to avoid a bazel crash (#1639)
03a0b2483 [docs] Fixing typos in CargoConfig doc strings (#1638)
bd4fd2ac5 Upgraded cfg-expr dependency to 0.12.0. (#1636)
330554a13 Disable failing job in CI (#1640)
849f106e6 Consider compilation mode when choosing `pic`/`nopic` object files to link (#1624)
53491d719 Updated rules_rust to version `0.12.0` (#1630)
8e8843724 Remove empty glob (#1628)
1f621a944 Added Rust 1.65.0 (#1627)
c6af4d025 Add `-c opt` mode to CI (#1621)
95320cc8b process_wrapper: print line on error to parse message as json (#1566)
81eaccf39 Fixed CI breakage (#1619)
478fc3a57 Fix ambiguous native dependencies in `proc_macro`s and `staticlib`s (#1611)
9e3d8415e Build deps of _build_script_run in 'exec' mode (#1561)
ea031082b Fixed outdated docs (#1614)
a8c540e49 Restore support for old cargo_build_script load statements (#1613)
295b5ccc7 Renamed `_build_script_run` rule to `cargo_build_script` (#1612)
3778069ec Remove references to Google mirror in docs (#1607)
aad54ba29 Updated crate_universe dependencies (#1606)
c349df2a6 Remove Google mirror from Starlark snippet in release notes (#1604)
0493b998d Avoid rendering a mock root package when possible (#1596)
b04aa053c process_wrapper: Apply substitutions to param files (#1565)
b209b3e15 Updated rules_rust to version `0.11.0`. (#1587)
b1079453b Typo correction on doc (#1593)
ca5678266 Updated crate_universe dependencies (#1591)
a364d448f Fixes crates_vendor labels in remote mode when used from the root workspace (#1575)
1cc37c268 Expose the output directory from cargo_build_script (#1588)
7ffe0a555 Ignore non-utf8 text in build script output (#1583)
c5b38fe00 Merge runfiles from transitive dependencies of rust_test crate attr (#1487)
da3d522d5 Fix build scripts targeting the wrong architecture (#1564)
d288ed634 Add `out_dir` support in `cargo_dep_env` (#1571)
78d6c1b46 fix: incorrect rustfmt edition query (#1582)
48927127e Set CARGO_MANIFEST_DIR at runtime for tests (#1559)
76bd69033 Add an output group for the .rmeta (#1585)
352bfeb05 Cleanup deprecated code (#1577)
86dc561f9 Move crate_root_src to utils (#1570)
beb554eb9 update to wasm-bindgen v0.2.83 (#1567)
73fd1616b Export AbsoluteLabel functionality (#1568)
c57e7a399 Remap $PWD to empty string instead of "." (#1563)
f0cdcedc2 Added Rust 1.64.0 (#1562)
1d326554a Update docs to show release policies and support (#1560)
78c793a0a Fix markdown typo in rust_analyzer.md (#1553)
c13980fb6 Add iOS examples (#1546)
8a5e07e9f Update apple_support (#1549)
6dacd9803 Strip leading '@'s for labels in the splicing manifest (#1547)
f73d1d6fb use crate_info.deps in establish_cc_info (#1543)
4845af6c0 Add android example (#1545)
9570b7aa7 Remove -lgcc from Android builds (#1541)
cb9ca1b81 Fix crate_universe/private/srcs.bzl to work with repo mappings (#1540)
d1fc9accc Minor cleanup of CI pipelines (#1534)
2bb077b3b Updated rules_rust to version 0.10.0 (#1533)
b8751b860 add cc config info to dummy wasm32 cc toolchain (#1532)
f5ed797ee Updates rust_test to use main.rs as the root when use_libtest_harness is false (#1518)
cfcaf21d5 Preserve directory structure of source files when some are generated (#1526)
51c065841 migrating to rbe_preconfig and remove bazel_toolchains (#1524)
055abd402 Fix typo in an example of crates_repository rule (#1520)
8bfed1cd2 Added Rust 1.63.0 (#1512)
3a69ce09b Update wasm_bindgen to 0.2.82 (#1513)
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf9ddeb7c83a9fe8a7d87c76134cdd8e16131b62
Signed-off-by: Adam Snaider <adsnaider@gmail.com>
Change-Id: Id9490c68d6221da66953a915a25042ef8b848505
diff --git a/crate_universe/src/cli.rs b/crate_universe/src/cli.rs
index fdff844..ade755c 100644
--- a/crate_universe/src/cli.rs
+++ b/crate_universe/src/cli.rs
@@ -7,10 +7,10 @@
use clap::Parser;
-use self::generate::GenerateOptions;
-use self::query::QueryOptions;
-use self::splice::SpliceOptions;
-use self::vendor::VendorOptions;
+pub use self::generate::GenerateOptions;
+pub use self::query::QueryOptions;
+pub use self::splice::SpliceOptions;
+pub use self::vendor::VendorOptions;
// Entrypoints
pub use generate::generate;
diff --git a/crate_universe/src/cli/generate.rs b/crate_universe/src/cli/generate.rs
index 3fdc97e..925aecd 100644
--- a/crate_universe/src/cli/generate.rs
+++ b/crate_universe/src/cli/generate.rs
@@ -9,8 +9,7 @@
use crate::config::Config;
use crate::context::Context;
use crate::lockfile::{lock_context, write_lockfile};
-use crate::metadata::load_metadata;
-use crate::metadata::Annotations;
+use crate::metadata::{load_metadata, Annotations, Cargo};
use crate::rendering::{write_outputs, Renderer};
use crate::splicing::SplicingManifest;
@@ -74,7 +73,12 @@
let context = Context::try_from_path(lockfile)?;
// Render build files
- let outputs = Renderer::new(config.rendering).render(&context)?;
+ let outputs = Renderer::new(
+ config.rendering,
+ config.supported_platform_triples,
+ config.generate_target_compatible_with,
+ )
+ .render(&context)?;
// Write the outputs to disk
write_outputs(outputs, &opt.repository_dir, opt.dry_run)?;
@@ -84,10 +88,10 @@
}
// Ensure Cargo and Rustc are available for use during generation.
- let cargo_bin = match &opt.cargo {
+ let cargo_bin = Cargo::new(match opt.cargo {
Some(bin) => bin,
None => bail!("The `--cargo` argument is required when generating unpinned content"),
- };
+ });
let rustc_bin = match &opt.rustc {
Some(bin) => bin,
None => bail!("The `--rustc` argument is required when generating unpinned content"),
@@ -102,27 +106,29 @@
// Load Metadata and Lockfile
let (cargo_metadata, cargo_lockfile) = load_metadata(metadata_path)?;
- // Copy the rendering config for later use
- let render_config = config.rendering.clone();
-
// Annotate metadata
let annotations = Annotations::new(cargo_metadata, cargo_lockfile.clone(), config.clone())?;
- // Generate renderable contexts for earch package
+ // Generate renderable contexts for each package
let context = Context::new(annotations)?;
// Render build files
- let outputs = Renderer::new(render_config).render(&context)?;
+ let outputs = Renderer::new(
+ config.rendering.clone(),
+ config.supported_platform_triples.clone(),
+ config.generate_target_compatible_with,
+ )
+ .render(&context)?;
// Write outputs
write_outputs(outputs, &opt.repository_dir, opt.dry_run)?;
- // Ensure Bazel lockfiles are written to disk so future generations can be short-circuted.
+ // Ensure Bazel lockfiles are written to disk so future generations can be short-circuited.
if let Some(lockfile) = opt.lockfile {
let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
let lock_content =
- lock_context(context, &config, &splicing_manifest, cargo_bin, rustc_bin)?;
+ lock_context(context, &config, &splicing_manifest, &cargo_bin, rustc_bin)?;
write_lockfile(lock_content, &lockfile, opt.dry_run)?;
}
diff --git a/crate_universe/src/cli/query.rs b/crate_universe/src/cli/query.rs
index 19087ab..51bacaa 100644
--- a/crate_universe/src/cli/query.rs
+++ b/crate_universe/src/cli/query.rs
@@ -9,6 +9,7 @@
use crate::config::Config;
use crate::context::Context;
use crate::lockfile::Digest;
+use crate::metadata::Cargo;
use crate::splicing::SplicingManifest;
/// Command line options for the `query` subcommand
@@ -66,14 +67,11 @@
&lockfile,
&config,
&splicing_manifest,
- &opt.cargo,
+ &Cargo::new(opt.cargo),
&opt.rustc,
)?;
if digest != expected {
- return announce_repin(&format!(
- "Digests do not match: {:?} != {:?}",
- digest, expected
- ));
+ return announce_repin(&format!("Digests do not match: {digest:?} != {expected:?}",));
}
// There is no need to repin
@@ -81,7 +79,7 @@
}
fn announce_repin(reason: &str) -> Result<()> {
- eprintln!("{}", reason);
+ eprintln!("{reason}");
println!("repin");
Ok(())
}
diff --git a/crate_universe/src/cli/splice.rs b/crate_universe/src/cli/splice.rs
index 213ee34..207c33e 100644
--- a/crate_universe/src/cli/splice.rs
+++ b/crate_universe/src/cli/splice.rs
@@ -6,7 +6,10 @@
use clap::Parser;
use crate::cli::Result;
-use crate::metadata::{write_metadata, CargoUpdateRequest, Generator, MetadataGenerator};
+use crate::config::Config;
+use crate::metadata::{
+ write_metadata, Cargo, CargoUpdateRequest, FeatureGenerator, Generator, MetadataGenerator,
+};
use crate::splicing::{generate_lockfile, Splicer, SplicingManifest, WorkspaceMetadata};
/// Command line options for the `splice` subcommand
@@ -22,7 +25,7 @@
pub cargo_lockfile: Option<PathBuf>,
/// The desired update/repin behavior
- #[clap(long, env = "CARGO_BAZEL_REPIN", default_missing_value = "true")]
+ #[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
pub repin: Option<CargoUpdateRequest>,
/// The directory in which to build the workspace. If this argument is not
@@ -42,6 +45,10 @@
#[clap(long)]
pub cargo_config: Option<PathBuf>,
+ /// The path to the config file (containing `cargo_bazel::config::Config`.)
+ #[clap(long)]
+ pub config: PathBuf,
+
/// The path to a Cargo binary to use for gathering metadata
#[clap(long, env = "CARGO")]
pub cargo: PathBuf,
@@ -72,25 +79,39 @@
// Splice together the manifest
let manifest_path = splicer.splice_workspace(&opt.cargo)?;
+ let cargo = Cargo::new(opt.cargo);
+
// Generate a lockfile
let cargo_lockfile = generate_lockfile(
&manifest_path,
&opt.cargo_lockfile,
- &opt.cargo,
+ cargo.clone(),
&opt.rustc,
&opt.repin,
)?;
+ let config = Config::try_from_path(&opt.config)?;
+
+ let feature_map = FeatureGenerator::new(cargo.clone(), opt.rustc.clone()).generate(
+ manifest_path.as_path_buf(),
+ &config.supported_platform_triples,
+ )?;
// Write the registry url info to the manifest now that a lockfile has been generated
- WorkspaceMetadata::write_registry_urls(&cargo_lockfile, &manifest_path)?;
+ WorkspaceMetadata::write_registry_urls_and_feature_map(
+ &cargo,
+ &cargo_lockfile,
+ feature_map,
+ manifest_path.as_path_buf(),
+ manifest_path.as_path_buf(),
+ )?;
let output_dir = opt.output_dir.clone();
// Write metadata to the workspace for future reuse
let (cargo_metadata, _) = Generator::new()
- .with_cargo(opt.cargo)
+ .with_cargo(cargo)
.with_rustc(opt.rustc)
- .generate(&manifest_path.as_path_buf())?;
+ .generate(manifest_path.as_path_buf())?;
let cargo_lockfile_path = manifest_path
.as_path_buf()
diff --git a/crate_universe/src/cli/vendor.rs b/crate_universe/src/cli/vendor.rs
index 0b90541..5d99bc2 100644
--- a/crate_universe/src/cli/vendor.rs
+++ b/crate_universe/src/cli/vendor.rs
@@ -12,8 +12,8 @@
use crate::config::{Config, VendorMode};
use crate::context::Context;
use crate::metadata::CargoUpdateRequest;
-use crate::metadata::{Annotations, VendorGenerator};
-use crate::metadata::{Generator, MetadataGenerator};
+use crate::metadata::FeatureGenerator;
+use crate::metadata::{Annotations, Cargo, Generator, MetadataGenerator, VendorGenerator};
use crate::rendering::{render_module_label, write_outputs, Renderer};
use crate::splicing::{generate_lockfile, Splicer, SplicingManifest, WorkspaceMetadata};
@@ -52,8 +52,8 @@
/// The desired update/repin behavior. The arguments passed here are forward to
/// [cargo update](https://doc.rust-lang.org/cargo/commands/cargo-update.html). See
- /// [metadata::CargoUpdateRequest] for details on the values to pass here.
- #[clap(long, env = "CARGO_BAZEL_REPIN", default_missing_value = "true")]
+ /// [crate::metadata::CargoUpdateRequest] for details on the values to pass here.
+ #[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
pub repin: Option<CargoUpdateRequest>,
/// The path to a Cargo metadata `json` file.
@@ -130,26 +130,39 @@
.splice_workspace(&opt.cargo)
.context("Failed to splice workspace")?;
+ let cargo = Cargo::new(opt.cargo);
+
// Gather a cargo lockfile
let cargo_lockfile = generate_lockfile(
&manifest_path,
&opt.cargo_lockfile,
- &opt.cargo,
+ cargo.clone(),
&opt.rustc,
&opt.repin,
)?;
+ // Load the config from disk
+ let config = Config::try_from_path(&opt.config)?;
+
+ let feature_map = FeatureGenerator::new(cargo.clone(), opt.rustc.clone()).generate(
+ manifest_path.as_path_buf(),
+ &config.supported_platform_triples,
+ )?;
+
// Write the registry url info to the manifest now that a lockfile has been generated
- WorkspaceMetadata::write_registry_urls(&cargo_lockfile, &manifest_path)?;
+ WorkspaceMetadata::write_registry_urls_and_feature_map(
+ &cargo,
+ &cargo_lockfile,
+ feature_map,
+ manifest_path.as_path_buf(),
+ manifest_path.as_path_buf(),
+ )?;
// Write metadata to the workspace for future reuse
let (cargo_metadata, cargo_lockfile) = Generator::new()
- .with_cargo(opt.cargo.clone())
+ .with_cargo(cargo.clone())
.with_rustc(opt.rustc.clone())
- .generate(&manifest_path.as_path_buf())?;
-
- // Load the config from disk
- let config = Config::try_from_path(&opt.config)?;
+ .generate(manifest_path.as_path_buf())?;
// Annotate metadata
let annotations = Annotations::new(cargo_metadata, cargo_lockfile.clone(), config.clone())?;
@@ -158,7 +171,12 @@
let context = Context::new(annotations)?;
// Render build files
- let outputs = Renderer::new(config.rendering.clone()).render(&context)?;
+ let outputs = Renderer::new(
+ config.rendering.clone(),
+ config.supported_platform_triples.clone(),
+ config.generate_target_compatible_with,
+ )
+ .render(&context)?;
// Cache the file names for potential use with buildifier
let file_names: BTreeSet<PathBuf> = outputs.keys().cloned().collect();
@@ -181,7 +199,7 @@
// Vendor the crates from the spliced workspace
if matches!(config.rendering.vendor_mode, Some(VendorMode::Local)) {
- VendorGenerator::new(opt.cargo.clone(), opt.rustc.clone())
+ VendorGenerator::new(cargo, opt.rustc.clone())
.generate(manifest_path.as_path_buf(), &vendor_dir)
.context("Failed to vendor dependencies")?;
}
diff --git a/crate_universe/src/config.rs b/crate_universe/src/config.rs
index 0560829..6f1b8ce 100644
--- a/crate_universe/src/config.rs
+++ b/crate_universe/src/config.rs
@@ -11,11 +11,12 @@
use cargo_lock::package::GitReference;
use cargo_metadata::Package;
use semver::VersionReq;
-use serde::de::Visitor;
+use serde::de::value::SeqAccessDeserializer;
+use serde::de::{Deserializer, SeqAccess, Visitor};
use serde::{Deserialize, Serialize, Serializer};
/// Representations of different kinds of crate vendoring into workspaces.
-#[derive(Debug, Serialize, Deserialize, Hash, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum VendorMode {
/// Crates having full source being vendored into a workspace
@@ -37,7 +38,7 @@
}
}
-#[derive(Debug, Default, Hash, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct RenderConfig {
/// The name of the repository being rendered
@@ -76,10 +77,29 @@
/// The command to use for regenerating generated files.
pub regen_command: String,
- /// An optional configuration for rendirng content to be rendered into repositories.
+ /// An optional configuration for rendering content to be rendered into repositories.
pub vendor_mode: Option<VendorMode>,
}
+// Default is manually implemented so that the default values match the default
+// values when deserializing, which involves calling the vairous `default_x()`
+// functions specified in `#[serde(default = "default_x")]`.
+impl Default for RenderConfig {
+ fn default() -> Self {
+ RenderConfig {
+ repository_name: String::default(),
+ build_file_template: default_build_file_template(),
+ crate_label_template: default_crate_label_template(),
+ crates_module_template: default_crates_module_template(),
+ crate_repository_template: default_crate_repository_template(),
+ default_package_name: Option::default(),
+ platforms_template: default_platforms_template(),
+ regen_command: String::default(),
+ vendor_mode: Option::default(),
+ }
+ }
+}
+
fn default_build_file_template() -> String {
"//:BUILD.{name}-{version}.bazel".to_owned()
}
@@ -141,8 +161,11 @@
},
}
-#[derive(Debug, Default, Hash, Deserialize, Serialize, Clone)]
+#[derive(Debug, Default, Deserialize, Serialize, Clone)]
pub struct CrateAnnotations {
+ /// Which subset of the crate's bins should get produced as `rust_binary` targets.
+ pub gen_binaries: Option<GenBinaries>,
+
/// Determins whether or not Cargo build scripts should be generated for the current package
pub gen_build_script: Option<bool>,
@@ -174,6 +197,9 @@
/// [compile_data](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-compile_data) attribute.
pub compile_data_glob: Option<BTreeSet<String>>,
+ /// If true, disables pipelining for library targets generated for this crate.
+ pub disable_pipelining: bool,
+
/// Additional data to pass to the target's
/// [rustc_env](https://bazelbuild.github.io/rules_rust/defs.html#rust_library-rustc_env) attribute.
pub rustc_env: Option<BTreeMap<String, String>>,
@@ -261,42 +287,20 @@
type Output = CrateAnnotations;
fn add(self, rhs: Self) -> Self::Output {
- let shallow_since = if self.shallow_since.is_some() {
- self.shallow_since
- } else if rhs.shallow_since.is_some() {
- rhs.shallow_since
- } else {
- None
- };
-
- let patch_tool = if self.patch_tool.is_some() {
- self.patch_tool
- } else if rhs.patch_tool.is_some() {
- rhs.patch_tool
- } else {
- None
- };
-
- let gen_build_script = if self.gen_build_script.is_some() {
- self.gen_build_script
- } else if rhs.gen_build_script.is_some() {
- rhs.gen_build_script
- } else {
- None
- };
-
let concat_string = |lhs: &mut String, rhs: String| {
- *lhs = format!("{}{}", lhs, rhs);
+ *lhs = format!("{lhs}{rhs}");
};
#[rustfmt::skip]
let output = CrateAnnotations {
- gen_build_script,
+ gen_binaries: self.gen_binaries.or(rhs.gen_binaries),
+ gen_build_script: self.gen_build_script.or(rhs.gen_build_script),
deps: joined_extra_member!(self.deps, rhs.deps, BTreeSet::new, BTreeSet::extend),
proc_macro_deps: joined_extra_member!(self.proc_macro_deps, rhs.proc_macro_deps, BTreeSet::new, BTreeSet::extend),
crate_features: joined_extra_member!(self.crate_features, rhs.crate_features, BTreeSet::new, BTreeSet::extend),
data: joined_extra_member!(self.data, rhs.data, BTreeSet::new, BTreeSet::extend),
data_glob: joined_extra_member!(self.data_glob, rhs.data_glob, BTreeSet::new, BTreeSet::extend),
+ disable_pipelining: self.disable_pipelining || rhs.disable_pipelining,
compile_data: joined_extra_member!(self.compile_data, rhs.compile_data, BTreeSet::new, BTreeSet::extend),
compile_data_glob: joined_extra_member!(self.compile_data_glob, rhs.compile_data_glob, BTreeSet::new, BTreeSet::extend),
rustc_env: joined_extra_member!(self.rustc_env, rhs.rustc_env, BTreeMap::new, BTreeMap::extend),
@@ -311,9 +315,9 @@
build_script_rustc_env: joined_extra_member!(self.build_script_rustc_env, rhs.build_script_rustc_env, BTreeMap::new, BTreeMap::extend),
build_script_toolchains: joined_extra_member!(self.build_script_toolchains, rhs.build_script_toolchains, BTreeSet::new, BTreeSet::extend),
additive_build_file_content: joined_extra_member!(self.additive_build_file_content, rhs.additive_build_file_content, String::new, concat_string),
- shallow_since,
+ shallow_since: self.shallow_since.or(rhs.shallow_since),
patch_args: joined_extra_member!(self.patch_args, rhs.patch_args, Vec::new, Vec::extend),
- patch_tool,
+ patch_tool: self.patch_tool.or(rhs.patch_tool),
patches: joined_extra_member!(self.patches, rhs.patches, BTreeSet::new, BTreeSet::extend),
};
@@ -328,7 +332,7 @@
}
/// A unique identifier for Crates
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct CrateId {
/// The name of the crate
pub name: String,
@@ -356,6 +360,16 @@
return true;
}
+ // If the version provided is the wildcard "*", it matches. Do not
+ // delegate to the semver crate in this case because semver does not
+ // consider "*" to match prerelease packages. That's expected behavior
+ // in the context of declaring package dependencies, but not in the
+ // context of declaring which versions of preselected packages an
+ // annotation applies to.
+ if self.version == "*" {
+ return true;
+ }
+
// Next, check to see if the version provided is a semver req and
// check if the package matches the condition
if let Ok(semver) = VersionReq::parse(&self.version) {
@@ -405,8 +419,7 @@
})
.ok_or_else(|| {
E::custom(format!(
- "Expected string value of `{{name}} {{version}}`. Got '{}'",
- v
+ "Expected string value of `{{name}} {{version}}`. Got '{v}'"
))
})
}
@@ -427,13 +440,81 @@
}
}
+#[derive(Debug, Hash, Clone)]
+pub enum GenBinaries {
+ All,
+ Some(BTreeSet<String>),
+}
+
+impl Default for GenBinaries {
+ fn default() -> Self {
+ GenBinaries::Some(BTreeSet::new())
+ }
+}
+
+impl Serialize for GenBinaries {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ match self {
+ GenBinaries::All => serializer.serialize_bool(true),
+ GenBinaries::Some(set) if set.is_empty() => serializer.serialize_bool(false),
+ GenBinaries::Some(set) => serializer.collect_seq(set),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for GenBinaries {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ deserializer.deserialize_any(GenBinariesVisitor)
+ }
+}
+
+struct GenBinariesVisitor;
+impl<'de> Visitor<'de> for GenBinariesVisitor {
+ type Value = GenBinaries;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("boolean, or array of bin names")
+ }
+
+ fn visit_bool<E>(self, gen_binaries: bool) -> Result<Self::Value, E> {
+ if gen_binaries {
+ Ok(GenBinaries::All)
+ } else {
+ Ok(GenBinaries::Some(BTreeSet::new()))
+ }
+ }
+
+ fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'de>,
+ {
+ BTreeSet::deserialize(SeqAccessDeserializer::new(seq)).map(GenBinaries::Some)
+ }
+}
+
/// Workspace specific settings to control how targets are generated
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Config {
+ /// Whether to generate `rust_binary` targets for all bins by default
+ pub generate_binaries: bool,
+
/// Whether or not to generate Cargo build scripts by default
pub generate_build_scripts: bool,
+ /// A set of platform triples to use in generated select statements
+ #[serde(
+ default = "default_generate_target_compatible_with",
+ skip_serializing_if = "skip_generate_target_compatible_with"
+ )]
+ pub generate_target_compatible_with: bool,
+
/// Additional settings to apply to generated crates
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub annotations: BTreeMap<CrateId, CrateAnnotations>,
@@ -456,6 +537,13 @@
}
}
+fn default_generate_target_compatible_with() -> bool {
+ true
+}
+fn skip_generate_target_compatible_with(value: &bool) -> bool {
+ *value
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -481,22 +569,26 @@
let mut package = mock_cargo_metadata_package();
let id = CrateId::new("mock-pkg".to_owned(), "0.1.0".to_owned());
- package.version = cargo_metadata::Version::new(0, 1, 0);
+ package.version = cargo_metadata::semver::Version::new(0, 1, 0);
assert!(id.matches(&package));
- package.version = cargo_metadata::Version::new(1, 0, 0);
+ package.version = cargo_metadata::semver::Version::new(1, 0, 0);
assert!(!id.matches(&package));
}
#[test]
fn test_crate_id_semver_matches() {
let mut package = mock_cargo_metadata_package();
- package.version = cargo_metadata::Version::new(1, 0, 0);
+ package.version = cargo_metadata::semver::Version::new(1, 0, 0);
let mut id = CrateId::new("mock-pkg".to_owned(), "0.1.0".to_owned());
id.version = "*".to_owned();
assert!(id.matches(&package));
+ let mut prerelease = mock_cargo_metadata_package();
+ prerelease.version = cargo_metadata::semver::Version::parse("1.0.0-pre.0").unwrap();
+ assert!(id.matches(&prerelease));
+
id.version = "<1".to_owned();
assert!(!id.matches(&package));
}
@@ -523,6 +615,7 @@
// Global settings
assert!(config.cargo_config.is_none());
+ assert!(!config.generate_binaries);
assert!(!config.generate_build_scripts);
// Render Config
diff --git a/crate_universe/src/context.rs b/crate_universe/src/context.rs
index 912cd80..41145c5 100644
--- a/crate_universe/src/context.rs
+++ b/crate_universe/src/context.rs
@@ -15,7 +15,7 @@
use crate::context::platforms::resolve_cfg_platforms;
use crate::lockfile::Digest;
use crate::metadata::Annotations;
-use crate::utils::starlark::{Select, SelectList};
+use crate::utils::starlark::Select;
pub use self::crate_context::*;
@@ -51,14 +51,15 @@
let crates: BTreeMap<CrateId, CrateContext> = annotations
.metadata
.crates
- .iter()
- // Convert the crate annotations into more renderable contexts
- .map(|(_, annotation)| {
+ .values()
+ .map(|annotation| {
let context = CrateContext::new(
annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
+ &annotations.features,
+ annotations.config.generate_binaries,
annotations.config.generate_build_scripts,
);
let id = CrateId::new(context.name.clone(), context.version.clone());
@@ -159,261 +160,41 @@
Ok(package_path_id)
}
- /// Filter a crate's dependencies to only ones with aliases
- pub fn crate_aliases(
- &self,
- crate_id: &CrateId,
- build: bool,
- include_dev: bool,
- ) -> SelectList<&CrateDependency> {
- let ctx = &self.crates[crate_id];
- let mut set = SelectList::default();
-
- // Return a set of aliases for build dependencies
- // vs normal dependencies when requested.
- if build {
- // Note that there may not be build dependencies so no dependencies
- // will be gathered in this case
- if let Some(attrs) = &ctx.build_script_attrs {
- let collection: Vec<(Option<String>, &CrateDependency)> = attrs
- .deps
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- attrs
- .deps
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- })
- .chain(attrs.proc_macro_deps.configurations().into_iter().flat_map(
- move |conf| {
- attrs
- .proc_macro_deps
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- },
- ))
- .collect();
-
- for (config, dep) in collection {
- set.insert(dep, config);
- }
- }
- } else {
- let attrs = &ctx.common_attrs;
- let mut collection: Vec<(Option<String>, &CrateDependency)> =
- attrs
- .deps
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- attrs
- .deps
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- })
- .chain(attrs.proc_macro_deps.configurations().into_iter().flat_map(
- move |conf| {
- attrs
- .proc_macro_deps
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- },
- ))
- .collect();
-
- // Optionally include dev dependencies
- if include_dev {
- collection = collection
- .into_iter()
- .chain(
- attrs
- .deps_dev
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- attrs
- .deps_dev
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- }),
- )
- .chain(
- attrs
- .proc_macro_deps_dev
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- attrs
- .proc_macro_deps_dev
- .get_iter(conf)
- .expect("Iterating over known keys should never panic")
- .filter(|dep| dep.alias.is_some())
- .map(move |dep| (conf.cloned(), dep))
- }),
- )
- .collect();
- }
-
- for (config, dep) in collection {
- set.insert(dep, config);
- }
- }
-
- set
- }
-
- /// Create a set of all direct dependencies of workspace member crates and map them to
- /// optional alternative names that allow them to be uniquely identified. This typically
- /// results in a mapping of ([CrateId], [None]) where [None] defaults to using the crate
- /// name. The next most common would be using ([CrateId], `Some(alias)`) as some projects
- /// may use aliases in Cargo as a way to differentiate different versions of the same dep.
- pub fn flat_workspace_member_deps(&self) -> BTreeMap<CrateId, Option<String>> {
- let workspace_member_dependencies: BTreeSet<CrateDependency> = self
- .workspace_members
- .iter()
- .map(|(id, _)| &self.crates[id])
+ /// Create a set of all direct dependencies of workspace member crates.
+ pub fn workspace_member_deps(&self) -> BTreeSet<&CrateDependency> {
+ self.workspace_members
+ .keys()
+ .map(move |id| &self.crates[id])
.flat_map(|ctx| {
- // Build an interator of all dependency CrateIds.
- // TODO: This expansion is horribly verbose and should be refactored but closures
- // were not playing nice when I tried it.
- ctx.common_attrs
- .deps
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- ctx.common_attrs
- .deps
- .get_iter(conf)
- .expect("Lookup should be guaranteed")
+ IntoIterator::into_iter([
+ &ctx.common_attrs.deps,
+ &ctx.common_attrs.deps_dev,
+ &ctx.common_attrs.proc_macro_deps,
+ &ctx.common_attrs.proc_macro_deps_dev,
+ ])
+ .flat_map(|deps| {
+ deps.configurations().into_iter().flat_map(move |conf| {
+ deps.get_iter(conf).expect("Lookup should be guaranteed")
})
- .chain(
- ctx.common_attrs
- .deps_dev
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- ctx.common_attrs
- .deps_dev
- .get_iter(conf)
- .expect("Lookup should be guaranteed")
- }),
- )
- .chain(
- ctx.common_attrs
- .proc_macro_deps
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- ctx.common_attrs
- .proc_macro_deps
- .get_iter(conf)
- .expect("Lookup should be guaranteed")
- }),
- )
- .chain(
- ctx.common_attrs
- .proc_macro_deps_dev
- .configurations()
- .into_iter()
- .flat_map(move |conf| {
- ctx.common_attrs
- .proc_macro_deps_dev
- .get_iter(conf)
- .expect("Lookup should be guaranteed")
- }),
- )
- })
- .cloned()
- .collect();
-
- // Search for any duplicate workspace member definitions
- let duplicate_deps: Vec<CrateDependency> = workspace_member_dependencies
- .iter()
- .filter(|dep| {
- workspace_member_dependencies
- .iter()
- .filter(|check| dep.id.name == check.id.name)
- .count()
- > 1
- })
- .cloned()
- .collect();
-
- workspace_member_dependencies
- .into_iter()
- .map(|dep| {
- if duplicate_deps.contains(&dep) {
- if let Some(alias) = &dep.alias {
- // Check for any duplicate aliases
- let aliases = duplicate_deps
- .iter()
- .filter(|dupe| dupe.id.name == dep.id.name)
- .filter(|dupe| dupe.alias.is_some())
- .filter(|dupe| dupe.alias == dep.alias);
-
- // If there are multiple aliased crates with the same name, the name is updated to
- // be `{alias}-{version}` to differentiate them.
- if aliases.count() >= 2 {
- let rename = format!("{}-{}", &alias, &dep.id.version);
- (dep.id, Some(rename))
- } else {
- (dep.id, Some(alias.clone()))
- }
- } else {
- // Check for all duplicates that match the current dependency and have no alias
- let unaliased = duplicate_deps
- .iter()
- .filter(|dupe| dupe.id.name == dep.id.name)
- .filter(|dupe| dupe.alias.is_none());
-
- // If there are multiple unaliased crates with the same name, the name is updated to
- // be `{name}-{version}` to differentiate them.
- if unaliased.count() >= 2 {
- let rename = format!("{}-{}", &dep.id.name, &dep.id.version);
- (dep.id, Some(rename))
- } else {
- (dep.id, None)
- }
- }
- } else {
- (dep.id, dep.alias)
- }
+ })
})
.collect()
}
- /// Produce a list of binary dependencies with optional aliases which prevent duplicate
- /// targets from being generated.
- pub fn flat_binary_deps(&self) -> BTreeMap<CrateId, Option<String>> {
- // Check for any duplicate binary crate names. If one exists provide an alias to differentiate them
- self.binary_crates
+ pub fn has_duplicate_workspace_member_dep(&self, dep: &CrateDependency) -> bool {
+ 1 < self
+ .workspace_member_deps()
+ .into_iter()
+ .filter(|check| check.id.name == dep.id.name && check.alias == dep.alias)
+ .count()
+ }
+
+ pub fn has_duplicate_binary_crate(&self, bin: &CrateId) -> bool {
+ 1 < self
+ .binary_crates
.iter()
- .map(|crate_id| {
- let dupe_count = self
- .binary_crates
- .iter()
- .filter(|id| crate_id.name == id.name)
- .count();
- // For targets that appear twice (which can happen if one crate aliases a binary dependency)
- if dupe_count >= 2 {
- let rename = format!("{}-{}", crate_id.name, crate_id.version);
- (crate_id.clone(), Some(rename))
- } else {
- (crate_id.clone(), None)
- }
- })
- .collect()
+ .filter(|check| check.name == bin.name)
+ .count()
}
}
@@ -446,66 +227,51 @@
}
#[test]
- fn flat_workspace_member_deps() {
+ fn workspace_member_deps() {
let context = mock_context_common();
- let workspace_member_deps = context.flat_workspace_member_deps();
+ let workspace_member_deps = context.workspace_member_deps();
- assert_eq!(
- workspace_member_deps,
- BTreeMap::from([
- (
- CrateId::new("bitflags".to_owned(), "1.3.2".to_owned()),
- None
- ),
- (CrateId::new("cfg-if".to_owned(), "1.0.0".to_owned()), None),
- ])
- );
+ assert_eq! {
+ workspace_member_deps
+ .iter()
+ .map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep(dep)))
+ .collect::<Vec<_>>(),
+ [
+ (&CrateId::new("bitflags".to_owned(), "1.3.2".to_owned()), false),
+ (&CrateId::new("cfg-if".to_owned(), "1.0.0".to_owned()), false),
+ ],
+ }
}
#[test]
- fn flat_workspace_member_deps_with_alises() {
+ fn workspace_member_deps_with_aliases() {
let context = mock_context_aliases();
- let workspace_member_deps = context.flat_workspace_member_deps();
+ let workspace_member_deps = context.workspace_member_deps();
- assert_eq!(
- workspace_member_deps,
- BTreeMap::from([
- (
- CrateId {
- name: "log".to_owned(),
- version: "0.3.9".to_owned(),
- },
- Some("pinned_log".to_owned())
- ),
- (
- CrateId {
- name: "log".to_owned(),
- version: "0.4.14".to_owned(),
- },
- None
- ),
- (
- CrateId {
- name: "names".to_owned(),
- version: "0.12.1-dev".to_owned(),
- },
- Some("pinned_names".to_owned())
- ),
- (
- CrateId {
- name: "names".to_owned(),
- version: "0.13.0".to_owned(),
- },
- None
- ),
- (
- CrateId {
- name: "value-bag".to_owned(),
- version: "1.0.0-alpha.7".to_owned(),
- },
- None
- ),
- ])
- );
+ assert_eq! {
+ workspace_member_deps
+ .iter()
+ .map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep(dep)))
+ .collect::<Vec<_>>(),
+ [
+ (&CrateId::new("log".to_owned(), "0.3.9".to_owned()), false),
+ (&CrateId::new("log".to_owned(), "0.4.14".to_owned()), false),
+ (&CrateId::new("names".to_owned(), "0.12.1-dev".to_owned()), false),
+ (&CrateId::new("names".to_owned(), "0.13.0".to_owned()), false),
+ (&CrateId::new("value-bag".to_owned(), "1.0.0-alpha.7".to_owned()), false),
+ ],
+ }
+ }
+
+ #[test]
+ fn seralization() {
+ let context = mock_context_aliases();
+
+ // Seralize and deseralize the context object
+ let json_text = serde_json::to_string(&context).unwrap();
+ let deserialized_context: Context = serde_json::from_str(&json_text).unwrap();
+
+ // The data should be identical
+ assert_eq!(context, deserialized_context);
}
}
diff --git a/crate_universe/src/context/crate_context.rs b/crate_universe/src/context/crate_context.rs
index 923fa1e..16f5c23 100644
--- a/crate_universe/src/context/crate_context.rs
+++ b/crate_universe/src/context/crate_context.rs
@@ -5,7 +5,7 @@
use cargo_metadata::{Node, Package, PackageId};
use serde::{Deserialize, Serialize};
-use crate::config::CrateId;
+use crate::config::{CrateId, GenBinaries};
use crate::metadata::{CrateAnnotation, Dependency, PairredExtras, SourceAnnotation};
use crate::utils::sanitize_module_name;
use crate::utils::starlark::{Glob, SelectList, SelectMap, SelectStringDict, SelectStringList};
@@ -28,6 +28,12 @@
#[serde(default)]
pub struct TargetAttributes {
/// The module name of the crate (notably, not the package name).
+ //
+ // This must be the first field of `TargetAttributes` to make it the
+ // lexicographically first thing the derived `Ord` implementation will sort
+ // by. The `Ord` impl controls the order of multiple rules of the same type
+ // in the same BUILD file. In particular, this makes packages with multiple
+ // bin crates generate those `rust_binary` targets in alphanumeric order.
pub crate_name: String,
/// The path to the crate's root source file, relative to the manifest.
@@ -39,17 +45,58 @@
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
pub enum Rule {
- /// `cargo_build_script`
- BuildScript(TargetAttributes),
+ /// `rust_library`
+ Library(TargetAttributes),
/// `rust_proc_macro`
ProcMacro(TargetAttributes),
- /// `rust_library`
- Library(TargetAttributes),
-
/// `rust_binary`
Binary(TargetAttributes),
+
+ /// `cargo_build_script`
+ BuildScript(TargetAttributes),
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
+#[serde(untagged)]
+pub enum CrateFeatures {
+ // Not populated going forward. This just exists for backward compatiblity
+ // with old lock files.
+ LegacySet(BTreeSet<String>),
+
+ /// Map from target triplet to set of features.
+ SelectList(SelectList<String>),
+}
+
+impl Default for CrateFeatures {
+ fn default() -> Self {
+ CrateFeatures::SelectList(Default::default())
+ }
+}
+
+impl From<&CrateFeatures> for SelectList<String> {
+ fn from(value: &CrateFeatures) -> Self {
+ match value {
+ CrateFeatures::LegacySet(s) => {
+ let mut sl = SelectList::default();
+ for v in s {
+ sl.insert(v.clone(), None);
+ }
+ sl
+ }
+ CrateFeatures::SelectList(sl) => sl.clone(),
+ }
+ }
+}
+
+impl CrateFeatures {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ CrateFeatures::LegacySet(s) => s.is_empty(),
+ CrateFeatures::SelectList(sl) => sl.is_empty(),
+ }
+ }
}
/// A set of attributes common to most `rust_library`, `rust_proc_macro`, and other
@@ -57,28 +104,28 @@
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct CommonAttributes {
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub compile_data: SelectStringList,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub compile_data_glob: BTreeSet<String>,
- #[serde(skip_serializing_if = "BTreeSet::is_empty")]
- pub crate_features: BTreeSet<String>,
+ #[serde(skip_serializing_if = "CrateFeatures::is_empty")]
+ pub crate_features: CrateFeatures,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub data: SelectStringList,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub data_glob: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub deps: SelectList<CrateDependency>,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub extra_deps: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub deps_dev: SelectList<CrateDependency>,
pub edition: String,
@@ -86,19 +133,19 @@
#[serde(skip_serializing_if = "Option::is_none")]
pub linker_script: Option<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub proc_macro_deps: SelectList<CrateDependency>,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub extra_proc_macro_deps: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub proc_macro_deps_dev: SelectList<CrateDependency>,
- #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
pub rustc_env: SelectStringDict,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub rustc_env_files: SelectStringList,
#[serde(skip_serializing_if = "Vec::is_empty")]
@@ -141,40 +188,40 @@
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct BuildScriptAttributes {
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub compile_data: SelectStringList,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub data: SelectStringList,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub data_glob: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub deps: SelectList<CrateDependency>,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub extra_deps: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
pub build_script_env: SelectStringDict,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub extra_proc_macro_deps: BTreeSet<String>,
- #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectList::is_empty")]
pub proc_macro_deps: SelectList<CrateDependency>,
- #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
pub rustc_env: SelectStringDict,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub rustc_flags: SelectStringList,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub rustc_env_files: SelectStringList,
- #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+ #[serde(skip_serializing_if = "SelectStringList::is_empty")]
pub tools: SelectStringList,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -221,7 +268,7 @@
pub repository: Option<SourceAnnotation>,
/// A list of all targets (lib, proc-macro, bin) associated with this package
- pub targets: Vec<Rule>,
+ pub targets: BTreeSet<Rule>,
/// The name of the crate's root library target. This is the target that a dependent
/// would get if they were to depend on `{crate_name}`.
@@ -241,6 +288,10 @@
/// Additional text to add to the generated BUILD file.
#[serde(skip_serializing_if = "Option::is_none")]
pub additive_build_file_content: Option<String>,
+
+ /// If true, disables pipelining for library targets generated for this crate
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
+ pub disable_pipelining: bool,
}
impl CrateContext {
@@ -249,6 +300,8 @@
packages: &BTreeMap<PackageId, Package>,
source_annotations: &BTreeMap<PackageId, SourceAnnotation>,
extras: &BTreeMap<CrateId, PairredExtras>,
+ features: &BTreeMap<CrateId, SelectList<String>>,
+ include_binaries: bool,
include_build_scripts: bool,
) -> Self {
let package: &Package = &packages[&annotation.node.id];
@@ -281,21 +334,44 @@
// Gather all "common" attributes
let mut common_attrs = CommonAttributes {
- crate_features: annotation.node.features.iter().cloned().collect(),
+ crate_features: CrateFeatures::SelectList(
+ features
+ .get(¤t_crate_id)
+ .map_or_else(SelectList::default, |f| f.clone()),
+ ),
deps,
deps_dev,
- edition: package.edition.clone(),
+ edition: package.edition.as_str().to_string(),
proc_macro_deps,
proc_macro_deps_dev,
version: package.version.to_string(),
..Default::default()
};
+ // Locate extra settings for the current package.
+ let package_extra = extras
+ .iter()
+ .find(|(_, settings)| settings.package_id == package.id);
+
let include_build_scripts =
- Self::crate_includes_build_script(package, extras, include_build_scripts);
+ Self::crate_includes_build_script(package_extra, include_build_scripts);
+
+ let gen_none = GenBinaries::Some(BTreeSet::new());
+ let gen_binaries = package_extra
+ .and_then(|(_, settings)| settings.crate_extra.gen_binaries.as_ref())
+ .unwrap_or(if include_binaries {
+ &GenBinaries::All
+ } else {
+ &gen_none
+ });
// Iterate over each target and produce a Bazel target for all supported "kinds"
- let targets = Self::collect_targets(&annotation.node, packages, include_build_scripts);
+ let targets = Self::collect_targets(
+ &annotation.node,
+ packages,
+ gen_binaries,
+ include_build_scripts,
+ );
// Parse the library crate name from the set of included targets
let library_target_name = {
@@ -368,6 +444,7 @@
build_script_attrs,
license,
additive_build_file_content: None,
+ disable_pipelining: false,
}
.with_overrides(extras)
}
@@ -403,8 +480,13 @@
// Crate features
if let Some(extra) = &crate_extra.crate_features {
- for data in extra.iter() {
- self.common_attrs.crate_features.insert(data.clone());
+ match &mut self.common_attrs.crate_features {
+ CrateFeatures::LegacySet(s) => s.append(&mut extra.clone()),
+ CrateFeatures::SelectList(sl) => {
+ for data in extra.iter() {
+ sl.insert(data.clone(), None);
+ }
+ }
}
}
@@ -420,6 +502,11 @@
self.common_attrs.data_glob.extend(extra.clone());
}
+ // Disable pipelining
+ if crate_extra.disable_pipelining {
+ self.disable_pipelining = true;
+ }
+
// Rustc flags
if let Some(extra) = &crate_extra.rustc_flags {
self.common_attrs.rustc_flags.append(&mut extra.clone());
@@ -427,7 +514,7 @@
// Rustc env
if let Some(extra) = &crate_extra.rustc_env {
- self.common_attrs.rustc_env.insert(extra.clone(), None);
+ self.common_attrs.rustc_env.extend(extra.clone(), None);
}
// Rustc env files
@@ -477,12 +564,12 @@
// Rustc env
if let Some(extra) = &crate_extra.build_script_rustc_env {
- attrs.rustc_env.insert(extra.clone(), None);
+ attrs.rustc_env.extend(extra.clone(), None);
}
// Build script env
if let Some(extra) = &crate_extra.build_script_env {
- attrs.build_script_env.insert(extra.clone(), None);
+ attrs.build_script_env.extend(extra.clone(), None);
}
}
@@ -533,18 +620,12 @@
/// Determine whether or not a crate __should__ include a build script
/// (build.rs) if it happens to have one.
fn crate_includes_build_script(
- package: &Package,
- overrides: &BTreeMap<CrateId, PairredExtras>,
+ package_extra: Option<(&CrateId, &PairredExtras)>,
default_generate_build_script: bool,
) -> bool {
- // Locate extra settings for the current package.
- let settings = overrides
- .iter()
- .find(|(_, settings)| settings.package_id == package.id);
-
// If the crate has extra settings, which explicitly set `gen_build_script`, always use
// this value, otherwise, fallback to the provided default.
- settings
+ package_extra
.and_then(|(_, settings)| settings.crate_extra.gen_build_script)
.unwrap_or(default_generate_build_script)
}
@@ -553,8 +634,9 @@
fn collect_targets(
node: &Node,
packages: &BTreeMap<PackageId, Package>,
+ gen_binaries: &GenBinaries,
include_build_scripts: bool,
- ) -> Vec<Rule> {
+ ) -> BTreeSet<Rule> {
let package = &packages[&node.id];
let package_root = package
@@ -567,60 +649,61 @@
.targets
.iter()
.flat_map(|target| {
- target
- .kind
- .iter()
- .filter_map(|kind| {
- // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
- // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
- // content to align when rendering, the package target names are always sanitized.
- let crate_name = sanitize_module_name(&target.name);
+ target.kind.iter().filter_map(move |kind| {
+ // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
+ // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
+ // content to align when rendering, the package target names are always sanitized.
+ let crate_name = sanitize_module_name(&target.name);
- // Locate the crate's root source file relative to the package root normalized for unix
- let crate_root = pathdiff::diff_paths(&target.src_path, package_root).map(
- // Normalize the path so that it always renders the same regardless of platform
- |root| root.to_string_lossy().replace('\\', "/"),
- );
+ // Locate the crate's root source file relative to the package root normalized for unix
+ let crate_root = pathdiff::diff_paths(&target.src_path, package_root).map(
+ // Normalize the path so that it always renders the same regardless of platform
+ |root| root.to_string_lossy().replace('\\', "/"),
+ );
- // Conditionally check to see if the dependencies is a build-script target
- if include_build_scripts && kind == "custom-build" {
- return Some(Rule::BuildScript(TargetAttributes {
- crate_name,
- crate_root,
- srcs: Glob::new_rust_srcs(),
- }));
+ // Conditionally check to see if the dependencies is a build-script target
+ if include_build_scripts && kind == "custom-build" {
+ return Some(Rule::BuildScript(TargetAttributes {
+ crate_name,
+ crate_root,
+ srcs: Glob::new_rust_srcs(),
+ }));
+ }
+
+ // Check to see if the dependencies is a proc-macro target
+ if kind == "proc-macro" {
+ return Some(Rule::ProcMacro(TargetAttributes {
+ crate_name,
+ crate_root,
+ srcs: Glob::new_rust_srcs(),
+ }));
+ }
+
+ // Check to see if the dependencies is a library target
+ if ["lib", "rlib"].contains(&kind.as_str()) {
+ return Some(Rule::Library(TargetAttributes {
+ crate_name,
+ crate_root,
+ srcs: Glob::new_rust_srcs(),
+ }));
+ }
+
+ // Check if the target kind is binary and is one of the ones included in gen_binaries
+ if kind == "bin"
+ && match gen_binaries {
+ GenBinaries::All => true,
+ GenBinaries::Some(set) => set.contains(&target.name),
}
+ {
+ return Some(Rule::Binary(TargetAttributes {
+ crate_name: target.name.clone(),
+ crate_root,
+ srcs: Glob::new_rust_srcs(),
+ }));
+ }
- // Check to see if the dependencies is a proc-macro target
- if kind == "proc-macro" {
- return Some(Rule::ProcMacro(TargetAttributes {
- crate_name,
- crate_root,
- srcs: Glob::new_rust_srcs(),
- }));
- }
-
- // Check to see if the dependencies is a library target
- if ["lib", "rlib"].contains(&kind.as_str()) {
- return Some(Rule::Library(TargetAttributes {
- crate_name,
- crate_root,
- srcs: Glob::new_rust_srcs(),
- }));
- }
-
- // Check to see if the dependencies is a library target
- if kind == "bin" {
- return Some(Rule::Binary(TargetAttributes {
- crate_name: target.name.clone(),
- crate_root,
- srcs: Glob::new_rust_srcs(),
- }));
- }
-
- None
- })
- .collect::<Vec<Rule>>()
+ None
+ })
})
.collect()
}
@@ -650,29 +733,26 @@
repr: "common 0.1.0 (path+file://{TEMP_DIR}/common)".to_owned(),
}];
+ let include_binaries = false;
+ let include_build_scripts = false;
let context = CrateContext::new(
crate_annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
- false,
+ &annotations.features,
+ include_binaries,
+ include_build_scripts,
);
assert_eq!(context.name, "common");
assert_eq!(
context.targets,
- vec![
- Rule::Library(TargetAttributes {
- crate_name: "common".to_owned(),
- crate_root: Some("lib.rs".to_owned()),
- srcs: Glob::new_rust_srcs(),
- }),
- Rule::Binary(TargetAttributes {
- crate_name: "common-bin".to_owned(),
- crate_root: Some("main.rs".to_owned()),
- srcs: Glob::new_rust_srcs(),
- }),
- ]
+ BTreeSet::from([Rule::Library(TargetAttributes {
+ crate_name: "common".to_owned(),
+ crate_root: Some("lib.rs".to_owned()),
+ srcs: Glob::new_rust_srcs(),
+ })]),
);
}
@@ -692,24 +772,29 @@
PairredExtras {
package_id,
crate_extra: CrateAnnotations {
+ gen_binaries: Some(GenBinaries::All),
data_glob: Some(BTreeSet::from(["**/data_glob/**".to_owned()])),
..CrateAnnotations::default()
},
},
);
+ let include_binaries = false;
+ let include_build_scripts = false;
let context = CrateContext::new(
crate_annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&pairred_extras,
- false,
+ &annotations.features,
+ include_binaries,
+ include_build_scripts,
);
assert_eq!(context.name, "common");
assert_eq!(
context.targets,
- vec![
+ BTreeSet::from([
Rule::Library(TargetAttributes {
crate_name: "common".to_owned(),
crate_root: Some("lib.rs".to_owned()),
@@ -720,7 +805,7 @@
crate_root: Some("main.rs".to_owned()),
srcs: Glob::new_rust_srcs(),
}),
- ]
+ ]),
);
assert_eq!(
context.common_attrs.data_glob,
@@ -751,25 +836,29 @@
let annotations = build_script_annotations();
let package_id = PackageId {
- repr: "openssl-sys 0.9.72 (registry+https://github.com/rust-lang/crates.io-index)"
+ repr: "openssl-sys 0.9.87 (registry+https://github.com/rust-lang/crates.io-index)"
.to_owned(),
};
let crate_annotation = &annotations.metadata.crates[&package_id];
+ let include_binaries = false;
+ let include_build_scripts = true;
let context = CrateContext::new(
crate_annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
- true,
+ &annotations.features,
+ include_binaries,
+ include_build_scripts,
);
assert_eq!(context.name, "openssl-sys");
assert!(context.build_script_attrs.is_some());
assert_eq!(
context.targets,
- vec![
+ BTreeSet::from([
Rule::Library(TargetAttributes {
crate_name: "openssl_sys".to_owned(),
crate_root: Some("src/lib.rs".to_owned()),
@@ -780,7 +869,7 @@
crate_root: Some("build/main.rs".to_owned()),
srcs: Glob::new_rust_srcs(),
})
- ]
+ ]),
);
// Cargo build scripts should include all sources
@@ -792,29 +881,33 @@
let annotations = build_script_annotations();
let package_id = PackageId {
- repr: "openssl-sys 0.9.72 (registry+https://github.com/rust-lang/crates.io-index)"
+ repr: "openssl-sys 0.9.87 (registry+https://github.com/rust-lang/crates.io-index)"
.to_owned(),
};
let crate_annotation = &annotations.metadata.crates[&package_id];
+ let include_binaries = false;
+ let include_build_scripts = false;
let context = CrateContext::new(
crate_annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
- false,
+ &annotations.features,
+ include_binaries,
+ include_build_scripts,
);
assert_eq!(context.name, "openssl-sys");
assert!(context.build_script_attrs.is_none());
assert_eq!(
context.targets,
- vec![Rule::Library(TargetAttributes {
+ BTreeSet::from([Rule::Library(TargetAttributes {
crate_name: "openssl_sys".to_owned(),
crate_root: Some("src/lib.rs".to_owned()),
srcs: Glob::new_rust_srcs(),
- })],
+ })]),
);
}
@@ -829,23 +922,27 @@
let crate_annotation = &annotations.metadata.crates[&package_id];
+ let include_binaries = false;
+ let include_build_scripts = false;
let context = CrateContext::new(
crate_annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
- false,
+ &annotations.features,
+ include_binaries,
+ include_build_scripts,
);
assert_eq!(context.name, "sysinfo");
assert!(context.build_script_attrs.is_none());
assert_eq!(
context.targets,
- vec![Rule::Library(TargetAttributes {
+ BTreeSet::from([Rule::Library(TargetAttributes {
crate_name: "sysinfo".to_owned(),
crate_root: Some("src/lib.rs".to_owned()),
srcs: Glob::new_rust_srcs(),
- })],
+ })]),
);
}
}
diff --git a/crate_universe/src/context/platforms.rs b/crate_universe/src/context/platforms.rs
index 724dbd0..fc4e789 100644
--- a/crate_universe/src/context/platforms.rs
+++ b/crate_universe/src/context/platforms.rs
@@ -1,4 +1,4 @@
-use std::collections::{BTreeMap, BTreeSet, HashMap};
+use std::collections::{BTreeMap, BTreeSet};
use anyhow::{anyhow, Context, Result};
use cfg_expr::targets::{get_builtin_target_by_triple, TargetInfo};
@@ -56,8 +56,8 @@
// (`x86_64-unknown-linux-gun` vs `cfg(target = "x86_64-unkonwn-linux-gnu")`). So
// in order to parse configurations, the text is renamed for the check but the
// original is retained for comaptibility with the manifest.
- let rename = |cfg: &str| -> String { format!("cfg(target = \"{}\")", cfg) };
- let original_cfgs: HashMap<String, String> = configurations
+ let rename = |cfg: &str| -> String { format!("cfg(target = \"{cfg}\")") };
+ let original_cfgs: BTreeMap<String, String> = configurations
.iter()
.filter(|cfg| !cfg.starts_with("cfg("))
.map(|cfg| (rename(cfg), cfg.clone()))
@@ -73,8 +73,8 @@
})
// Check the current configuration with against each supported triple
.map(|cfg| {
- let expression = Expression::parse(&cfg)
- .context(format!("Failed to parse expression: '{}'", cfg))?;
+ let expression =
+ Expression::parse(&cfg).context(format!("Failed to parse expression: '{cfg}'"))?;
let triples = target_infos
.iter()
@@ -116,6 +116,7 @@
"aarch64-apple-darwin".to_owned(),
"aarch64-apple-ios".to_owned(),
"aarch64-linux-android".to_owned(),
+ "aarch64-pc-windows-msvc".to_owned(),
"aarch64-unknown-linux-gnu".to_owned(),
"arm-unknown-linux-gnueabi".to_owned(),
"armv7-unknown-linux-gnueabi".to_owned(),
@@ -165,9 +166,7 @@
assert_eq!(configurations, BTreeMap::new(),)
}
- #[test]
- fn resolve_targeted() {
- let configuration = r#"cfg(target = "x86_64-unknown-linux-gnu")"#.to_owned();
+ fn mock_resolve_context(configuration: String) -> CrateContext {
let mut deps = SelectList::default();
deps.insert(
CrateDependency {
@@ -175,10 +174,10 @@
target: "mock_crate_b".to_owned(),
alias: None,
},
- Some(configuration.clone()),
+ Some(configuration),
);
- let context = CrateContext {
+ CrateContext {
name: "mock_crate_a".to_owned(),
version: "0.1.0".to_owned(),
common_attrs: CommonAttributes {
@@ -186,18 +185,39 @@
..CommonAttributes::default()
},
..CrateContext::default()
- };
+ }
+ }
- let configurations =
- resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
+ #[test]
+ fn resolve_targeted() {
+ let data = BTreeMap::from([
+ (
+ r#"cfg(target = "x86_64-unknown-linux-gnu")"#.to_owned(),
+ BTreeSet::from(["x86_64-unknown-linux-gnu".to_owned()]),
+ ),
+ (
+ r#"cfg(any(target_os = "macos", target_os = "ios"))"#.to_owned(),
+ BTreeSet::from([
+ "aarch64-apple-darwin".to_owned(),
+ "aarch64-apple-ios".to_owned(),
+ "i686-apple-darwin".to_owned(),
+ "x86_64-apple-darwin".to_owned(),
+ "x86_64-apple-ios".to_owned(),
+ ]),
+ ),
+ ]);
- assert_eq!(
- configurations,
- BTreeMap::from([(
- configuration,
- BTreeSet::from(["x86_64-unknown-linux-gnu".to_owned()])
- )])
- );
+ data.into_iter().for_each(|(configuration, expectation)| {
+ let context = mock_resolve_context(configuration.clone());
+
+ let configurations =
+ resolve_cfg_platforms(vec![&context], &supported_platform_triples()).unwrap();
+
+ assert_eq!(
+ configurations,
+ BTreeMap::from([(configuration, expectation,)])
+ );
+ })
}
#[test]
diff --git a/crate_universe/src/lib.rs b/crate_universe/src/lib.rs
index ae85717..e6fbd6f 100644
--- a/crate_universe/src/lib.rs
+++ b/crate_universe/src/lib.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::large_enum_variant)]
+
pub mod cli;
mod config;
diff --git a/crate_universe/src/lockfile.rs b/crate_universe/src/lockfile.rs
index 65738a6..7eccea3 100644
--- a/crate_universe/src/lockfile.rs
+++ b/crate_universe/src/lockfile.rs
@@ -1,6 +1,6 @@
//! Utility module for interracting with different kinds of lock files
-use std::collections::HashMap;
+use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ffi::OsStr;
use std::fs;
@@ -14,13 +14,14 @@
use crate::config::Config;
use crate::context::Context;
+use crate::metadata::Cargo;
use crate::splicing::{SplicingManifest, SplicingMetadata};
pub fn lock_context(
mut context: Context,
config: &Config,
splicing_manifest: &SplicingManifest,
- cargo_bin: &Path,
+ cargo_bin: &Cargo,
rustc_bin: &Path,
) -> Result<Context> {
// Ensure there is no existing checksum which could impact the lockfile results
@@ -35,12 +36,12 @@
})
}
-/// Write a [crate::planning::PlannedContext] to disk
+/// Write a [crate::context::Context] to disk
pub fn write_lockfile(lockfile: Context, path: &Path, dry_run: bool) -> Result<()> {
let content = serde_json::to_string_pretty(&lockfile)?;
if dry_run {
- println!("{:#?}", content);
+ println!("{content:#?}");
} else {
// Ensure the parent directory exists
if let Some(parent) = path.parent() {
@@ -61,11 +62,11 @@
context: &Context,
config: &Config,
splicing_manifest: &SplicingManifest,
- cargo_bin: &Path,
+ cargo_bin: &Cargo,
rustc_bin: &Path,
) -> Result<Self> {
let splicing_metadata = SplicingMetadata::try_from((*splicing_manifest).clone())?;
- let cargo_version = Self::bin_version(cargo_bin)?;
+ let cargo_version = cargo_bin.full_version()?;
let rustc_version = Self::bin_version(rustc_bin)?;
let cargo_bazel_version = env!("CARGO_PKG_VERSION");
@@ -129,7 +130,7 @@
Self(hasher.finalize().encode_hex::<String>())
}
- fn bin_version(binary: &Path) -> Result<String> {
+ pub fn bin_version(binary: &Path) -> Result<String> {
let safe_vars = [OsStr::new("HOMEDRIVE"), OsStr::new("PATHEXT")];
let env = std::env::vars_os().filter(|(var, _)| safe_vars.contains(&var.as_os_str()));
@@ -137,7 +138,8 @@
.arg("--version")
.env_clear()
.envs(env)
- .output()?;
+ .output()
+ .with_context(|| format!("Failed to run {} to get its version", binary.display()))?;
if !output.status.success() {
bail!("Failed to query cargo version")
@@ -152,7 +154,7 @@
// computed consistently. If a new binary is released then this
// condition should be removed
// https://github.com/rust-lang/cargo/issues/10547
- let corrections = HashMap::from([
+ let corrections = BTreeMap::from([
(
"cargo 1.60.0 (d1fd9fe 2022-03-01)",
"cargo 1.60.0 (d1fd9fe2c 2022-03-01)",
@@ -209,7 +211,7 @@
assert_eq!(
digest,
- Digest("9711073103bd532b7d9c2e32e805280d29fc8591c3e76f9fe489fc372e2866db".to_owned())
+ Digest("fcca6635448d70091bffb6409f5edb153a46fcf7e889e39a33a9b9ff6e345ca0".to_owned())
);
}
@@ -217,6 +219,7 @@
fn digest_with_config() {
let context = Context::default();
let config = Config {
+ generate_binaries: false,
generate_build_scripts: false,
annotations: BTreeMap::from([(
CrateId::new("rustonomicon".to_owned(), "1.0.0".to_owned()),
@@ -229,6 +232,7 @@
supported_platform_triples: BTreeSet::from([
"aarch64-apple-darwin".to_owned(),
"aarch64-unknown-linux-gnu".to_owned(),
+ "aarch64-pc-windows-msvc".to_owned(),
"wasm32-unknown-unknown".to_owned(),
"wasm32-wasi".to_owned(),
"x86_64-apple-darwin".to_owned(),
@@ -252,7 +256,7 @@
assert_eq!(
digest,
- Digest("756a613410573552bb8a85d6fcafd24a9df3000b8d943bf74c38bda9c306ef0e".to_owned())
+ Digest("c90e7e5a98e49884c9962f99aea5cf20d5a32df243dbb549001c50badf0a02d3".to_owned())
);
}
@@ -283,7 +287,7 @@
assert_eq!(
digest,
- Digest("851b789765d8ee248fd3d55840ffd702ba2f8b0ca6aed2faa45ea63d1b011a99".to_owned())
+ Digest("e199dd859bd5b75d6b152f364f8cc6ad6c3a2a68ae777dfb8b250c2d90e35f28".to_owned())
);
}
@@ -332,7 +336,7 @@
assert_eq!(
digest,
- Digest("a9f7ea66f1b04331f8e09c64cd0b972e4c2a136907d7ef90e81ae2654e3c002c".to_owned())
+ Digest("0222be160f1031346cc209a8732c678bf32acb08f891fdfa0e9965d0ad22a33a".to_owned())
);
}
}
diff --git a/crate_universe/src/metadata.rs b/crate_universe/src/metadata.rs
index 57d90e7..4e8cf1a 100644
--- a/crate_universe/src/metadata.rs
+++ b/crate_universe/src/metadata.rs
@@ -3,15 +3,23 @@
mod dependency;
mod metadata_annotation;
+use std::collections::{BTreeMap, BTreeSet};
use std::env;
use std::fs;
+use std::io::BufRead;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
+use std::sync::{Arc, Mutex};
-use anyhow::{bail, Context, Result};
+use crate::lockfile::Digest;
+use anyhow::{anyhow, bail, Context, Result};
use cargo_lock::Lockfile as CargoLockfile;
use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand};
+use semver::Version;
+
+use crate::config::CrateId;
+use crate::utils::starlark::SelectList;
pub use self::dependency::*;
pub use self::metadata_annotation::*;
@@ -25,7 +33,7 @@
/// Generates Cargo metadata and a lockfile from a provided manifest.
pub struct Generator {
/// The path to a `cargo` binary
- cargo_bin: PathBuf,
+ cargo_bin: Cargo,
/// The path to a `rustc` binary
rustc_bin: PathBuf,
@@ -34,12 +42,14 @@
impl Generator {
pub fn new() -> Self {
Generator {
- cargo_bin: PathBuf::from(env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())),
+ cargo_bin: Cargo::new(PathBuf::from(
+ env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()),
+ )),
rustc_bin: PathBuf::from(env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string())),
}
}
- pub fn with_cargo(mut self, cargo_bin: PathBuf) -> Self {
+ pub fn with_cargo(mut self, cargo_bin: Cargo) -> Self {
self.cargo_bin = cargo_bin;
self
}
@@ -64,8 +74,9 @@
cargo_lock::Lockfile::load(lock_path)?
};
- let metadata = MetadataCommand::new()
- .cargo_path(&self.cargo_bin)
+ let metadata = self
+ .cargo_bin
+ .metadata_command()?
.current_dir(manifest_dir)
.manifest_path(manifest_path.as_ref())
.other_options(["--locked".to_owned()])
@@ -75,8 +86,76 @@
}
}
+/// Cargo encapsulates a path to a `cargo` binary.
+/// Any invocations of `cargo` (either as a `std::process::Command` or via `cargo_metadata`) should
+/// go via this wrapper to ensure that any environment variables needed are set appropriately.
+#[derive(Clone)]
+pub struct Cargo {
+ path: PathBuf,
+ full_version: Arc<Mutex<Option<String>>>,
+}
+
+impl Cargo {
+ pub fn new(path: PathBuf) -> Cargo {
+ Cargo {
+ path,
+ full_version: Arc::new(Mutex::new(None)),
+ }
+ }
+
+ /// Returns a new `Command` for running this cargo.
+ pub fn command(&self) -> Result<Command> {
+ let mut command = Command::new(&self.path);
+ command.envs(self.env()?);
+ Ok(command)
+ }
+
+ /// Returns a new `MetadataCommand` using this cargo.
+ pub fn metadata_command(&self) -> Result<MetadataCommand> {
+ let mut command = MetadataCommand::new();
+ command.cargo_path(&self.path);
+ for (k, v) in self.env()? {
+ command.env(k, v);
+ }
+ Ok(command)
+ }
+
+ /// Returns the output of running `cargo version`, trimming any leading or trailing whitespace.
+ /// This function performs normalisation to work around `<https://github.com/rust-lang/cargo/issues/10547>`
+ pub fn full_version(&self) -> Result<String> {
+ let mut full_version = self.full_version.lock().unwrap();
+ if full_version.is_none() {
+ let observed_version = Digest::bin_version(&self.path)?;
+ *full_version = Some(observed_version);
+ }
+ Ok(full_version.clone().unwrap())
+ }
+
+ pub fn use_sparse_registries_for_crates_io(&self) -> Result<bool> {
+ let full_version = self.full_version()?;
+ let version_str = full_version.split(' ').nth(1);
+ if let Some(version_str) = version_str {
+ let version = Version::parse(version_str).context("Failed to parse cargo version")?;
+ return Ok(version.major >= 1 && version.minor >= 68);
+ }
+ bail!("Couldn't parse cargo version");
+ }
+
+ fn env(&self) -> Result<BTreeMap<String, String>> {
+ let mut map = BTreeMap::new();
+
+ if self.use_sparse_registries_for_crates_io()? {
+ map.insert(
+ "CARGO_REGISTRIES_CRATES_IO_PROTOCOL".into(),
+ "sparse".into(),
+ );
+ }
+ Ok(map)
+ }
+}
+
/// A configuration desrcibing how to invoke [cargo update](https://doc.rust-lang.org/cargo/commands/cargo-update.html).
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CargoUpdateRequest {
/// Translates to an unrestricted `cargo update` command
Eager,
@@ -100,11 +179,11 @@
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.to_lowercase();
- if ["1", "yes", "true", "on"].contains(&lower.as_str()) {
+ if ["eager", "full", "all"].contains(&lower.as_str()) {
return Ok(Self::Eager);
}
- if ["workspace", "minimal"].contains(&lower.as_str()) {
+ if ["1", "yes", "true", "on", "workspace", "minimal"].contains(&lower.as_str()) {
return Ok(Self::Workspace);
}
@@ -136,11 +215,12 @@
}
/// Calls `cargo update` with arguments specific to the state of the current variant.
- pub fn update(&self, manifest: &Path, cargo_bin: &Path, rustc_bin: &Path) -> Result<()> {
+ pub fn update(&self, manifest: &Path, cargo_bin: &Cargo, rustc_bin: &Path) -> Result<()> {
let manifest_dir = manifest.parent().unwrap();
// Simply invoke `cargo update`
- let output = Command::new(cargo_bin)
+ let output = cargo_bin
+ .command()?
// Cargo detects config files based on `pwd` when running so
// to ensure user provided Cargo config files are used, it's
// critical to set the working directory to the manifest dir.
@@ -149,7 +229,7 @@
.arg("--manifest-path")
.arg(manifest)
.args(self.get_update_args())
- .env("RUSTC", &rustc_bin)
+ .env("RUSTC", rustc_bin)
.output()
.with_context(|| {
format!(
@@ -170,14 +250,14 @@
pub struct LockGenerator {
/// The path to a `cargo` binary
- cargo_bin: PathBuf,
+ cargo_bin: Cargo,
/// The path to a `rustc` binary
rustc_bin: PathBuf,
}
impl LockGenerator {
- pub fn new(cargo_bin: PathBuf, rustc_bin: PathBuf) -> Self {
+ pub fn new(cargo_bin: Cargo, rustc_bin: PathBuf) -> Self {
Self {
cargo_bin,
rustc_bin,
@@ -205,7 +285,7 @@
if generated_lockfile_path.exists() {
fs::remove_file(&generated_lockfile_path)?;
}
- fs::copy(&lock, &generated_lockfile_path)?;
+ fs::copy(lock, &generated_lockfile_path)?;
if let Some(request) = update_request {
request.update(manifest_path, &self.cargo_bin, &self.rustc_bin)?;
@@ -213,7 +293,9 @@
// Ensure the Cargo cache is up to date to simulate the behavior
// of having just generated a new one
- let output = Command::new(&self.cargo_bin)
+ let output = self
+ .cargo_bin
+ .command()?
// Cargo detects config files based on `pwd` when running so
// to ensure user provided Cargo config files are used, it's
// critical to set the working directory to the manifest dir.
@@ -239,7 +321,9 @@
}
} else {
// Simply invoke `cargo generate-lockfile`
- let output = Command::new(&self.cargo_bin)
+ let output = self
+ .cargo_bin
+ .command()?
// Cargo detects config files based on `pwd` when running so
// to ensure user provided Cargo config files are used, it's
// critical to set the working directory to the manifest dir.
@@ -271,14 +355,14 @@
/// A generator which runs `cargo vendor` on a given manifest
pub struct VendorGenerator {
/// The path to a `cargo` binary
- cargo_bin: PathBuf,
+ cargo_bin: Cargo,
/// The path to a `rustc` binary
rustc_bin: PathBuf,
}
impl VendorGenerator {
- pub fn new(cargo_bin: PathBuf, rustc_bin: PathBuf) -> Self {
+ pub fn new(cargo_bin: Cargo, rustc_bin: PathBuf) -> Self {
Self {
cargo_bin,
rustc_bin,
@@ -289,7 +373,9 @@
let manifest_dir = manifest_path.parent().unwrap();
// Simply invoke `cargo generate-lockfile`
- let output = Command::new(&self.cargo_bin)
+ let output = self
+ .cargo_bin
+ .command()?
// Cargo detects config files based on `pwd` when running so
// to ensure user provided Cargo config files are used, it's
// critical to set the working directory to the manifest dir.
@@ -319,6 +405,177 @@
}
}
+/// A generate which computes per-platform feature sets.
+pub struct FeatureGenerator {
+ /// The path to a `cargo` binary
+ cargo_bin: Cargo,
+
+ /// The path to a `rustc` binary
+ rustc_bin: PathBuf,
+}
+
+impl FeatureGenerator {
+ pub fn new(cargo_bin: Cargo, rustc_bin: PathBuf) -> Self {
+ Self {
+ cargo_bin,
+ rustc_bin,
+ }
+ }
+
+ /// Computes the set of enabled features for each target triplet for each crate.
+ pub fn generate(
+ &self,
+ manifest_path: &Path,
+ platform_triples: &BTreeSet<String>,
+ ) -> Result<BTreeMap<CrateId, SelectList<String>>> {
+ let manifest_dir = manifest_path.parent().unwrap();
+ let mut target_to_child = BTreeMap::new();
+ for target in platform_triples {
+ // We use `cargo tree` here because `cargo metadata` doesn't report
+ // back target-specific features (enabled with `resolver = "2"`).
+ // This is unfortunately a bit of a hack. See:
+ // - https://github.com/rust-lang/cargo/issues/9863
+ // - https://github.com/bazelbuild/rules_rust/issues/1662
+ let output = self
+ .cargo_bin
+ .command()?
+ .current_dir(manifest_dir)
+ .arg("tree")
+ .arg("--locked")
+ .arg("--manifest-path")
+ .arg(manifest_path)
+ .arg("--prefix=none")
+ // https://doc.rust-lang.org/cargo/commands/cargo-tree.html#tree-formatting-options
+ .arg("--format=|{p}|{f}|")
+ .arg("--color=never")
+ .arg("--workspace")
+ .arg("--target")
+ .arg(target)
+ .env("RUSTC", &self.rustc_bin)
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .with_context(|| {
+ format!(
+ "Error spawning cargo in child process to compute features for target '{}', manifest path '{}'",
+ target,
+ manifest_path.display()
+ )
+ })?;
+ target_to_child.insert(target, output);
+ }
+ let mut crate_features = BTreeMap::<CrateId, BTreeMap<String, BTreeSet<String>>>::new();
+ for (target, child) in target_to_child.into_iter() {
+ let output = child
+ .wait_with_output()
+ .with_context(|| {
+ format!(
+ "Error running cargo in child process to compute features for target '{}', manifest path '{}'",
+ target,
+ manifest_path.display()
+ )
+ })?;
+ if !output.status.success() {
+ eprintln!("{}", String::from_utf8_lossy(&output.stdout));
+ eprintln!("{}", String::from_utf8_lossy(&output.stderr));
+ bail!(format!("Failed to run cargo tree: {}", output.status))
+ }
+ for (crate_id, features) in
+ parse_features_from_cargo_tree_output(output.stdout.lines())?
+ {
+ crate_features
+ .entry(crate_id)
+ .or_default()
+ .insert(target.to_owned(), features);
+ }
+ }
+ let mut result = BTreeMap::<CrateId, SelectList<String>>::new();
+ for (crate_id, features) in crate_features.into_iter() {
+ let common = features
+ .iter()
+ .fold(
+ None,
+ |common: Option<BTreeSet<String>>, (_, features)| match common {
+ Some(common) => Some(common.intersection(features).cloned().collect()),
+ None => Some(features.clone()),
+ },
+ )
+ .unwrap_or_default();
+ let mut select_list = SelectList::default();
+ for (target, fs) in features {
+ if fs != common {
+ for f in fs {
+ select_list.insert(f, Some(target.clone()));
+ }
+ }
+ }
+ for f in common {
+ select_list.insert(f, None);
+ }
+ result.insert(crate_id, select_list);
+ }
+ Ok(result)
+ }
+}
+
+/// Parses the output of `cargo tree --format=|{p}|{f}|`. Other flags may be
+/// passed to `cargo tree` as well, but this format is critical.
+fn parse_features_from_cargo_tree_output<I, S, E>(
+ lines: I,
+) -> Result<BTreeMap<CrateId, BTreeSet<String>>>
+where
+ I: Iterator<Item = std::result::Result<S, E>>,
+ S: AsRef<str>,
+ E: std::error::Error + Sync + Send + 'static,
+{
+ let mut crate_features = BTreeMap::<CrateId, BTreeSet<String>>::new();
+ for line in lines {
+ let line = line?;
+ let line = line.as_ref();
+ if line.is_empty() {
+ continue;
+ }
+ let parts = line.split('|').collect::<Vec<_>>();
+ if parts.len() != 4 {
+ bail!("Unexpected line '{}'", line);
+ }
+ // We expect the crate id (parts[1]) to be either
+ // "<crate name> v<crate version>" or
+ // "<crate name> v<crate version> (<path>)"
+ // "<crate name> v<crate version> (proc-macro) (<path>)"
+ // https://github.com/rust-lang/cargo/blob/19f952f160d4f750d1e12fad2bf45e995719673d/src/cargo/ops/tree/mod.rs#L281
+ let crate_id_parts = parts[1].split(' ').collect::<Vec<_>>();
+ if crate_id_parts.len() < 2 && crate_id_parts.len() > 4 {
+ bail!(
+ "Unexpected crate id format '{}' when parsing 'cargo tree' output.",
+ parts[1]
+ );
+ }
+ let crate_id = CrateId::new(
+ crate_id_parts[0].to_owned(),
+ crate_id_parts[1]
+ .strip_prefix('v')
+ .ok_or_else(|| {
+ anyhow!(
+ "Unexpected crate version '{}' when parsing 'cargo tree' output.",
+ crate_id_parts[1]
+ )
+ })?
+ .to_owned(),
+ );
+ let mut features = if parts[2].is_empty() {
+ BTreeSet::new()
+ } else {
+ parts[2].split(',').map(str::to_owned).collect()
+ };
+ crate_features
+ .entry(crate_id)
+ .or_default()
+ .append(&mut features);
+ }
+ Ok(crate_features)
+}
+
/// A helper function for writing Cargo metadata to a file.
pub fn write_metadata(path: &Path, metadata: &cargo_metadata::Metadata) -> Result<()> {
let content =
@@ -361,7 +618,7 @@
#[test]
fn deserialize_cargo_update_request_for_eager() {
- for value in ["1", "yes", "true", "on"] {
+ for value in ["all", "full", "eager"] {
let request = CargoUpdateRequest::from_str(value).unwrap();
assert_eq!(request, CargoUpdateRequest::Eager);
@@ -370,7 +627,7 @@
#[test]
fn deserialize_cargo_update_request_for_workspace() {
- for value in ["workspace", "minimal"] {
+ for value in ["1", "true", "yes", "on", "workspace", "minimal"] {
let request = CargoUpdateRequest::from_str(value).unwrap();
assert_eq!(request, CargoUpdateRequest::Workspace);
@@ -402,4 +659,60 @@
}
);
}
+
+ #[test]
+ fn parse_features_from_cargo_tree_output_prefix_none() {
+ assert_eq!(
+ parse_features_from_cargo_tree_output(
+ vec![
+ Ok::<&str, std::io::Error>(""), // Blank lines are ignored.
+ Ok("|multi_cfg_dep v0.1.0 (/private/tmp/ct)||"),
+ Ok("|chrono v0.4.24|default,std|"),
+ Ok("|cpufeatures v0.2.1||"),
+ Ok("|libc v0.2.117|default,std|"),
+ Ok("|serde_derive v1.0.152 (proc-macro) (*)||"),
+ Ok("|chrono v0.4.24|default,std,serde|"),
+ ]
+ .into_iter()
+ )
+ .unwrap(),
+ BTreeMap::from([
+ (
+ CrateId {
+ name: "multi_cfg_dep".to_owned(),
+ version: "0.1.0".to_owned()
+ },
+ BTreeSet::from([])
+ ),
+ (
+ CrateId {
+ name: "cpufeatures".to_owned(),
+ version: "0.2.1".to_owned()
+ },
+ BTreeSet::from([])
+ ),
+ (
+ CrateId {
+ name: "libc".to_owned(),
+ version: "0.2.117".to_owned()
+ },
+ BTreeSet::from(["default".to_owned(), "std".to_owned()])
+ ),
+ (
+ CrateId {
+ name: "serde_derive".to_owned(),
+ version: "1.0.152".to_owned()
+ },
+ BTreeSet::from([])
+ ),
+ (
+ CrateId {
+ name: "chrono".to_owned(),
+ version: "0.4.24".to_owned()
+ },
+ BTreeSet::from(["default".to_owned(), "std".to_owned(), "serde".to_owned()])
+ ),
+ ])
+ );
+ }
}
diff --git a/crate_universe/src/metadata/dependency.rs b/crate_universe/src/metadata/dependency.rs
index 7a98ae2..ceb4931 100644
--- a/crate_universe/src/metadata/dependency.rs
+++ b/crate_universe/src/metadata/dependency.rs
@@ -1,6 +1,8 @@
-///! Gathering dependencies is the largest part of annotating.
+//! Gathering dependencies is the largest part of annotating.
+
use anyhow::{bail, Result};
use cargo_metadata::{Metadata as CargoMetadata, Node, NodeDep, Package, PackageId};
+use cargo_platform::Platform;
use serde::{Deserialize, Serialize};
use crate::utils::sanitize_module_name;
@@ -44,8 +46,8 @@
.partition(|dep| is_dev_dependency(dep));
(
- collect_deps_selectable(dev, metadata),
- collect_deps_selectable(normal, metadata),
+ collect_deps_selectable(node, dev, metadata),
+ collect_deps_selectable(node, normal, metadata),
)
};
@@ -60,11 +62,13 @@
.partition(|dep| is_dev_dependency(dep));
(
- collect_deps_selectable(dev, metadata),
- collect_deps_selectable(normal, metadata),
+ collect_deps_selectable(node, dev, metadata),
+ collect_deps_selectable(node, normal, metadata),
)
};
+ // For rules on build script dependencies see:
+ // https://doc.rust-lang.org/cargo/reference/build-scripts.html#build-dependencies
let (build_proc_macro_deps, mut build_deps) = {
let (proc_macro, normal) = node
.deps
@@ -76,8 +80,8 @@
.partition(|dep| is_proc_macro_package(&metadata[&dep.pkg]));
(
- collect_deps_selectable(proc_macro, metadata),
- collect_deps_selectable(normal, metadata),
+ collect_deps_selectable(node, proc_macro, metadata),
+ collect_deps_selectable(node, normal, metadata),
)
};
@@ -87,6 +91,7 @@
// on a `*-sys` crate for itself, so would it's build script. Hopefully this is correct.
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
+ // https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate
let sys_name = format!("{}-sys", &metadata[&node.id].name);
normal_deps.configurations().into_iter().for_each(|config| {
normal_deps
@@ -113,7 +118,39 @@
}
}
+/// For details on optional dependencies see [the Rust docs](https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies).
+fn is_optional_crate_enabled(
+ parent: &Node,
+ dep: &NodeDep,
+ target: Option<&Platform>,
+ metadata: &CargoMetadata,
+) -> bool {
+ let pkg = &metadata[&parent.id];
+
+ let mut enabled_deps = pkg
+ .features
+ .iter()
+ .filter(|(pkg_feature, _)| parent.features.contains(pkg_feature))
+ .flat_map(|(_, features)| features)
+ .filter_map(|f| f.strip_prefix("dep:"));
+
+ // if the crate is marked as optional dependency, we check whether
+ // a feature prefixed with dep: is enabled
+ if let Some(toml_dep) = pkg
+ .dependencies
+ .iter()
+ .filter(|&d| d.target.as_ref() == target)
+ .filter(|&d| d.optional)
+ .find(|&d| sanitize_module_name(&d.name) == dep.name)
+ {
+ enabled_deps.any(|d| d == toml_dep.name.as_str())
+ } else {
+ true
+ }
+}
+
fn collect_deps_selectable(
+ node: &Node,
deps: Vec<&NodeDep>,
metadata: &cargo_metadata::Metadata,
) -> SelectList<Dependency> {
@@ -126,17 +163,19 @@
let alias = get_target_alias(&dep.name, dep_pkg);
for kind_info in &dep.dep_kinds {
- selectable.insert(
- Dependency {
- package_id: dep.pkg.clone(),
- target_name: target_name.clone(),
- alias: alias.clone(),
- },
- kind_info
- .target
- .as_ref()
- .map(|platform| platform.to_string()),
- );
+ if is_optional_crate_enabled(node, dep, kind_info.target.as_ref(), metadata) {
+ selectable.insert(
+ Dependency {
+ package_id: dep.pkg.clone(),
+ target_name: target_name.clone(),
+ alias: alias.clone(),
+ },
+ kind_info
+ .target
+ .as_ref()
+ .map(|platform| platform.to_string()),
+ );
+ }
}
}
@@ -227,7 +266,7 @@
/// for targets where packages (packages[#].targets[#].name) uses crate names. In order to
/// determine whether or not a dependency is aliased, we compare it with all available targets
/// on it's package. Note that target names are not guaranteed to be module names where Node
-/// dependnecies are, so we need to do a conversion to check for this
+/// dependencies are, so we need to do a conversion to check for this
fn get_target_alias(target_name: &str, package: &Package) -> Option<String> {
match package
.targets
@@ -406,6 +445,69 @@
}
#[test]
+ fn sys_crate_with_build_script() {
+ let metadata = metadata::build_scripts();
+
+ let libssh2 = find_metadata_node("libssh2-sys", &metadata);
+ let libssh2_depset = DependencySet::new_for_node(libssh2, &metadata);
+
+ // Collect build dependencies into a set
+ let build_deps: BTreeSet<String> = libssh2_depset
+ .build_deps
+ .configurations()
+ .into_iter()
+ .flat_map(|conf| {
+ libssh2_depset
+ .build_deps
+ .get_iter(conf)
+ .unwrap()
+ .map(|dep| dep.package_id.repr.clone())
+ .collect::<Vec<String>>()
+ })
+ .collect();
+
+ assert_eq!(
+ BTreeSet::from([
+ "cc 1.0.72 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
+ "pkg-config 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)"
+ .to_owned(),
+ "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)".to_owned()
+ ]),
+ build_deps,
+ );
+
+ // Collect normal dependencies into a set
+ let normal_deps: BTreeSet<String> = libssh2_depset
+ .normal_deps
+ .configurations()
+ .into_iter()
+ .flat_map(|conf| {
+ libssh2_depset
+ .normal_deps
+ .get_iter(conf)
+ .unwrap()
+ .map(|dep| dep.package_id.to_string())
+ .collect::<Vec<String>>()
+ })
+ .collect();
+
+ assert_eq!(
+ BTreeSet::from([
+ "libc 0.2.112 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
+ "libz-sys 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)".to_owned(),
+ "openssl-sys 0.9.87 (registry+https://github.com/rust-lang/crates.io-index)"
+ .to_owned(),
+ ]),
+ normal_deps,
+ );
+
+ assert!(libssh2_depset.proc_macro_deps.is_empty());
+ assert!(libssh2_depset.normal_dev_deps.is_empty());
+ assert!(libssh2_depset.proc_macro_dev_deps.is_empty());
+ assert!(libssh2_depset.build_proc_macro_deps.is_empty());
+ }
+
+ #[test]
fn tracked_aliases() {
let metadata = metadata::alias();
@@ -465,7 +567,7 @@
let node = find_metadata_node("cpufeatures", &metadata);
let dependencies = DependencySet::new_for_node(node, &metadata);
- let libc_cfgs: Vec<Option<String>> = dependencies
+ let libc_cfgs: BTreeSet<String> = dependencies
.normal_deps
.configurations()
.into_iter()
@@ -477,17 +579,73 @@
.filter(|dep| dep.target_name == "libc")
.map(move |_| conf.cloned())
})
+ .flatten()
.collect();
- assert_eq!(libc_cfgs.len(), 2);
-
- let cfg_strs: BTreeSet<String> = libc_cfgs.into_iter().flatten().collect();
assert_eq!(
- cfg_strs,
BTreeSet::from([
- "aarch64-apple-darwin".to_owned(),
+ "aarch64-linux-android".to_owned(),
"cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))".to_owned(),
- ])
+ "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))".to_owned(),
+ ]),
+ libc_cfgs,
);
}
+
+ #[test]
+ fn optional_deps_disabled() {
+ let metadata = metadata::optional_deps_disabled();
+
+ let node = find_metadata_node("clap", &metadata);
+ let dependencies = DependencySet::new_for_node(node, &metadata);
+
+ assert!(!dependencies
+ .normal_deps
+ .get_iter(None)
+ .expect("Iterating over known keys should never panic")
+ .any(|dep| { dep.target_name == "is-terminal" || dep.target_name == "termcolor" }));
+ }
+
+ #[test]
+ fn optional_deps_enabled() {
+ let metadata = metadata::optional_deps_enabled();
+
+ let clap = find_metadata_node("clap", &metadata);
+ let clap_depset = DependencySet::new_for_node(clap, &metadata);
+ assert_eq!(
+ clap_depset
+ .normal_deps
+ .get_iter(None)
+ .expect("Iterating over known keys should never panic")
+ .filter(|dep| {
+ dep.target_name == "is-terminal" || dep.target_name == "termcolor"
+ })
+ .count(),
+ 2
+ );
+
+ let notify = find_metadata_node("notify", &metadata);
+ let notify_depset = DependencySet::new_for_node(notify, &metadata);
+
+ // mio is not present in the common list of dependencies
+ assert!(!notify_depset
+ .normal_deps
+ .get_iter(None)
+ .expect("Iterating over known keys should never panic")
+ .any(|dep| { dep.target_name == "mio" }));
+
+ // mio is a dependency on linux
+ assert!(notify_depset
+ .normal_deps
+ .get_iter(Some(&"cfg(target_os = \"linux\")".to_string()))
+ .expect("Iterating over known keys should never panic")
+ .any(|dep| { dep.target_name == "mio" }));
+
+ // mio is marked optional=true on macos
+ assert!(!notify_depset
+ .normal_deps
+ .get_iter(Some(&"cfg(target_os = \"macos\")".to_string()))
+ .expect("Iterating over known keys should never panic")
+ .any(|dep| { dep.target_name == "mio" }));
+ }
}
diff --git a/crate_universe/src/metadata/metadata_annotation.rs b/crate_universe/src/metadata/metadata_annotation.rs
index c045519..b19d696 100644
--- a/crate_universe/src/metadata/metadata_annotation.rs
+++ b/crate_universe/src/metadata/metadata_annotation.rs
@@ -12,6 +12,7 @@
use crate::config::{Commitish, Config, CrateAnnotations, CrateId};
use crate::metadata::dependency::DependencySet;
use crate::splicing::{SourceInfo, WorkspaceMetadata};
+use crate::utils::starlark::SelectList;
pub type CargoMetadata = cargo_metadata::Metadata;
pub type CargoLockfile = cargo_lock::Lockfile;
@@ -359,6 +360,9 @@
/// Pairred crate annotations
pub pairred_extras: BTreeMap<CrateId, PairredExtras>,
+
+ /// Feature set for each target triplet and crate.
+ pub features: BTreeMap<CrateId, SelectList<String>>,
}
impl Annotations {
@@ -415,12 +419,15 @@
);
}
+ let features = metadata_annotation.workspace_metadata.features.clone();
+
// Annotate metadata
Ok(Annotations {
metadata: metadata_annotation,
lockfile: lockfile_annotation,
config,
pairred_extras,
+ features,
})
}
}
@@ -549,7 +556,7 @@
let result = Annotations::new(test::metadata::no_deps(), test::lockfile::no_deps(), config);
assert!(result.is_err());
- let result_str = format!("{:?}", result);
+ let result_str = format!("{result:?}");
assert!(result_str.contains("Unused annotations were provided. Please remove them"));
assert!(result_str.contains("mock-crate"));
}
diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs
index c4cc249..1e9818d 100644
--- a/crate_universe/src/rendering.rs
+++ b/crate_universe/src/rendering.rs
@@ -2,35 +2,59 @@
mod template_engine;
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, BTreeSet};
use std::fs;
+use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{bail, Context as AnyhowContext, Result};
+use indoc::formatdoc;
-use crate::config::RenderConfig;
-use crate::context::Context;
+use crate::config::{RenderConfig, VendorMode};
+use crate::context::crate_context::{CrateContext, CrateDependency, Rule};
+use crate::context::{Context, TargetAttributes};
use crate::rendering::template_engine::TemplateEngine;
use crate::splicing::default_splicing_package_crate_id;
-use crate::utils::starlark::Label;
+use crate::utils::starlark::{
+ self, Alias, CargoBuildScript, CommonAttrs, Data, ExportsFiles, Filegroup, Glob, Label, Load,
+ Package, RustBinary, RustLibrary, RustProcMacro, Select, SelectDict, SelectList, SelectMap,
+ Starlark, TargetCompatibleWith,
+};
+use crate::utils::{self, sanitize_repository_name};
+
+// Configuration remapper used to convert from cfg expressions like "cfg(unix)"
+// to platform labels like "@rules_rust//rust/platform:x86_64-unknown-linux-gnu".
+pub(crate) type Platforms = BTreeMap<String, BTreeSet<String>>;
pub struct Renderer {
config: RenderConfig,
+ supported_platform_triples: BTreeSet<String>,
+ generate_target_compatible_with: bool,
engine: TemplateEngine,
}
impl Renderer {
- pub fn new(config: RenderConfig) -> Self {
+ pub fn new(
+ config: RenderConfig,
+ supported_platform_triples: BTreeSet<String>,
+ generate_target_compatible_with: bool,
+ ) -> Self {
let engine = TemplateEngine::new(&config);
- Self { config, engine }
+ Self {
+ config,
+ supported_platform_triples,
+ generate_target_compatible_with,
+ engine,
+ }
}
pub fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
let mut output = BTreeMap::new();
- output.extend(self.render_build_files(context)?);
- output.extend(self.render_crates_module(context)?);
+ let platforms = self.render_platform_labels(context);
+ output.extend(self.render_build_files(context, &platforms)?);
+ output.extend(self.render_crates_module(context, &platforms)?);
if let Some(vendor_mode) = &self.config.vendor_mode {
match vendor_mode {
@@ -46,7 +70,32 @@
Ok(output)
}
- fn render_crates_module(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+ fn render_platform_labels(&self, context: &Context) -> BTreeMap<String, BTreeSet<String>> {
+ context
+ .conditions
+ .iter()
+ .map(|(cfg, triples)| {
+ (
+ cfg.clone(),
+ triples
+ .iter()
+ .map(|triple| {
+ render_platform_constraint_label(
+ &self.config.platforms_template,
+ triple,
+ )
+ })
+ .collect(),
+ )
+ })
+ .collect()
+ }
+
+ fn render_crates_module(
+ &self,
+ context: &Context,
+ platforms: &Platforms,
+ ) -> Result<BTreeMap<PathBuf, String>> {
let module_label = render_module_label(&self.config.crates_module_template, "defs.bzl")
.context("Failed to resolve string to module file label")?;
let module_build_label =
@@ -56,43 +105,510 @@
let mut map = BTreeMap::new();
map.insert(
Renderer::label_to_path(&module_label),
- self.engine.render_module_bzl(context)?,
+ self.engine.render_module_bzl(context, platforms)?,
);
map.insert(
Renderer::label_to_path(&module_build_label),
- self.engine.render_module_build_file(context)?,
+ self.render_module_build_file(context)?,
);
Ok(map)
}
- fn render_build_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+ fn render_module_build_file(&self, context: &Context) -> Result<String> {
+ let mut starlark = Vec::new();
+
+ // Banner comment for top of the file.
+ let header = self.engine.render_header()?;
+ starlark.push(Starlark::Verbatim(header));
+
+ // Package visibility, exported bzl files.
+ let package = Package::default_visibility_public();
+ starlark.push(Starlark::Package(package));
+
+ let mut exports_files = ExportsFiles {
+ paths: BTreeSet::from(["cargo-bazel.json".to_owned(), "defs.bzl".to_owned()]),
+ globs: Glob {
+ include: BTreeSet::from(["*.bazel".to_owned()]),
+ exclude: BTreeSet::new(),
+ },
+ };
+ if let Some(VendorMode::Remote) = self.config.vendor_mode {
+ exports_files.paths.insert("crates.bzl".to_owned());
+ }
+ starlark.push(Starlark::ExportsFiles(exports_files));
+
+ let filegroup = Filegroup {
+ name: "srcs".to_owned(),
+ srcs: Glob {
+ include: BTreeSet::from(["*.bazel".to_owned(), "*.bzl".to_owned()]),
+ exclude: BTreeSet::new(),
+ },
+ };
+ starlark.push(Starlark::Filegroup(filegroup));
+
+ // An `alias` for each direct dependency of a workspace member crate.
+ let mut dependencies = Vec::new();
+ for dep in context.workspace_member_deps() {
+ let krate = &context.crates[&dep.id];
+ if let Some(library_target_name) = &krate.library_target_name {
+ let rename = dep.alias.as_ref().unwrap_or(&krate.name);
+ dependencies.push(Alias {
+ // If duplicates exist, include version to disambiguate them.
+ name: if context.has_duplicate_workspace_member_dep(dep) {
+ format!("{}-{}", rename, krate.version)
+ } else {
+ rename.clone()
+ },
+ actual: self.crate_label(&krate.name, &krate.version, library_target_name),
+ tags: BTreeSet::from(["manual".to_owned()]),
+ });
+ }
+ }
+ if !dependencies.is_empty() {
+ let comment = "# Workspace Member Dependencies".to_owned();
+ starlark.push(Starlark::Verbatim(comment));
+ starlark.extend(dependencies.into_iter().map(Starlark::Alias));
+ }
+
+ // An `alias` for each binary dependency.
+ let mut binaries = Vec::new();
+ for crate_id in &context.binary_crates {
+ let krate = &context.crates[crate_id];
+ for rule in &krate.targets {
+ if let Rule::Binary(bin) = rule {
+ binaries.push(Alias {
+ // If duplicates exist, include version to disambiguate them.
+ name: if context.has_duplicate_binary_crate(crate_id) {
+ format!("{}-{}__{}", krate.name, krate.version, bin.crate_name)
+ } else {
+ format!("{}__{}", krate.name, bin.crate_name)
+ },
+ actual: self.crate_label(
+ &krate.name,
+ &krate.version,
+ &format!("{}__bin", bin.crate_name),
+ ),
+ tags: BTreeSet::from(["manual".to_owned()]),
+ });
+ }
+ }
+ }
+ if !binaries.is_empty() {
+ let comment = "# Binaries".to_owned();
+ starlark.push(Starlark::Verbatim(comment));
+ starlark.extend(binaries.into_iter().map(Starlark::Alias));
+ }
+
+ let starlark = starlark::serialize(&starlark)?;
+ Ok(starlark)
+ }
+
+ fn render_build_files(
+ &self,
+ context: &Context,
+ platforms: &Platforms,
+ ) -> Result<BTreeMap<PathBuf, String>> {
let default_splicing_package_id = default_splicing_package_crate_id();
- self.engine
- .render_crate_build_files(context)?
- .into_iter()
+ context
+ .crates
+ .keys()
// Do not render the default splicing package
- .filter(|(id, _)| *id != &default_splicing_package_id)
+ .filter(|id| *id != &default_splicing_package_id)
// Do not render local packages
- .filter(|(id, _)| !context.workspace_members.contains_key(id))
- .map(|(id, content)| {
- let ctx = &context.crates[id];
+ .filter(|id| !context.workspace_members.contains_key(id))
+ .map(|id| {
let label = match render_build_file_template(
&self.config.build_file_template,
- &ctx.name,
- &ctx.version,
+ &id.name,
+ &id.version,
) {
Ok(label) => label,
Err(e) => bail!(e),
};
let filename = Renderer::label_to_path(&label);
-
+ let content = self.render_one_build_file(platforms, &context.crates[id])?;
Ok((filename, content))
})
.collect()
}
+ fn render_one_build_file(&self, platforms: &Platforms, krate: &CrateContext) -> Result<String> {
+ let mut starlark = Vec::new();
+
+ // Banner comment for top of the file.
+ let header = self.engine.render_header()?;
+ starlark.push(Starlark::Verbatim(header));
+
+ // Loads: map of bzl file to set of items imported from that file. These
+ // get inserted into `starlark` at the bottom of this function.
+ let mut loads: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
+ let mut load = |bzl: &str, item: &str| {
+ loads
+ .entry(bzl.to_owned())
+ .or_default()
+ .insert(item.to_owned())
+ };
+
+ let disable_visibility = "# buildifier: disable=bzl-visibility".to_owned();
+ starlark.push(Starlark::Verbatim(disable_visibility));
+ starlark.push(Starlark::Load(Load {
+ bzl: "@rules_rust//crate_universe/private:selects.bzl".to_owned(),
+ items: BTreeSet::from(["selects".to_owned()]),
+ }));
+
+ // Package visibility.
+ let package = Package::default_visibility_public();
+ starlark.push(Starlark::Package(package));
+
+ if let Some(license) = &krate.license {
+ starlark.push(Starlark::Verbatim(formatdoc! {r#"
+ # licenses([
+ # "TODO", # {license}
+ # ])
+ "#}));
+ }
+
+ for rule in &krate.targets {
+ match rule {
+ Rule::BuildScript(target) => {
+ load("@rules_rust//cargo:defs.bzl", "cargo_build_script");
+ let cargo_build_script =
+ self.make_cargo_build_script(platforms, krate, target)?;
+ starlark.push(Starlark::CargoBuildScript(cargo_build_script));
+ starlark.push(Starlark::Alias(Alias {
+ name: target.crate_name.clone(),
+ actual: format!("{}_build_script", krate.name),
+ tags: BTreeSet::from(["manual".to_owned()]),
+ }));
+ }
+ Rule::ProcMacro(target) => {
+ load("@rules_rust//rust:defs.bzl", "rust_proc_macro");
+ let rust_proc_macro = self.make_rust_proc_macro(platforms, krate, target)?;
+ starlark.push(Starlark::RustProcMacro(rust_proc_macro));
+ }
+ Rule::Library(target) => {
+ load("@rules_rust//rust:defs.bzl", "rust_library");
+ let rust_library = self.make_rust_library(platforms, krate, target)?;
+ starlark.push(Starlark::RustLibrary(rust_library));
+ }
+ Rule::Binary(target) => {
+ load("@rules_rust//rust:defs.bzl", "rust_binary");
+ let rust_binary = self.make_rust_binary(platforms, krate, target)?;
+ starlark.push(Starlark::RustBinary(rust_binary));
+ }
+ }
+ }
+
+ if let Some(additive_build_file_content) = &krate.additive_build_file_content {
+ let comment = "# Additive BUILD file content".to_owned();
+ starlark.push(Starlark::Verbatim(comment));
+ starlark.push(Starlark::Verbatim(additive_build_file_content.clone()));
+ }
+
+ // Insert all the loads immediately after the header banner comment.
+ let loads = loads
+ .into_iter()
+ .map(|(bzl, items)| Starlark::Load(Load { bzl, items }));
+ starlark.splice(1..1, loads);
+
+ let starlark = starlark::serialize(&starlark)?;
+ Ok(starlark)
+ }
+
+ fn make_cargo_build_script(
+ &self,
+ platforms: &Platforms,
+ krate: &CrateContext,
+ target: &TargetAttributes,
+ ) -> Result<CargoBuildScript> {
+ let empty_set = BTreeSet::<String>::new();
+ let empty_list = SelectList::<String>::default();
+ let empty_deps = SelectList::<CrateDependency>::default();
+ let attrs = krate.build_script_attrs.as_ref();
+
+ Ok(CargoBuildScript {
+ // Because `cargo_build_script` does some invisible target name
+ // mutating to determine the package and crate name for a build
+ // script, the Bazel target name of any build script cannot be the
+ // Cargo canonical name of "cargo_build_script" without losing out
+ // on having certain Cargo environment variables set.
+ //
+ // Do not change this name to "cargo_build_script".
+ name: format!("{}_build_script", krate.name),
+ aliases: self
+ .make_aliases(krate, true, false)
+ .remap_configurations(platforms),
+ build_script_env: attrs
+ .map_or_else(SelectDict::default, |attrs| attrs.build_script_env.clone())
+ .remap_configurations(platforms),
+ compile_data: make_data(
+ platforms,
+ &empty_set,
+ attrs.map_or(&empty_list, |attrs| &attrs.compile_data),
+ ),
+ crate_features: SelectList::from(&krate.common_attrs.crate_features)
+ .map_configuration_names(|triple| {
+ render_platform_constraint_label(&self.config.platforms_template, &triple)
+ }),
+ crate_name: utils::sanitize_module_name(&target.crate_name),
+ crate_root: target.crate_root.clone(),
+ data: make_data(
+ platforms,
+ attrs.map_or(&empty_set, |attrs| &attrs.data_glob),
+ attrs.map_or(&empty_list, |attrs| &attrs.data),
+ ),
+ deps: self
+ .make_deps(
+ attrs.map_or(&empty_deps, |attrs| &attrs.deps),
+ attrs.map_or(&empty_set, |attrs| &attrs.extra_deps),
+ )
+ .remap_configurations(platforms),
+ edition: krate.common_attrs.edition.clone(),
+ linker_script: krate.common_attrs.linker_script.clone(),
+ links: attrs.and_then(|attrs| attrs.links.clone()),
+ proc_macro_deps: self
+ .make_deps(
+ attrs.map_or(&empty_deps, |attrs| &attrs.proc_macro_deps),
+ attrs.map_or(&empty_set, |attrs| &attrs.extra_proc_macro_deps),
+ )
+ .remap_configurations(platforms),
+ rustc_env: attrs
+ .map_or_else(SelectDict::default, |attrs| attrs.rustc_env.clone())
+ .remap_configurations(platforms),
+ rustc_env_files: attrs
+ .map_or_else(SelectList::default, |attrs| attrs.rustc_env_files.clone())
+ .remap_configurations(platforms),
+ rustc_flags: {
+ let mut rustc_flags =
+ attrs.map_or_else(SelectList::default, |attrs| attrs.rustc_flags.clone());
+ // In most cases, warnings in 3rd party crates are not
+ // interesting as they're out of the control of consumers. The
+ // flag here silences warnings. For more details see:
+ // https://doc.rust-lang.org/rustc/lints/levels.html
+ rustc_flags.insert("--cap-lints=allow".to_owned(), None);
+ rustc_flags.remap_configurations(platforms)
+ },
+ srcs: target.srcs.clone(),
+ tags: {
+ let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
+ tags.insert("cargo-bazel".to_owned());
+ tags.insert("manual".to_owned());
+ tags.insert("noclippy".to_owned());
+ tags.insert("norustfmt".to_owned());
+ tags.insert(format!("crate-name={}", krate.name));
+ tags
+ },
+ tools: attrs
+ .map_or_else(SelectList::default, |attrs| attrs.tools.clone())
+ .remap_configurations(platforms),
+ toolchains: attrs.map_or_else(BTreeSet::new, |attrs| attrs.toolchains.clone()),
+ version: krate.common_attrs.version.clone(),
+ visibility: BTreeSet::from(["//visibility:private".to_owned()]),
+ })
+ }
+
+ fn make_rust_proc_macro(
+ &self,
+ platforms: &Platforms,
+ krate: &CrateContext,
+ target: &TargetAttributes,
+ ) -> Result<RustProcMacro> {
+ Ok(RustProcMacro {
+ name: target.crate_name.clone(),
+ deps: self
+ .make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps)
+ .remap_configurations(platforms),
+ proc_macro_deps: self
+ .make_deps(
+ &krate.common_attrs.proc_macro_deps,
+ &krate.common_attrs.extra_proc_macro_deps,
+ )
+ .remap_configurations(platforms),
+ aliases: self
+ .make_aliases(krate, false, false)
+ .remap_configurations(platforms),
+ common: self.make_common_attrs(platforms, krate, target)?,
+ })
+ }
+
+ fn make_rust_library(
+ &self,
+ platforms: &Platforms,
+ krate: &CrateContext,
+ target: &TargetAttributes,
+ ) -> Result<RustLibrary> {
+ Ok(RustLibrary {
+ name: target.crate_name.clone(),
+ deps: self
+ .make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps)
+ .remap_configurations(platforms),
+ proc_macro_deps: self
+ .make_deps(
+ &krate.common_attrs.proc_macro_deps,
+ &krate.common_attrs.extra_proc_macro_deps,
+ )
+ .remap_configurations(platforms),
+ aliases: self
+ .make_aliases(krate, false, false)
+ .remap_configurations(platforms),
+ common: self.make_common_attrs(platforms, krate, target)?,
+ disable_pipelining: krate.disable_pipelining,
+ })
+ }
+
+ fn make_rust_binary(
+ &self,
+ platforms: &Platforms,
+ krate: &CrateContext,
+ target: &TargetAttributes,
+ ) -> Result<RustBinary> {
+ Ok(RustBinary {
+ name: format!("{}__bin", target.crate_name),
+ deps: {
+ let mut deps =
+ self.make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps);
+ if let Some(library_target_name) = &krate.library_target_name {
+ deps.insert(format!(":{library_target_name}"), None);
+ }
+ deps.remap_configurations(platforms)
+ },
+ proc_macro_deps: self
+ .make_deps(
+ &krate.common_attrs.proc_macro_deps,
+ &krate.common_attrs.extra_proc_macro_deps,
+ )
+ .remap_configurations(platforms),
+ aliases: self
+ .make_aliases(krate, false, false)
+ .remap_configurations(platforms),
+ common: self.make_common_attrs(platforms, krate, target)?,
+ })
+ }
+
+ fn make_common_attrs(
+ &self,
+ platforms: &Platforms,
+ krate: &CrateContext,
+ target: &TargetAttributes,
+ ) -> Result<CommonAttrs> {
+ Ok(CommonAttrs {
+ compile_data: make_data(
+ platforms,
+ &krate.common_attrs.compile_data_glob,
+ &krate.common_attrs.compile_data,
+ ),
+ crate_features: SelectList::from(&krate.common_attrs.crate_features)
+ .map_configuration_names(|triple| {
+ render_platform_constraint_label(&self.config.platforms_template, &triple)
+ }),
+ crate_root: target.crate_root.clone(),
+ data: make_data(
+ platforms,
+ &krate.common_attrs.data_glob,
+ &krate.common_attrs.data,
+ ),
+ edition: krate.common_attrs.edition.clone(),
+ linker_script: krate.common_attrs.linker_script.clone(),
+ rustc_env: krate
+ .common_attrs
+ .rustc_env
+ .clone()
+ .remap_configurations(platforms),
+ rustc_env_files: krate
+ .common_attrs
+ .rustc_env_files
+ .clone()
+ .remap_configurations(platforms),
+ rustc_flags: {
+ let mut rustc_flags = krate.common_attrs.rustc_flags.clone();
+ // In most cases, warnings in 3rd party crates are not
+ // interesting as they're out of the control of consumers. The
+ // flag here silences warnings. For more details see:
+ // https://doc.rust-lang.org/rustc/lints/levels.html
+ rustc_flags.insert(0, "--cap-lints=allow".to_owned());
+ rustc_flags
+ },
+ srcs: target.srcs.clone(),
+ tags: {
+ let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
+ tags.insert("cargo-bazel".to_owned());
+ tags.insert("manual".to_owned());
+ tags.insert("noclippy".to_owned());
+ tags.insert("norustfmt".to_owned());
+ tags.insert(format!("crate-name={}", krate.name));
+ tags
+ },
+ target_compatible_with: self.generate_target_compatible_with.then(|| {
+ TargetCompatibleWith::new(
+ self.supported_platform_triples
+ .iter()
+ .map(|triple| {
+ render_platform_constraint_label(
+ &self.config.platforms_template,
+ triple,
+ )
+ })
+ .collect(),
+ )
+ }),
+ version: krate.common_attrs.version.clone(),
+ })
+ }
+
+ /// Filter a crate's dependencies to only ones with aliases
+ fn make_aliases(
+ &self,
+ krate: &CrateContext,
+ build: bool,
+ include_dev: bool,
+ ) -> SelectDict<String> {
+ let mut dep_lists = Vec::new();
+ if build {
+ if let Some(build_script_attrs) = &krate.build_script_attrs {
+ dep_lists.push(&build_script_attrs.deps);
+ dep_lists.push(&build_script_attrs.proc_macro_deps);
+ }
+ } else {
+ dep_lists.push(&krate.common_attrs.deps);
+ dep_lists.push(&krate.common_attrs.proc_macro_deps);
+ if include_dev {
+ dep_lists.push(&krate.common_attrs.deps_dev);
+ dep_lists.push(&krate.common_attrs.proc_macro_deps_dev);
+ }
+ }
+
+ let mut aliases = SelectDict::default();
+ for (dep, conf) in dep_lists.into_iter().flat_map(|deps| {
+ deps.configurations().into_iter().flat_map(move |conf| {
+ deps.get_iter(conf)
+ .expect("Iterating over known keys should never panic")
+ .map(move |dep| (dep, conf))
+ })
+ }) {
+ if let Some(alias) = &dep.alias {
+ let label = self.crate_label(&dep.id.name, &dep.id.version, &dep.target);
+ aliases.insert(label, alias.clone(), conf.cloned());
+ }
+ }
+ aliases
+ }
+
+ fn make_deps(
+ &self,
+ deps: &SelectList<CrateDependency>,
+ extra_deps: &BTreeSet<String>,
+ ) -> SelectList<String> {
+ let mut deps = deps
+ .clone()
+ .map(|dep| self.crate_label(&dep.id.name, &dep.id.version, &dep.target));
+ for extra_dep in extra_deps {
+ deps.insert(extra_dep.clone(), None);
+ }
+ deps
+ }
+
fn render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
let module_label = render_module_label(&self.config.crates_module_template, "crates.bzl")
.context("Failed to resolve string to module file label")?;
@@ -112,9 +628,19 @@
None => PathBuf::from(&label.target),
}
}
+
+ fn crate_label(&self, name: &str, version: &str, target: &str) -> String {
+ sanitize_repository_name(&render_crate_bazel_label(
+ &self.config.crate_label_template,
+ &self.config.repository_name,
+ name,
+ version,
+ target,
+ ))
+ }
}
-/// Write a set of [CrateContext][crate::context::CrateContext] to disk.
+/// Write a set of [crate::context::crate_context::CrateContext] to disk.
pub fn write_outputs(
outputs: BTreeMap<PathBuf, String>,
out_dir: &Path,
@@ -134,7 +660,7 @@
println!(
"==============================================================================="
);
- println!("{}\n", content);
+ println!("{content}\n");
}
} else {
for (path, content) in outputs {
@@ -193,7 +719,7 @@
}
/// Render the Bazel label of a platform triple
-pub fn render_platform_constraint_label(template: &str, triple: &str) -> String {
+fn render_platform_constraint_label(template: &str, triple: &str) -> String {
template.replace("{triple}", triple)
}
@@ -205,23 +731,43 @@
)
}
+fn make_data(platforms: &Platforms, glob: &BTreeSet<String>, select: &SelectList<String>) -> Data {
+ const COMMON_GLOB_EXCLUDES: &[&str] = &[
+ "**/* *",
+ "BUILD.bazel",
+ "BUILD",
+ "WORKSPACE.bazel",
+ "WORKSPACE",
+ ".tmp_git_root/**/*",
+ ];
+
+ Data {
+ glob: Glob {
+ include: glob.clone(),
+ exclude: COMMON_GLOB_EXCLUDES
+ .iter()
+ .map(|&glob| glob.to_owned())
+ .collect(),
+ },
+ select: select.clone().remap_configurations(platforms),
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
+ use indoc::indoc;
+ use std::collections::BTreeSet;
+
use crate::config::{Config, CrateId, VendorMode};
use crate::context::crate_context::{CrateContext, Rule};
- use crate::context::{BuildScriptAttributes, CommonAttributes, Context, TargetAttributes};
+ use crate::context::{
+ BuildScriptAttributes, CommonAttributes, Context, CrateFeatures, TargetAttributes,
+ };
use crate::metadata::Annotations;
use crate::test;
-
- fn mock_render_config() -> RenderConfig {
- serde_json::from_value(serde_json::json!({
- "repository_name": "test_rendering",
- "regen_command": "cargo_bazel_regen_command",
- }))
- .unwrap()
- }
+ use crate::utils::starlark::SelectList;
fn mock_target_attributes() -> TargetAttributes {
TargetAttributes {
@@ -231,6 +777,42 @@
}
}
+ fn mock_render_config(vendor_mode: Option<VendorMode>) -> RenderConfig {
+ RenderConfig {
+ repository_name: "test_rendering".to_owned(),
+ regen_command: "cargo_bazel_regen_command".to_owned(),
+ vendor_mode,
+ ..RenderConfig::default()
+ }
+ }
+
+ fn mock_supported_platform_triples() -> BTreeSet<String> {
+ BTreeSet::from([
+ "aarch64-apple-darwin".to_owned(),
+ "aarch64-apple-ios".to_owned(),
+ "aarch64-linux-android".to_owned(),
+ "aarch64-pc-windows-msvc".to_owned(),
+ "aarch64-unknown-linux-gnu".to_owned(),
+ "arm-unknown-linux-gnueabi".to_owned(),
+ "armv7-unknown-linux-gnueabi".to_owned(),
+ "i686-apple-darwin".to_owned(),
+ "i686-linux-android".to_owned(),
+ "i686-pc-windows-msvc".to_owned(),
+ "i686-unknown-freebsd".to_owned(),
+ "i686-unknown-linux-gnu".to_owned(),
+ "powerpc-unknown-linux-gnu".to_owned(),
+ "s390x-unknown-linux-gnu".to_owned(),
+ "wasm32-unknown-unknown".to_owned(),
+ "wasm32-wasi".to_owned(),
+ "x86_64-apple-darwin".to_owned(),
+ "x86_64-apple-ios".to_owned(),
+ "x86_64-linux-android".to_owned(),
+ "x86_64-pc-windows-msvc".to_owned(),
+ "x86_64-unknown-freebsd".to_owned(),
+ "x86_64-unknown-linux-gnu".to_owned(),
+ ])
+ }
+
#[test]
fn render_rust_library() {
let mut context = Context::default();
@@ -240,12 +822,16 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Library(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
..CrateContext::default()
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -254,6 +840,36 @@
assert!(build_file_content.contains("rust_library("));
assert!(build_file_content.contains("name = \"mock_crate\""));
+ assert!(build_file_content.contains("\"crate-name=mock_crate\""));
+ }
+
+ #[test]
+ fn test_disable_pipelining() {
+ let mut context = Context::default();
+ let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+ context.crates.insert(
+ crate_id.clone(),
+ CrateContext {
+ name: crate_id.name,
+ version: crate_id.version,
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
+ disable_pipelining: true,
+ ..CrateContext::default()
+ },
+ );
+
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
+ let output = renderer.render(&context).unwrap();
+
+ let build_file_content = output
+ .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+ .unwrap();
+
+ assert!(build_file_content.contains("disable_pipelining = True"));
}
#[test]
@@ -265,18 +881,22 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::BuildScript(TargetAttributes {
+ targets: BTreeSet::from([Rule::BuildScript(TargetAttributes {
crate_name: "build_script_build".to_owned(),
crate_root: Some("build.rs".to_owned()),
..TargetAttributes::default()
- })],
+ })]),
// Build script attributes are required.
build_script_attrs: Some(BuildScriptAttributes::default()),
..CrateContext::default()
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -285,6 +905,7 @@
assert!(build_file_content.contains("cargo_build_script("));
assert!(build_file_content.contains("name = \"build_script_build\""));
+ assert!(build_file_content.contains("\"crate-name=mock_crate\""));
// Ensure `cargo_build_script` requirements are met
assert!(build_file_content.contains("name = \"mock_crate_build_script\""));
@@ -299,12 +920,16 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::ProcMacro(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::ProcMacro(mock_target_attributes())]),
..CrateContext::default()
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -313,6 +938,7 @@
assert!(build_file_content.contains("rust_proc_macro("));
assert!(build_file_content.contains("name = \"mock_crate\""));
+ assert!(build_file_content.contains("\"crate-name=mock_crate\""));
}
#[test]
@@ -324,12 +950,16 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Binary(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]),
..CrateContext::default()
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -338,6 +968,7 @@
assert!(build_file_content.contains("rust_binary("));
assert!(build_file_content.contains("name = \"mock_crate__bin\""));
+ assert!(build_file_content.contains("\"crate-name=mock_crate\""));
}
#[test]
@@ -349,7 +980,7 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Binary(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]),
additive_build_file_content: Some(
"# Hello World from additive section!".to_owned(),
),
@@ -357,7 +988,11 @@
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -369,15 +1004,19 @@
#[test]
fn render_aliases() {
- let annotations = Annotations::new(
- test::metadata::alias(),
- test::lockfile::alias(),
- Config::default(),
- )
- .unwrap();
+ let config = Config {
+ generate_binaries: true,
+ ..Config::default()
+ };
+ let annotations =
+ Annotations::new(test::metadata::alias(), test::lockfile::alias(), config).unwrap();
let context = Context::new(annotations).unwrap();
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output.get(&PathBuf::from("BUILD.bazel")).unwrap();
@@ -395,12 +1034,16 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Library(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
..CrateContext::default()
},
);
- let renderer = Renderer::new(mock_render_config());
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
@@ -417,18 +1060,17 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Library(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
..CrateContext::default()
},
);
// Enable remote vendor mode
- let config = RenderConfig {
- vendor_mode: Some(VendorMode::Remote),
- ..mock_render_config()
- };
-
- let renderer = Renderer::new(config);
+ let renderer = Renderer::new(
+ mock_render_config(Some(VendorMode::Remote)),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
@@ -447,18 +1089,17 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Library(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
..CrateContext::default()
},
);
// Enable local vendor mode
- let config = RenderConfig {
- vendor_mode: Some(VendorMode::Local),
- ..mock_render_config()
- };
-
- let renderer = Renderer::new(config);
+ let renderer = Renderer::new(
+ mock_render_config(Some(VendorMode::Local)),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
// Local vendoring does not produce a `crate_repositories` macro
@@ -486,7 +1127,7 @@
CrateContext {
name: crate_id.name,
version: crate_id.version,
- targets: vec![Rule::Library(mock_target_attributes())],
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
common_attrs: CommonAttributes {
rustc_flags: rustc_flags.clone(),
..CommonAttributes::default()
@@ -496,12 +1137,11 @@
);
// Enable local vendor mode
- let config = RenderConfig {
- vendor_mode: Some(VendorMode::Local),
- ..mock_render_config()
- };
-
- let renderer = Renderer::new(config);
+ let renderer = Renderer::new(
+ mock_render_config(Some(VendorMode::Local)),
+ mock_supported_platform_triples(),
+ true,
+ );
let output = renderer.render(&context).unwrap();
let build_file_content = output
@@ -513,9 +1153,143 @@
assert!(build_file_content.replace(' ', "").contains(
&rustc_flags
.iter()
- .map(|s| format!("\"{}\",", s))
+ .map(|s| format!("\"{s}\","))
.collect::<Vec<String>>()
.join("\n")
));
}
+
+ #[test]
+ fn test_render_build_file_deps() {
+ let config: Config = serde_json::from_value(serde_json::json!({
+ "generate_binaries": false,
+ "generate_build_scripts": false,
+ "rendering": {
+ "repository_name": "multi_cfg_dep",
+ "regen_command": "bazel test //crate_universe:unit_test",
+ },
+ "supported_platform_triples": [
+ "x86_64-apple-darwin",
+ "x86_64-unknown-linux-gnu",
+ "aarch64-apple-darwin",
+ "aarch64-unknown-linux-gnu",
+ ],
+ }))
+ .unwrap();
+ let metadata = test::metadata::multi_cfg_dep();
+ let lockfile = test::lockfile::multi_cfg_dep();
+
+ let annotations = Annotations::new(metadata, lockfile, config.clone()).unwrap();
+ let context = Context::new(annotations).unwrap();
+
+ let renderer = Renderer::new(config.rendering, config.supported_platform_triples, true);
+ let output = renderer.render(&context).unwrap();
+
+ let build_file_content = output
+ .get(&PathBuf::from("BUILD.cpufeatures-0.2.7.bazel"))
+ .unwrap();
+
+ // This is unfortunately somewhat brittle. Alas. Ultimately we wish to demonstrate that the
+ // original cfg(...) strings are preserved in the `deps` list for ease of debugging.
+ let expected = indoc! {r#"
+ deps = select({
+ "@rules_rust//rust/platform:aarch64-apple-darwin": [
+ "@multi_cfg_dep__libc-0.2.117//:libc", # cfg(all(target_arch = "aarch64", target_vendor = "apple"))
+ ],
+ "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [
+ "@multi_cfg_dep__libc-0.2.117//:libc", # cfg(all(target_arch = "aarch64", target_os = "linux"))
+ ],
+ "//conditions:default": [],
+ }),
+ "#};
+
+ assert!(
+ build_file_content.contains(&expected.replace('\n', "\n ")),
+ "{}",
+ build_file_content,
+ );
+ }
+
+ #[test]
+ fn legacy_crate_features() {
+ let mut context = Context::default();
+ let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+ context.crates.insert(
+ crate_id.clone(),
+ CrateContext {
+ name: crate_id.name,
+ version: crate_id.version,
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
+ common_attrs: CommonAttributes {
+ crate_features: CrateFeatures::LegacySet(BTreeSet::from([
+ "foo".to_owned(),
+ "bar".to_owned(),
+ ])),
+ ..CommonAttributes::default()
+ },
+ ..CrateContext::default()
+ },
+ );
+
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
+ let output = renderer.render(&context).unwrap();
+
+ let build_file_content = output
+ .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+ .unwrap();
+ assert!(build_file_content.replace(' ', "").contains(
+ &r#"crate_features = [
+ "bar",
+ "foo",
+],"#
+ .replace(' ', "")
+ ));
+ }
+ #[test]
+ fn crate_features_by_target() {
+ let mut context = Context::default();
+ let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+ let mut features = SelectList::default();
+ features.insert("foo".to_owned(), Some("aarch64-apple-darwin".to_owned()));
+ features.insert("bar".to_owned(), None);
+ context.crates.insert(
+ crate_id.clone(),
+ CrateContext {
+ name: crate_id.name,
+ version: crate_id.version,
+ targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
+ common_attrs: CommonAttributes {
+ crate_features: CrateFeatures::SelectList(features),
+ ..CommonAttributes::default()
+ },
+ ..CrateContext::default()
+ },
+ );
+
+ let renderer = Renderer::new(
+ mock_render_config(None),
+ mock_supported_platform_triples(),
+ true,
+ );
+ let output = renderer.render(&context).unwrap();
+
+ let build_file_content = output
+ .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+ .unwrap();
+ assert!(build_file_content.replace(' ', "").contains(
+ &r#"crate_features = [
+ "bar",
+ ] + select({
+ "@rules_rust//rust/platform:aarch64-apple-darwin": [
+ "foo",
+ ],
+ "//conditions:default": [],
+}),"#
+ .replace(' ', "")
+ ));
+ }
}
diff --git a/crate_universe/src/rendering/template_engine.rs b/crate_universe/src/rendering/template_engine.rs
index 3785b34..28fff72 100644
--- a/crate_universe/src/rendering/template_engine.rs
+++ b/crate_universe/src/rendering/template_engine.rs
@@ -6,15 +6,14 @@
use serde_json::{from_value, to_value, Value};
use tera::{self, Tera};
-use crate::config::{CrateId, RenderConfig};
+use crate::config::RenderConfig;
use crate::context::Context;
use crate::rendering::{
render_crate_bazel_label, render_crate_bazel_repository, render_crate_build_file,
- render_module_label, render_platform_constraint_label,
+ render_module_label, Platforms,
};
-use crate::utils::sanitize_module_name;
use crate::utils::sanitize_repository_name;
-use crate::utils::starlark::{SelectStringDict, SelectStringList};
+use crate::utils::starlark::SelectStringList;
pub struct TemplateEngine {
engine: Tera,
@@ -26,55 +25,6 @@
let mut tera = Tera::default();
tera.add_raw_templates(vec![
(
- "partials/crate/aliases.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/aliases.j2"
- )),
- ),
- (
- "partials/crate/binary.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/binary.j2"
- )),
- ),
- (
- "partials/crate/build_script.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/build_script.j2"
- )),
- ),
- (
- "partials/crate/common_attrs.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/common_attrs.j2"
- )),
- ),
- (
- "partials/crate/deps.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/deps.j2"
- )),
- ),
- (
- "partials/crate/library.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/library.j2"
- )),
- ),
- (
- "partials/crate/proc_macro.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/crate/proc_macro.j2"
- )),
- ),
- (
"partials/module/aliases_map.j2",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
@@ -103,27 +53,6 @@
)),
),
(
- "partials/starlark/glob.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/starlark/glob.j2"
- )),
- ),
- (
- "partials/starlark/selectable_dict.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/starlark/selectable_dict.j2"
- )),
- ),
- (
- "partials/starlark/selectable_list.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/partials/starlark/selectable_list.j2"
- )),
- ),
- (
"partials/header.j2",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
@@ -131,20 +60,6 @@
)),
),
(
- "crate_build_file.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/crate_build_file.j2"
- )),
- ),
- (
- "module_build_file.j2",
- include_str!(concat!(
- env!("CARGO_MANIFEST_DIR"),
- "/src/rendering/templates/module_build_file.j2"
- )),
- ),
- (
"module_bzl.j2",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
@@ -180,18 +95,12 @@
),
);
tera.register_function(
- "platform_label",
- platform_label_fn_generator(render_config.platforms_template.clone()),
- );
- tera.register_function("sanitize_module_name", sanitize_module_name_fn);
- tera.register_function(
"crates_module_label",
module_label_fn_generator(render_config.crates_module_template.clone()),
);
let mut context = tera::Context::new();
context.insert("default_select_list", &SelectStringList::default());
- context.insert("default_select_dict", &SelectStringDict::default());
context.insert("repository_name", &render_config.repository_name);
context.insert("vendor_mode", &render_config.vendor_mode);
context.insert("regen_command", &render_config.regen_command);
@@ -199,7 +108,7 @@
context.insert(
"default_package_name",
&match render_config.default_package_name.as_ref() {
- Some(pkg_name) => format!("\"{}\"", pkg_name),
+ Some(pkg_name) => format!("\"{pkg_name}\""),
None => "None".to_owned(),
},
);
@@ -214,53 +123,20 @@
self.context.clone()
}
- pub fn render_crate_build_files<'a>(
- &self,
- ctx: &'a Context,
- ) -> Result<HashMap<&'a CrateId, String>> {
- // Create the render context with the global planned context to be
- // reused when rendering crates.
- let mut context = self.new_tera_ctx();
- context.insert("context", ctx);
-
- ctx.crates
- .iter()
- .map(|(id, _)| {
- let aliases = ctx.crate_aliases(id, false, false);
- let build_aliases = ctx.crate_aliases(id, true, false);
-
- context.insert("crate_id", &id);
- context.insert("common_aliases", &aliases);
- context.insert("build_aliases", &build_aliases);
-
- let content = self
- .engine
- .render("crate_build_file.j2", &context)
- .context("Failed to render BUILD file")?;
-
- Ok((id, content))
- })
- .collect()
+ pub fn render_header(&self) -> Result<String> {
+ let context = self.new_tera_ctx();
+ let mut header = self
+ .engine
+ .render("partials/header.j2", &context)
+ .context("Failed to render header comment")?;
+ header.push('\n');
+ Ok(header)
}
- pub fn render_module_build_file(&self, data: &Context) -> Result<String> {
+ pub fn render_module_bzl(&self, data: &Context, platforms: &Platforms) -> Result<String> {
let mut context = self.new_tera_ctx();
context.insert("context", data);
-
- let workspace_member_deps = data.flat_workspace_member_deps();
- context.insert("workspace_member_dependencies", &workspace_member_deps);
-
- let binary_crates_map = data.flat_binary_deps();
- context.insert("binary_crates_map", &binary_crates_map);
-
- self.engine
- .render("module_build_file.j2", &context)
- .context("Failed to render crates module")
- }
-
- pub fn render_module_bzl(&self, data: &Context) -> Result<String> {
- let mut context = self.new_tera_ctx();
- context.insert("context", data);
+ context.insert("platforms", platforms);
self.engine
.render("module_bzl.j2", &context)
@@ -301,29 +177,6 @@
}
/// Convert a crate name into a module name by applying transforms to invalid characters.
-fn sanitize_module_name_fn(args: &HashMap<String, Value>) -> tera::Result<Value> {
- let crate_name = parse_tera_param!("crate_name", String, args);
-
- match to_value(sanitize_module_name(&crate_name)) {
- Ok(v) => Ok(v),
- Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
- }
-}
-
-/// Convert a crate name into a module name by applying transforms to invalid characters.
-fn platform_label_fn_generator(template: String) -> impl tera::Function {
- Box::new(
- move |args: &HashMap<String, Value>| -> tera::Result<Value> {
- let triple = parse_tera_param!("triple", String, args);
- match to_value(render_platform_constraint_label(&template, &triple)) {
- Ok(v) => Ok(v),
- Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
- }
- },
- )
-}
-
-/// Convert a crate name into a module name by applying transforms to invalid characters.
fn crate_build_file_fn_generator(template: String) -> impl tera::Function {
Box::new(
move |args: &HashMap<String, Value>| -> tera::Result<Value> {
diff --git a/crate_universe/src/rendering/templates/crate_build_file.j2 b/crate_universe/src/rendering/templates/crate_build_file.j2
deleted file mode 100644
index ff9d4ad..0000000
--- a/crate_universe/src/rendering/templates/crate_build_file.j2
+++ /dev/null
@@ -1,44 +0,0 @@
-{%- set crate = context.crates | get(key=crate_id) %}
-{%- include "partials/header.j2" %}
-
-load(
- "@bazel_skylib//lib:selects.bzl",
- "selects",
-)
-load(
- "@rules_rust//cargo:defs.bzl",
- "cargo_build_script",
-)
-load(
- "@rules_rust//rust:defs.bzl",
- "rust_binary",
- "rust_library",
- "rust_proc_macro",
-)
-
-# buildifier: disable=bzl-visibility
-load("@rules_rust//crate_universe/private:selects.bzl", "select_with_or")
-
-package(default_visibility = ["//visibility:public"])
-
-# licenses([
-# "TODO", # {{ crate.license }}
-# ])
-
-{% for rule in crate.targets -%}
-{%- for rule_type, target in rule %}
-{%- if rule_type in ["BuildScript"] %}
-{% include "partials/crate/build_script.j2" %}
-{%- elif rule_type in ["ProcMacro"] %}
-{% include "partials/crate/proc_macro.j2" %}
-{%- elif rule_type in ["Library"] %}
-{% include "partials/crate/library.j2" %}
-{%- elif rule_type in ["Binary"] %}
-{% include "partials/crate/binary.j2" %}
-{%- endif %}
-{%- endfor %}
-{%- endfor %}
-{%- if crate.additive_build_file_content %}
-# Additive BUILD file content
-{{ crate.additive_build_file_content }}
-{%- endif %}
diff --git a/crate_universe/src/rendering/templates/module_build_file.j2 b/crate_universe/src/rendering/templates/module_build_file.j2
deleted file mode 100644
index 63124dc..0000000
--- a/crate_universe/src/rendering/templates/module_build_file.j2
+++ /dev/null
@@ -1,49 +0,0 @@
-{%- include "partials/header.j2" %}
-
-package(default_visibility = ["//visibility:public"])
-
-exports_files(
- [
- "cargo-bazel.json",
- "defs.bzl",
- {%- set current_vendor_mode = vendor_mode | default(value="") %}{%- if current_vendor_mode == "remote" %}"crates.bzl",{%- endif %}
- ] + glob([
- "*.bazel",
- ]),
-)
-
-filegroup(
- name = "srcs",
- srcs = glob([
- "*.bazel",
- "*.bzl",
- ]),
-)
-
-# Workspace Member Dependencies
-{%- for dep, rename in workspace_member_dependencies %}
-{%- set crate = context.crates | get(key=dep) %}
-{%- if crate | get(key="library_target_name", default=Null) %}
-alias(
- name = "{{ rename | default(value=crate.name) }}",
- actual = "{{ crate_label(name = crate.name, version = crate.version, target = crate.library_target_name) }}",
- tags = ["manual"],
-)
-{%- endif %}
-{%- endfor %}
-
-# Binaries
-{%- for id, rename in binary_crates_map %}
-{%- set crate = context.crates | get(key=id) %}
-{%- for rule in crate.targets %}
-{%- for rule_type, target in rule %}
-{%- if rule_type in ["Binary"] %}
-alias(
- name = "{{ rename | default(value=crate.name) }}__{{ target.crate_name }}",
- actual = "{{ crate_label(name = crate.name, version = crate.version, target = target.crate_name ~ '__bin') }}",
- tags = ["manual"],
-)
-{%- endif %}
-{%- endfor %}
-{%- endfor %}
-{%- endfor %}
diff --git a/crate_universe/src/rendering/templates/module_bzl.j2 b/crate_universe/src/rendering/templates/module_bzl.j2
index c3a1e6d..63d027c 100644
--- a/crate_universe/src/rendering/templates/module_bzl.j2
+++ b/crate_universe/src/rendering/templates/module_bzl.j2
@@ -1,6 +1,6 @@
{#
To keep line numbers consistent with the rendered version, empty space is
-intentionally plced here which should match the line length of `partials/header.j2`.
+intentionally placed here which should match the line length of `partials/header.j2`.
Expected length = 6 lines
#}{%- include "partials/header.j2" %}
@@ -37,7 +37,7 @@
# name of the workspace this file is defined in.
"workspace_member_package": {
- # Not all dependnecies are supported for all platforms.
+ # Not all dependencies are supported for all platforms.
# the condition key is the condition required to be true
# on the host platform.
"condition": {
@@ -153,7 +153,7 @@
Args:
normal (bool, optional): If True, normal dependencies are included in the
output list.
- normal_dev (bool, optional): If True, normla dev dependencies will be
+ normal_dev (bool, optional): If True, normal dev dependencies will be
included in the output list..
proc_macro (bool, optional): If True, proc_macro dependencies are included
in the output list.
@@ -202,7 +202,10 @@
crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())
for condition, deps in dependencies.items():
- crate_deps += selects.with_or({_CONDITIONS[condition]: deps.values()})
+ crate_deps += selects.with_or({
+ tuple(_CONDITIONS[condition]): deps.values(),
+ "//conditions:default": [],
+ })
return crate_deps
@@ -222,7 +225,7 @@
Args:
normal (bool, optional): If True, normal dependencies are included in the
output list.
- normal_dev (bool, optional): If True, normla dev dependencies will be
+ normal_dev (bool, optional): If True, normal dev dependencies will be
included in the output list..
proc_macro (bool, optional): If True, proc_macro dependencies are included
in the output list.
@@ -274,15 +277,16 @@
# Build a single select statement where each conditional has accounted for the
# common set of aliases.
- crate_aliases = {"//conditions:default": common_items}
+ crate_aliases = {"//conditions:default": dict(common_items)}
for condition, deps in aliases.items():
condition_triples = _CONDITIONS[condition]
- if condition_triples in crate_aliases:
- crate_aliases[condition_triples].update(deps)
- else:
- crate_aliases.update({_CONDITIONS[condition]: dict(deps.items() + common_items)})
+ for triple in condition_triples:
+ if triple in crate_aliases:
+ crate_aliases[triple].update(deps)
+ else:
+ crate_aliases.update({triple: dict(deps.items() + common_items)})
- return selects.with_or(crate_aliases)
+ return select(crate_aliases)
###############################################################################
# WORKSPACE MEMBER DEPS AND ALIASES
@@ -313,7 +317,7 @@
_BUILD_PROC_MACRO_ALIASES = {% set deps_type = "build-proc-macro" %}{% include "partials/module/aliases_map.j2" %}
_CONDITIONS = {
-{%- for condition, triples in context.conditions %}
+{%- for condition, triples in platforms %}
"{{ condition | addslashes }}": {{ triples | sort | json_encode | safe }},
{%- endfor %}
}
diff --git a/crate_universe/src/rendering/templates/partials/crate/aliases.j2 b/crate_universe/src/rendering/templates/partials/crate/aliases.j2
deleted file mode 100644
index 5c4b67e..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/aliases.j2
+++ /dev/null
@@ -1,32 +0,0 @@
-selects.with_or({
- {%- for cfg, values in selectable.selects %}
- {%- if cfg in context.conditions and context.conditions[cfg] | length %}
- # {{ cfg }}
- (
- {%- for triple in context.conditions[cfg] %}
- "{{ platform_label(triple = triple) }}",
- {%- endfor %}
- ): {
- {%- for dep in values %}
- {%- set dep_crate = context.crates | get(key=dep.id) %}
- "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.target) }}": "{{ dep.alias }}",
- {%- endfor %}
- {%- for dep in selectable.common %}
- {%- set dep_crate = context.crates | get(key=dep.id) %}
- "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.target) }}": "{{ dep.alias }}",
- {%- endfor %}
- },
- {%- else %}
- # {
- # No supported platform triples for cfg: '{{ cfg }}'
- # Skipped dependencies: {{ values | json_encode | safe }}
- # }
- {%- endif %}
- {%- endfor %}
- "//conditions:default": {
- {%- for dep in selectable.common %}
- {%- set dep_crate = context.crates | get(key=dep.id) %}
- "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.target) }}": "{{ dep.alias }}",
- {%- endfor %}
- },
- })
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/crate/binary.j2 b/crate_universe/src/rendering/templates/partials/crate/binary.j2
deleted file mode 100644
index 2cdc5d9..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/binary.j2
+++ /dev/null
@@ -1,18 +0,0 @@
-rust_binary(
- name = "{{ target.crate_name }}__bin",
- deps = [
- {%- if crate.library_target_name %}
- ":{{ crate.library_target_name }}",
- {%- endif %}
- {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- proc_macro_deps = [
- {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/build_script.j2 b/crate_universe/src/rendering/templates/partials/crate/build_script.j2
deleted file mode 100644
index 9b1ff45..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/build_script.j2
+++ /dev/null
@@ -1,84 +0,0 @@
-cargo_build_script(
- # See comment associated with alias. Do not change this name
- name = "{{ crate.name }}_build_script",
- aliases = {% set selectable = build_aliases %}{% include "partials/crate/aliases.j2" -%},
- build_script_env = {% set selectable = crate.build_script_attrs | get(key="build_script_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
- compile_data = {% if crate.build_script_attrs | get(key="compile_data_glob") %}glob({{ crate.build_script_attrs.compile_data_glob | json_encode | safe }}) + {% endif %}{% set selectable = crate.build_script_attrs | get(key="compile_data", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
- crate_name = "{{ sanitize_module_name(crate_name=target.crate_name) }}",
- crate_root = "{{ target.crate_root }}",
- crate_features = [
- {%- if crate.common_attrs | get(key="crate_features", default=Null) %}
- {%- for feature in crate.common_attrs.crate_features %}
- "{{ feature }}",
- {%- endfor %}
- {%- endif %}
- ],
- data = {% if crate.build_script_attrs | get(key="data_glob") %}glob({{ crate.build_script_attrs.data_glob | json_encode | safe }}) + {% endif %}{% set selectable = crate.build_script_attrs | get(key="data", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
- deps = [
- {%- for dep in crate.build_script_attrs | get(key="extra_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.build_script_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- edition = "{{ crate.common_attrs.edition }}",
- {%- if crate.common_attrs.linker_script %}
- linker_script = "{{ crate.common_attrs.linker_script }}",
- {%- endif %}
- {%- if crate.build_script_attrs | get(key="links", default=Null) %}
- links = "{{ crate.build_script_attrs.links }}",
- {%- endif %}
- proc_macro_deps = [
- {%- for dep in crate.build_script_attrs | get(key="extra_proc_macro_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.build_script_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- rustc_env = {% set selectable = crate.build_script_attrs | get(key="rustc_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
- rustc_env_files = {% set selectable = crate.build_script_attrs | get(key="rustc_env_files", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
- rustc_flags = [
- # In most cases, warnings in 3rd party crates are not interesting as
- # they're out of the control of consumers. The flag here silences
- # warnings. For more details see:
- # https://doc.rust-lang.org/rustc/lints/levels.html
- "--cap-lints=allow",
- {%- if crate.common_attrs | get(key="rustc_flags", default=Null) %}
-
- # User provided rustc_flags
- {%- for rustc_flag in crate.common_attrs.rustc_flags %}
- "{{ rustc_flag }}",
- {%- endfor %}
- {%- endif %}
- ],
- srcs = {% set glob = target.srcs %}{% include "partials/starlark/glob.j2" -%},
- tools = {% set selectable = crate.build_script_attrs | get(key="tools", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
- version = "{{ crate.common_attrs.version }}",
- tags = [
- {%- if crate.common_attrs | get(key="tags", default=Null) %}
- {%- for tag in crate.common_attrs.tags %}
- "{{ tag }}",
- {%- endfor %}
- {%- endif %}
- "cargo-bazel",
- "manual",
- "noclippy",
- "norustfmt",
- ],
- {%- if crate.build_script_attrs | get(key="toolchains", default=Null) %}
- toolchains = [
- {%- for toolchain in crate.build_script_attrs.toolchains %}
- "{{ toolchain }}",
- {%- endfor %}
- ],
- {%- endif %}
- visibility = ["//visibility:private"],
-)
-alias(
- # Because `cargo_build_script` does some invisible target name mutating to
- # determine the package and crate name for a build script, the Bazel
- # target namename of any build script cannot be the Cargo canonical name
- # of `build_script_build` without losing out on having certain Cargo
- # environment variables set.
- name = "{{ target.crate_name }}",
- actual = "{{ crate.name }}_build_script",
- tags = [
- "manual",
- ],
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2 b/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2
deleted file mode 100644
index c1cccbb..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2
+++ /dev/null
@@ -1,41 +0,0 @@
- compile_data = {% if crate.common_attrs | get(key="compile_data_glob") %}glob(include = {{ crate.common_attrs.compile_data_glob | json_encode | safe }}, exclude = ["BUILD", "BUILD.bazel", "WORKSPACE", "WORKSPACE.bazel"]) + {% endif %}{% set selectable = crate.common_attrs | get(key="compile_data", default=default_select_list) %}{% include "partials/starlark/selectable_list.j2" -%},
- crate_root = "{{ target.crate_root }}",
- crate_features = [
- {%- for feature in crate.common_attrs | get(key="crate_features", default=[]) %}
- "{{ feature }}",
- {%- endfor %}
- ],
- data = {% if crate.common_attrs | get(key="data_glob") %}glob(include = {{ crate.common_attrs.data_glob | json_encode | safe }}, exclude = ["BUILD", "BUILD.bazel", "WORKSPACE", "WORKSPACE.bazel"]) + {% endif %}{% set selectable = crate.common_attrs | get(key="data", default=default_select_list) %}{% include "partials/starlark/selectable_list.j2" -%},
- edition = "{{ crate.common_attrs.edition }}",
- {%- if crate.common_attrs | get(key="linker_script", default=Null) %}
- linker_script = "{{ crate.common_attrs.linker_script }}",
- {%- endif %}
- rustc_env = {% set selectable = crate.common_attrs | get(key="rustc_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
- rustc_env_files = {% set selectable = crate.common_attrs | get(key="rustc_env_files", default=Null) %}{% include "partials/starlark/selectable_list.j2" -%},
- rustc_flags = [
- # In most cases, warnings in 3rd party crates are not interesting as
- # they're out of the control of consumers. The flag here silences
- # warnings. For more details see:
- # https://doc.rust-lang.org/rustc/lints/levels.html
- "--cap-lints=allow",
- {%- if crate.common_attrs | get(key="rustc_flags", default=Null) %}
-
- # User provided rustc_flags
- {%- for rustc_flag in crate.common_attrs.rustc_flags %}
- "{{ rustc_flag }}",
- {%- endfor %}
- {%- endif %}
- ],
- srcs = {% set glob = target.srcs %}{% include "partials/starlark/glob.j2" -%},
- version = "{{ crate.common_attrs.version }}",
- tags = [
- {%- if crate.common_attrs | get(key="tags", default=Null) %}
- {%- for tag in crate.common_attrs.tags %}
- "{{ tag }}",
- {%- endfor %}
- {%- endif %}
- "cargo-bazel",
- "manual",
- "noclippy",
- "norustfmt",
- ],
diff --git a/crate_universe/src/rendering/templates/partials/crate/deps.j2 b/crate_universe/src/rendering/templates/partials/crate/deps.j2
deleted file mode 100644
index 0e0bf71..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/deps.j2
+++ /dev/null
@@ -1,36 +0,0 @@
-select_with_or({
- {%- set selectable = deps | default(value=default_select_list) %}
- {%- for cfg, values in selectable.selects %}
- # {{ cfg }}
- {%- if cfg in context.conditions and context.conditions[cfg] | length %}
- (
- {%- for triple in context.conditions[cfg] %}
- "{{ platform_label(triple = triple) }}",
- {%- endfor %}
- ): [
- # Target Deps
- {%- for dep in values %}
- {%- set dep_crate = context.crates | get(key=dep.id) %}
- "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.target) }}",
- {%- endfor %}
-
- # Common Deps
- {%- for common_dep in selectable.common %}
- {%- set common_dep_crate = context.crates | get(key=common_dep.id) %}
- "{{ crate_label(name = common_dep_crate.name, version = common_dep_crate.version, target = common_dep.target) }}",
- {%- endfor %}
- ],
- {%- else %}
- #
- # No supported platform triples for cfg: '{{ cfg }}'
- # Skipped dependencies: {{ values | json_encode | safe }}
- #
- {%- endif %}
- {%- endfor %}
- "//conditions:default": [
- {%- for common_dep in selectable.common %}
- {%- set common_dep_crate = context.crates | get(key=common_dep.id) %}
- "{{ crate_label(name = common_dep_crate.name, version = common_dep_crate.version, target = common_dep.target) }}",
- {%- endfor %}
- ],
- })
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/crate/library.j2 b/crate_universe/src/rendering/templates/partials/crate/library.j2
deleted file mode 100644
index f678bd9..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/library.j2
+++ /dev/null
@@ -1,15 +0,0 @@
-rust_library(
- name = "{{ target.crate_name }}",
- deps = [
- {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- proc_macro_deps = [
- {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2 b/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2
deleted file mode 100644
index c0b9d1d..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2
+++ /dev/null
@@ -1,15 +0,0 @@
-rust_proc_macro(
- name = "{{ target.crate_name }}",
- deps = [
- {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- proc_macro_deps = [
- {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
- "{{ dep }}",
- {%- endfor %}
- ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
- aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/starlark/glob.j2 b/crate_universe/src/rendering/templates/partials/starlark/glob.j2
deleted file mode 100644
index 67f70af..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/glob.j2
+++ /dev/null
@@ -1,12 +0,0 @@
-glob(
- include = [
- {%- for pattern in glob.include %}
- "{{ pattern }}",
- {%- endfor %}
- ],
- exclude = [
- {%- for pattern in glob.exclude %}
- "{{ pattern }}",
- {%- endfor %}
- ],
- )
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2 b/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2
deleted file mode 100644
index 8012cc7..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2
+++ /dev/null
@@ -1,36 +0,0 @@
-{%- set selectable = selectable | default(value=default_select_dict) %}
-{%- if selectable.selects | length -%}
- selects.with_or({
- {%- for cfg, map in selectable.selects %}
- {%- if cfg in context.conditions and context.conditions[cfg] | length %}
- # {{ cfg }}
- (
- {%- for triple in context.conditions[cfg] %}
- "{{ platform_label(triple = triple) }}",
- {%- endfor %}
- ): {
- {%- if selectable.common | length %}
- {%- for key, val in selectable.common %}
- "{{ key }}": "{{ val }}",
- {%- endfor %}
- {%- endif %}
- {%- for key, val in map %}
- "{{ key }}": "{{ val }}",
- {%- endfor %}
- },
- {%- else %}
- # No supported platform triples for cfg: '{{ cfg }}'
- # Skipped dependencies: {{ map | json_encode| safe }}
- {%- endif %}
- {%- endfor %}
- "//conditions:default": {},
- })
-{%- else -%}
- {
- {%- if selectable.common | length %}
- {%- for key, val in selectable.common %}
- "{{ key }}": "{{ val }}",
- {%- endfor %}
- {%- endif %}
- }
-{%- endif %}
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2 b/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2
deleted file mode 100644
index 2641713..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2
+++ /dev/null
@@ -1,31 +0,0 @@
-select_with_or({
- {%- set selectable = selectable | default(value=default_select_list) %}
- {%- for cfg, values in selectable.selects %}
- # {{ cfg }}
- {%- if cfg in context.conditions and context.conditions[cfg] | length %}
- (
- {%- for triple in context.conditions[cfg] %}
- "{{ platform_label(triple = triple) }}",
- {%- endfor %}
- ): [
- # Target Deps
- {%- for val in values %}
- "{{ val }}",
- {%- endfor %}
-
- # Common Deps
- {%- for val in selectable.common %}
- "{{ val }}",
- {%- endfor %}
- ],
- {%- else %}
- # No supported platform triples for cfg: '{{ cfg }}'
- # Skipped dependencies: {{ values | json_encode | safe }}
- {%- endif %}
- {%- endfor %}
- "//conditions:default": [
- {%- for val in selectable.common %}
- "{{ val }}",
- {%- endfor %}
- ],
- })
\ No newline at end of file
diff --git a/crate_universe/src/splicing.rs b/crate_universe/src/splicing.rs
index 8d42e1e..fe525b9 100644
--- a/crate_universe/src/splicing.rs
+++ b/crate_universe/src/splicing.rs
@@ -1,24 +1,26 @@
//! This module is responsible for finding a Cargo workspace
pub(crate) mod cargo_config;
+mod crate_index_lookup;
mod splicer;
-use std::collections::{BTreeMap, BTreeSet, HashMap};
+use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryFrom;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
use cargo_toml::Manifest;
-use hex::ToHex;
use serde::{Deserialize, Serialize};
use crate::config::CrateId;
-use crate::metadata::{CargoUpdateRequest, LockGenerator};
-use crate::utils::starlark::Label;
+use crate::metadata::{Cargo, CargoUpdateRequest, LockGenerator};
+use crate::utils;
+use crate::utils::starlark::{Label, SelectList};
use self::cargo_config::CargoConfig;
+use self::crate_index_lookup::CrateIndexLookup;
pub use self::splicer::*;
type DirectPackageManifest = BTreeMap<String, cargo_toml::DependencyDetail>;
@@ -116,9 +118,16 @@
.manifests
.into_iter()
.map(|(path, label)| {
- let manifest = cargo_toml::Manifest::from_path(&path)
+ // We read the content of a manifest file to buffer and use `from_slice` to
+ // parse it. The reason is that the `from_path` version will resolve indirect
+ // path dependencies in the workspace to absolute path, which causes the hash
+ // to be unstable. Not resolving implicit data is okay here because the
+ // workspace manifest is also included in the hash.
+ // See https://github.com/bazelbuild/rules_rust/issues/2016
+ let manifest_content = fs::read(&path)
.with_context(|| format!("Failed to load manifest '{}'", path.display()))?;
-
+ let manifest = cargo_toml::Manifest::from_slice(&manifest_content)
+ .with_context(|| format!("Failed to parse manifest '{}'", path.display()))?;
Ok((label, manifest))
})
.collect::<Result<BTreeMap<Label, Manifest>>>()?;
@@ -152,15 +161,19 @@
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct WorkspaceMetadata {
/// A mapping of crates to information about where their source can be downloaded
- #[serde(serialize_with = "toml::ser::tables_last")]
pub sources: BTreeMap<CrateId, SourceInfo>,
/// The path from the root of a Bazel workspace to the root of the Cargo workspace
pub workspace_prefix: Option<String>,
/// Paths from the root of a Bazel workspace to a Cargo package
- #[serde(serialize_with = "toml::ser::tables_last")]
pub package_prefixes: BTreeMap<String, String>,
+
+ /// Feature set for each target triplet and crate.
+ ///
+ /// We store this here because it's computed during the splicing phase via
+ /// calls to "cargo tree" which need the full spliced workspace.
+ pub features: BTreeMap<CrateId, SelectList<String>>,
}
impl TryFrom<toml::Value> for WorkspaceMetadata {
@@ -193,7 +206,7 @@
impl WorkspaceMetadata {
fn new(
splicing_manifest: &SplicingManifest,
- member_manifests: HashMap<&PathBuf, String>,
+ member_manifests: BTreeMap<&PathBuf, String>,
) -> Result<Self> {
let mut package_prefixes: BTreeMap<String, String> = member_manifests
.iter()
@@ -233,14 +246,18 @@
sources: BTreeMap::new(),
workspace_prefix,
package_prefixes,
+ features: BTreeMap::new(),
})
}
- pub fn write_registry_urls(
+ pub fn write_registry_urls_and_feature_map(
+ cargo: &Cargo,
lockfile: &cargo_lock::Lockfile,
- manifest_path: &SplicedManifest,
+ features: BTreeMap<CrateId, SelectList<String>>,
+ input_manifest_path: &Path,
+ output_manifest_path: &Path,
) -> Result<()> {
- let mut manifest = read_manifest(manifest_path.as_path_buf())?;
+ let mut manifest = read_manifest(input_manifest_path)?;
let mut workspace_metaata = WorkspaceMetadata::try_from(
manifest
@@ -253,7 +270,7 @@
.clone(),
)?;
- // Locate all packages soruced from a registry
+ // Locate all packages sourced from a registry
let pkg_sources: Vec<&cargo_lock::Package> = lockfile
.packages
.iter()
@@ -270,8 +287,7 @@
// Load the cargo config
let cargo_config = {
// Note that this path must match the one defined in `splicing::setup_cargo_config`
- let config_path = manifest_path
- .as_path_buf()
+ let config_path = input_manifest_path
.parent()
.unwrap()
.join(".cargo")
@@ -288,86 +304,84 @@
let crate_indexes = index_urls
.into_iter()
.map(|url| {
- let index = {
- // Ensure the correct registry is mapped based on the give Cargo config.
- let index_url = if let Some(config) = &cargo_config {
- if let Some(source) = config.get_source_from_url(&url) {
- if let Some(replace_with) = &source.replace_with {
- if let Some(replacement) = config.get_registry_index_url_by_name(replace_with) {
- replacement
- } else {
- bail!("Tried to replace registry {} with registry named {} but didn't have metadata about the replacement", url, replace_with);
- }
- } else {
- &url
- }
- } else {
- &url
- }
- } else {
- &url
+ // Ensure the correct registry is mapped based on the give Cargo config.
+ let index_url = if let Some(config) = &cargo_config {
+ config.resolve_replacement_url(&url)?
+ } else {
+ &url
+ };
+ let index = if cargo.use_sparse_registries_for_crates_io()?
+ && index_url == utils::CRATES_IO_INDEX_URL
+ {
+ CrateIndexLookup::Http(crates_index::SparseIndex::from_url(
+ "sparse+https://index.crates.io/",
+ )?)
+ } else if index_url.starts_with("sparse+https://") {
+ CrateIndexLookup::Http(crates_index::SparseIndex::from_url(index_url)?)
+ } else {
+ let index = {
+ // Load the index for the current url
+ let index =
+ crates_index::Index::from_url(index_url).with_context(|| {
+ format!("Failed to load index for url: {index_url}")
+ })?;
+
+ // Ensure each index has a valid index config
+ index.index_config().with_context(|| {
+ format!("`config.json` not found in index: {index_url}")
+ })?;
+
+ index
};
- // Load the index for the current url
- let index = crates_index::Index::from_url(index_url)
- .with_context(|| format!("Failed to load index for url: {}", index_url))?;
-
- // Ensure each index has a valid index config
- index.index_config().with_context(|| {
- format!("`config.json` not found in index: {}", index_url)
- })?;
-
- index
+ CrateIndexLookup::Git(index)
};
-
Ok((url, index))
})
- .collect::<Result<BTreeMap<String, crates_index::Index>>>()
+ .collect::<Result<BTreeMap<String, _>>>()
.context("Failed to locate crate indexes")?;
// Get the download URL of each package based on it's registry url.
let additional_sources = pkg_sources
.iter()
- .filter_map(|pkg| {
+ .map(|pkg| {
let source_id = pkg.source.as_ref().unwrap();
- let index = &crate_indexes[&source_id.url().to_string()];
- let index_config = index.index_config().unwrap();
-
- index.crate_(pkg.name.as_str()).map(|crate_idx| {
- crate_idx
- .versions()
- .iter()
- .find(|v| v.version() == pkg.version.to_string())
- .and_then(|v| {
- v.download_url(&index_config).map(|url| {
- let crate_id =
- CrateId::new(v.name().to_owned(), v.version().to_owned());
- let sha256 = pkg
- .checksum
- .as_ref()
- .and_then(|sum| {
- sum.as_sha256().map(|sum| sum.encode_hex::<String>())
- })
- .unwrap_or_else(|| v.checksum().encode_hex::<String>());
- let source_info = SourceInfo { url, sha256 };
- (crate_id, source_info)
- })
- })
+ let source_url = source_id.url().to_string();
+ let lookup = crate_indexes.get(&source_url).ok_or_else(|| {
+ anyhow!(
+ "Couldn't find crate_index data for SourceID {:?}",
+ source_id
+ )
+ })?;
+ lookup.get_source_info(pkg).map(|source_info| {
+ (
+ CrateId::new(pkg.name.as_str().to_owned(), pkg.version.to_string()),
+ source_info,
+ )
})
})
- .flatten();
+ .collect::<Result<Vec<_>>>()?;
- workspace_metaata.sources.extend(additional_sources);
+ workspace_metaata
+ .sources
+ .extend(
+ additional_sources
+ .into_iter()
+ .filter_map(|(crate_id, source_info)| {
+ source_info.map(|source_info| (crate_id, source_info))
+ }),
+ );
+ workspace_metaata.features = features;
workspace_metaata.inject_into(&mut manifest)?;
- write_root_manifest(manifest_path.as_path_buf(), manifest)?;
+ write_root_manifest(output_manifest_path, manifest)?;
Ok(())
}
fn inject_into(&self, manifest: &mut Manifest) -> Result<()> {
let metadata_value = toml::Value::try_from(self)?;
- let mut workspace = manifest.workspace.as_mut().unwrap();
+ let workspace = manifest.workspace.as_mut().unwrap();
match &mut workspace.metadata {
Some(data) => match data.as_table_mut() {
@@ -412,7 +426,7 @@
pub fn generate_lockfile(
manifest_path: &SplicedManifest,
existing_lock: &Option<PathBuf>,
- cargo_bin: &Path,
+ cargo_bin: Cargo,
rustc_bin: &Path,
update_request: &Option<CargoUpdateRequest>,
) -> Result<cargo_lock::Lockfile> {
@@ -429,8 +443,11 @@
}
// Generate the new lockfile
- let lockfile = LockGenerator::new(PathBuf::from(cargo_bin), PathBuf::from(rustc_bin))
- .generate(manifest_path.as_path_buf(), existing_lock, update_request)?;
+ let lockfile = LockGenerator::new(cargo_bin, PathBuf::from(rustc_bin)).generate(
+ manifest_path.as_path_buf(),
+ existing_lock,
+ update_request,
+ )?;
// Write the lockfile to disk
if !root_lockfile_path.exists() {
@@ -480,17 +497,47 @@
assert_eq!(manifest.resolver_version, cargo_toml::Resolver::V2);
// Check packages
- assert_eq!(manifest.direct_packages.len(), 1);
+ assert_eq!(manifest.direct_packages.len(), 4);
let package = manifest.direct_packages.get("rand").unwrap();
assert_eq!(
package,
&cargo_toml::DependencyDetail {
- default_features: Some(false),
+ default_features: false,
features: vec!["small_rng".to_owned()],
version: Some("0.8.5".to_owned()),
..Default::default()
}
);
+ let package = manifest.direct_packages.get("cfg-if").unwrap();
+ assert_eq!(
+ package,
+ &cargo_toml::DependencyDetail {
+ git: Some("https://github.com/rust-lang/cfg-if.git".to_owned()),
+ rev: Some("b9c2246a".to_owned()),
+ default_features: true,
+ ..Default::default()
+ }
+ );
+ let package = manifest.direct_packages.get("log").unwrap();
+ assert_eq!(
+ package,
+ &cargo_toml::DependencyDetail {
+ git: Some("https://github.com/rust-lang/log.git".to_owned()),
+ branch: Some("master".to_owned()),
+ default_features: true,
+ ..Default::default()
+ }
+ );
+ let package = manifest.direct_packages.get("cargo_toml").unwrap();
+ assert_eq!(
+ package,
+ &cargo_toml::DependencyDetail {
+ git: Some("https://gitlab.com/crates.rs/cargo_toml.git".to_owned()),
+ tag: Some("v0.15.2".to_owned()),
+ default_features: true,
+ ..Default::default()
+ }
+ );
// Check cargo config
assert_eq!(
@@ -542,4 +589,43 @@
PathBuf::from("/tmp/abs/path/workspace/.cargo/config.toml"),
)
}
+
+ #[test]
+ fn splicing_metadata_workspace_path() {
+ let runfiles = runfiles::Runfiles::create().unwrap();
+ let workspace_manifest_path = runfiles
+ .rlocation("rules_rust/crate_universe/test_data/metadata/workspace_path/Cargo.toml");
+ let workspace_path = workspace_manifest_path.parent().unwrap().to_path_buf();
+ let child_a_manifest_path = runfiles.rlocation(
+ "rules_rust/crate_universe/test_data/metadata/workspace_path/child_a/Cargo.toml",
+ );
+ let child_b_manifest_path = runfiles.rlocation(
+ "rules_rust/crate_universe/test_data/metadata/workspace_path/child_b/Cargo.toml",
+ );
+ let manifest = SplicingManifest {
+ direct_packages: BTreeMap::new(),
+ manifests: BTreeMap::from([
+ (
+ workspace_manifest_path,
+ Label::from_str("//:Cargo.toml").unwrap(),
+ ),
+ (
+ child_a_manifest_path,
+ Label::from_str("//child_a:Cargo.toml").unwrap(),
+ ),
+ (
+ child_b_manifest_path,
+ Label::from_str("//child_b:Cargo.toml").unwrap(),
+ ),
+ ]),
+ cargo_config: None,
+ resolver_version: cargo_toml::Resolver::V2,
+ };
+ let metadata = SplicingMetadata::try_from(manifest).unwrap();
+ let metadata = serde_json::to_string(&metadata).unwrap();
+ assert!(
+ !metadata.contains(workspace_path.to_str().unwrap()),
+ "serialized metadata should not contain absolute path"
+ );
+ }
}
diff --git a/crate_universe/src/splicing/cargo_config.rs b/crate_universe/src/splicing/cargo_config.rs
index ffa42b7..09f715b 100644
--- a/crate_universe/src/splicing/cargo_config.rs
+++ b/crate_universe/src/splicing/cargo_config.rs
@@ -5,7 +5,8 @@
use std::path::Path;
use std::str::FromStr;
-use anyhow::Result;
+use crate::utils;
+use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
/// The [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry)
@@ -34,7 +35,7 @@
/// This is the default registry url per what's defined by Cargo.
fn default_registry_url() -> String {
- "https://github.com/rust-lang/crates.io-index".to_owned()
+ utils::CRATES_IO_INDEX_URL.to_owned()
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
@@ -114,15 +115,21 @@
}
impl CargoConfig {
- /// Load a Cargo conig from a path to a file on disk.
+ /// Load a Cargo config from a path to a file on disk.
pub fn try_from_path(path: &Path) -> Result<Self> {
let content = fs::read_to_string(path)?;
Self::from_str(&content)
}
- /// Look up a reigstry [Source] by it's url.
+ /// Look up a registry [Source] by its url.
pub fn get_source_from_url(&self, url: &str) -> Option<&Source> {
- self.source.values().find(|v| v.registry == url)
+ if let Some(found) = self.source.values().find(|v| v.registry == url) {
+ Some(found)
+ } else if url == utils::CRATES_IO_INDEX_URL {
+ self.source.get("crates-io")
+ } else {
+ None
+ }
}
pub fn get_registry_index_url_by_name(&self, name: &str) -> Option<&str> {
@@ -134,6 +141,22 @@
None
}
}
+
+ pub fn resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str> {
+ if let Some(source) = self.get_source_from_url(url) {
+ if let Some(replace_with) = &source.replace_with {
+ if let Some(replacement) = self.get_registry_index_url_by_name(replace_with) {
+ Ok(replacement)
+ } else {
+ bail!("Tried to replace registry {} with registry named {} but didn't have metadata about the replacement", url, replace_with);
+ }
+ } else {
+ Ok(url)
+ }
+ } else {
+ Ok(url)
+ }
+ }
}
#[cfg(test)]
@@ -241,4 +264,134 @@
Some("https://artprod.mycompany/artifactory/git/cargo-remote.git"),
);
}
+
+ #[test]
+ fn registry_settings_get_source_from_url() {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let config = temp_dir.as_ref().join("config.toml");
+
+ fs::write(
+ &config,
+ textwrap::dedent(
+ r##"
+ [source.some-mirror]
+ registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ "##,
+ ),
+ )
+ .unwrap();
+
+ let config = CargoConfig::try_from_path(&config).unwrap();
+ assert_eq!(
+ config
+ .get_source_from_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
+ .map(|s| s.registry.as_str()),
+ Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"),
+ );
+ }
+
+ #[test]
+ fn resolve_replacement_url_no_replacement() {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let config = temp_dir.as_ref().join("config.toml");
+
+ fs::write(&config, "").unwrap();
+
+ let config = CargoConfig::try_from_path(&config).unwrap();
+
+ assert_eq!(
+ config
+ .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
+ .unwrap(),
+ utils::CRATES_IO_INDEX_URL
+ );
+ assert_eq!(
+ config
+ .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
+ .unwrap(),
+ "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ );
+ }
+
+ #[test]
+ fn resolve_replacement_url_registry() {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let config = temp_dir.as_ref().join("config.toml");
+
+ fs::write(&config, textwrap::dedent(
+ r##"
+ [registries]
+ art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
+
+ [source.crates-io]
+ replace-with = "some-mirror"
+
+ [source.some-mirror]
+ registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ "##,
+ )).unwrap();
+
+ let config = CargoConfig::try_from_path(&config).unwrap();
+ assert_eq!(
+ config
+ .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
+ .unwrap(),
+ "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ );
+ assert_eq!(
+ config
+ .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
+ .unwrap(),
+ "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ );
+ assert_eq!(
+ config
+ .resolve_replacement_url(
+ "https://artprod.mycompany/artifactory/git/cargo-remote.git"
+ )
+ .unwrap(),
+ "https://artprod.mycompany/artifactory/git/cargo-remote.git"
+ );
+ }
+
+ #[test]
+ fn resolve_replacement_url_source() {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let config = temp_dir.as_ref().join("config.toml");
+
+ fs::write(&config, textwrap::dedent(
+ r##"
+ [registries]
+ art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
+
+ [source.crates-io]
+ replace-with = "art-crates-remote"
+
+ [source.some-mirror]
+ registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ "##,
+ )).unwrap();
+
+ let config = CargoConfig::try_from_path(&config).unwrap();
+ assert_eq!(
+ config
+ .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
+ .unwrap(),
+ "https://artprod.mycompany/artifactory/git/cargo-remote.git"
+ );
+ assert_eq!(
+ config
+ .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
+ .unwrap(),
+ "https://artmirror.mycompany/artifactory/cargo-mirror.git"
+ );
+ assert_eq!(
+ config
+ .resolve_replacement_url(
+ "https://artprod.mycompany/artifactory/git/cargo-remote.git"
+ )
+ .unwrap(),
+ "https://artprod.mycompany/artifactory/git/cargo-remote.git"
+ );
+ }
}
diff --git a/crate_universe/src/splicing/crate_index_lookup.rs b/crate_universe/src/splicing/crate_index_lookup.rs
new file mode 100644
index 0000000..3ba4fda
--- /dev/null
+++ b/crate_universe/src/splicing/crate_index_lookup.rs
@@ -0,0 +1,153 @@
+use crate::splicing::SourceInfo;
+use anyhow::{Context, Result};
+use crates_index::IndexConfig;
+use hex::ToHex;
+
+pub enum CrateIndexLookup {
+ Git(crates_index::Index),
+ Http(crates_index::SparseIndex),
+}
+
+impl CrateIndexLookup {
+ pub fn get_source_info(&self, pkg: &cargo_lock::Package) -> Result<Option<SourceInfo>> {
+ let index_config = self
+ .index_config()
+ .context("Failed to get crate index config")?;
+ let crate_ = match self {
+ // The crates we care about should all be in the cache already,
+ // because `cargo metadata` ran which should have fetched them.
+ Self::Http(index) => Some(
+ index
+ .crate_from_cache(pkg.name.as_str())
+ .with_context(|| format!("Failed to get crate from cache for {pkg:?}"))?,
+ ),
+ Self::Git(index) => index.crate_(pkg.name.as_str()),
+ };
+ let source_info = crate_.and_then(|crate_idx| {
+ crate_idx
+ .versions()
+ .iter()
+ .find(|v| v.version() == pkg.version.to_string())
+ .and_then(|v| {
+ v.download_url(&index_config).map(|url| {
+ let sha256 = pkg
+ .checksum
+ .as_ref()
+ .and_then(|sum| sum.as_sha256().map(|sum| sum.encode_hex::<String>()))
+ .unwrap_or_else(|| v.checksum().encode_hex::<String>());
+ SourceInfo { url, sha256 }
+ })
+ })
+ });
+ Ok(source_info)
+ }
+
+ fn index_config(&self) -> Result<IndexConfig, crates_index::Error> {
+ match self {
+ Self::Git(index) => index.index_config(),
+ Self::Http(index) => index.index_config(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::splicing::crate_index_lookup::CrateIndexLookup;
+ use semver::Version;
+ use std::ffi::OsString;
+
+ // TODO: Avoid global state (env vars) in these tests.
+ // TODO: These should be separate tests methods but they have conflicting state.
+
+ #[test]
+ fn sparse_index() {
+ let runfiles = runfiles::Runfiles::create().unwrap();
+ {
+ let _e = EnvVarResetter::set(
+ "CARGO_HOME",
+ runfiles.rlocation(
+ "rules_rust/crate_universe/test_data/crate_indexes/lazy_static/cargo_home",
+ ),
+ );
+
+ let index = CrateIndexLookup::Http(
+ crates_index::SparseIndex::from_url("sparse+https://index.crates.io/").unwrap(),
+ );
+ let source_info = index
+ .get_source_info(&cargo_lock::Package {
+ name: "lazy_static".parse().unwrap(),
+ version: Version::parse("1.4.0").unwrap(),
+ source: None,
+ checksum: None,
+ dependencies: Vec::new(),
+ replace: None,
+ })
+ .unwrap()
+ .unwrap();
+ assert_eq!(
+ source_info.url,
+ "https://crates.io/api/v1/crates/lazy_static/1.4.0/download"
+ );
+ assert_eq!(
+ source_info.sha256,
+ "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+ );
+ }
+ {
+ let _e = EnvVarResetter::set("CARGO_HOME", runfiles.rlocation("rules_rust/crate_universe/test_data/crate_indexes/rewritten_lazy_static/cargo_home"));
+
+ let index = CrateIndexLookup::Http(
+ crates_index::SparseIndex::from_url("sparse+https://index.crates.io/").unwrap(),
+ );
+ let source_info = index
+ .get_source_info(&cargo_lock::Package {
+ name: "lazy_static".parse().unwrap(),
+ version: Version::parse("1.4.0").unwrap(),
+ source: None,
+ checksum: None,
+ dependencies: Vec::new(),
+ replace: None,
+ })
+ .unwrap()
+ .unwrap();
+ assert_eq!(
+ source_info.url,
+ "https://some-mirror.com/api/v1/crates/lazy_static/1.4.0/download"
+ );
+ assert_eq!(
+ source_info.sha256,
+ "fffffffffbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+ );
+ }
+ }
+
+ struct EnvVarResetter {
+ key: OsString,
+ value: Option<OsString>,
+ }
+
+ impl EnvVarResetter {
+ fn set<K: Into<OsString>, V: Into<OsString>>(key: K, value: V) -> EnvVarResetter {
+ let key = key.into();
+ let value = value.into();
+ let old_value = std::env::var_os(&key);
+
+ std::env::set_var(&key, value);
+
+ EnvVarResetter {
+ key,
+ value: old_value,
+ }
+ }
+ }
+
+ impl Drop for EnvVarResetter {
+ fn drop(&mut self) {
+ if let Some(old_value) = self.value.as_ref() {
+ std::env::set_var(&self.key, old_value);
+ } else {
+ std::env::remove_var(&self.key);
+ }
+ }
+ }
+}
diff --git a/crate_universe/src/splicing/splicer.rs b/crate_universe/src/splicing/splicer.rs
index 8374df4..63d2f21 100644
--- a/crate_universe/src/splicing/splicer.rs
+++ b/crate_universe/src/splicing/splicer.rs
@@ -1,6 +1,6 @@
//! Utility for creating valid Cargo workspaces
-use std::collections::{BTreeSet, HashMap};
+use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
@@ -33,7 +33,7 @@
},
/// Splice a manifest from multiple disjoint Cargo manifests.
MultiPackage {
- manifests: &'a HashMap<PathBuf, Manifest>,
+ manifests: &'a BTreeMap<PathBuf, Manifest>,
splicing_manifest: &'a SplicingManifest,
},
}
@@ -43,12 +43,12 @@
impl<'a> SplicerKind<'a> {
pub fn new(
- manifests: &'a HashMap<PathBuf, Manifest>,
+ manifests: &'a BTreeMap<PathBuf, Manifest>,
splicing_manifest: &'a SplicingManifest,
cargo: &Path,
) -> Result<Self> {
// First check for any workspaces in the provided manifests
- let workspace_owned: HashMap<&PathBuf, &Manifest> = manifests
+ let workspace_owned: BTreeMap<&PathBuf, &Manifest> = manifests
.iter()
.filter(|(_, manifest)| is_workspace_owned(manifest))
.collect();
@@ -57,11 +57,10 @@
if !workspace_owned.is_empty() {
// Filter for the root workspace manifest info
- let (mut workspace_roots, workspace_packages): (
- HashMap<&PathBuf, &Manifest>,
- HashMap<&PathBuf, &Manifest>,
+ let (workspace_roots, workspace_packages): (
+ BTreeMap<&PathBuf, &Manifest>,
+ BTreeMap<&PathBuf, &Manifest>,
) = workspace_owned
- .clone()
.into_iter()
.partition(|(_, manifest)| is_workspace_root(manifest));
@@ -96,7 +95,7 @@
// Ensure all workspace owned manifests are members of the one workspace root
// UNWRAP: Safe because we've checked workspace_roots isn't empty.
- let (root_manifest_path, root_manifest) = workspace_roots.drain().last().unwrap();
+ let (root_manifest_path, root_manifest) = workspace_roots.into_iter().next().unwrap();
let external_workspace_members: BTreeSet<String> = workspace_packages
.into_iter()
.filter(|(manifest_path, _)| {
@@ -118,7 +117,7 @@
.keys()
.map(|p| {
p.normalize()
- .with_context(|| format!("Failed to normalize path {:?}", p))
+ .with_context(|| format!("Failed to normalize path {p:?}"))
})
.collect::<Result<_, _>>()?,
)
@@ -165,7 +164,7 @@
.map(|member| {
let path = root_manifest_dir.join(member).join("Cargo.toml");
path.normalize()
- .with_context(|| format!("Failed to normalize path {:?}", path))
+ .with_context(|| format!("Failed to normalize path {path:?}"))
})
.collect::<Result<BTreeSet<normpath::BasePathBuf>, _>>()?;
@@ -178,10 +177,7 @@
.map(|workspace_manifest_path| {
let label = Label::from_absolute_path(workspace_manifest_path.as_path())
.with_context(|| {
- format!(
- "Failed to identify label for path {:?}",
- workspace_manifest_path
- )
+ format!("Failed to identify label for path {workspace_manifest_path:?}")
})?;
Ok(label.to_string())
})
@@ -227,10 +223,12 @@
Self::setup_cargo_config(&splicing_manifest.cargo_config, workspace_dir)?;
// Add any additional depeendencies to the root package
- Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ if !splicing_manifest.direct_packages.is_empty() {
+ Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ }
let root_manifest_path = workspace_dir.join("Cargo.toml");
- let member_manifests = HashMap::from([(*path, String::new())]);
+ let member_manifests = BTreeMap::from([(*path, String::new())]);
// Write the generated metadata to the manifest
let workspace_metadata = WorkspaceMetadata::new(splicing_manifest, member_manifests)?;
@@ -266,11 +264,13 @@
default_cargo_workspace_manifest(&splicing_manifest.resolver_version).workspace
}
- // Add any additional depeendencies to the root package
- Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ // Add any additional dependencies to the root package
+ if !splicing_manifest.direct_packages.is_empty() {
+ Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ }
let root_manifest_path = workspace_dir.join("Cargo.toml");
- let member_manifests = HashMap::from([(*path, String::new())]);
+ let member_manifests = BTreeMap::from([(*path, String::new())]);
// Write the generated metadata to the manifest
let workspace_metadata = WorkspaceMetadata::new(splicing_manifest, member_manifests)?;
@@ -285,7 +285,7 @@
/// Implementation for splicing together multiple Cargo packages/workspaces
fn splice_multi_package(
workspace_dir: &Path,
- manifests: &&HashMap<PathBuf, Manifest>,
+ manifests: &&BTreeMap<PathBuf, Manifest>,
splicing_manifest: &&SplicingManifest,
) -> Result<SplicedManifest> {
let mut manifest = default_cargo_workspace_manifest(&splicing_manifest.resolver_version);
@@ -296,12 +296,27 @@
let installations =
Self::inject_workspace_members(&mut manifest, manifests, workspace_dir)?;
+ // Collect all patches from the manifests provided
+ for (_, sub_manifest) in manifests.iter() {
+ Self::inject_patches(&mut manifest, &sub_manifest.patch).with_context(|| {
+ format!(
+ "Duplicate `[patch]` entries detected in {:#?}",
+ manifests
+ .keys()
+ .map(|p| p.display().to_string())
+ .collect::<Vec<String>>()
+ )
+ })?;
+ }
+
// Write the generated metadata to the manifest
let workspace_metadata = WorkspaceMetadata::new(splicing_manifest, installations)?;
workspace_metadata.inject_into(&mut manifest)?;
// Add any additional depeendencies to the root package
- Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ if !splicing_manifest.direct_packages.is_empty() {
+ Self::inject_direct_packages(&mut manifest, &splicing_manifest.direct_packages)?;
+ }
// Write the root manifest
let root_manifest_path = workspace_dir.join("Cargo.toml");
@@ -390,7 +405,7 @@
fs::create_dir_all(&dot_cargo_dir)?;
}
- fs::copy(cargo_config_path, &dot_cargo_dir.join("config.toml"))?;
+ fs::copy(cargo_config_path, dot_cargo_dir.join("config.toml"))?;
}
Ok(())
@@ -400,9 +415,9 @@
/// Cargo workspace members.
fn inject_workspace_members<'b>(
root_manifest: &mut Manifest,
- manifests: &'b HashMap<PathBuf, Manifest>,
+ manifests: &'b BTreeMap<PathBuf, Manifest>,
workspace_dir: &Path,
- ) -> Result<HashMap<&'b PathBuf, String>> {
+ ) -> Result<BTreeMap<&'b PathBuf, String>> {
manifests
.iter()
.map(|(path, manifest)| {
@@ -469,11 +484,42 @@
Ok(())
}
+
+ fn inject_patches(manifest: &mut Manifest, patches: &cargo_toml::PatchSet) -> Result<()> {
+ for (registry, new_patches) in patches.iter() {
+ // If there is an existing patch entry it will need to be merged
+ if let Some(existing_patches) = manifest.patch.get_mut(registry) {
+ // Error out if there are duplicate patches
+ existing_patches.extend(
+ new_patches
+ .iter()
+ .map(|(pkg, info)| {
+ if let Some(existing_info) = existing_patches.get(pkg) {
+ // Only error if the patches are not identical
+ if existing_info != info {
+ bail!(
+ "Duplicate patches were found for `[patch.{}] {}`",
+ registry,
+ pkg
+ );
+ }
+ }
+ Ok((pkg.clone(), info.clone()))
+ })
+ .collect::<Result<cargo_toml::DepsSet>>()?,
+ );
+ } else {
+ manifest.patch.insert(registry.clone(), new_patches.clone());
+ }
+ }
+
+ Ok(())
+ }
}
pub struct Splicer {
workspace_dir: PathBuf,
- manifests: HashMap<PathBuf, Manifest>,
+ manifests: BTreeMap<PathBuf, Manifest>,
splicing_manifest: SplicingManifest,
}
@@ -482,13 +528,13 @@
// Load all manifests
let manifests = splicing_manifest
.manifests
- .iter()
- .map(|(path, _)| {
+ .keys()
+ .map(|path| {
let m = read_manifest(path)
.with_context(|| format!("Failed to read manifest at {}", path.display()))?;
Ok((path.clone(), m))
})
- .collect::<Result<HashMap<PathBuf, Manifest>>>()?;
+ .collect::<Result<BTreeMap<PathBuf, Manifest>>>()?;
Ok(Self {
workspace_dir,
@@ -510,7 +556,7 @@
pub fn default_cargo_package_manifest() -> cargo_toml::Manifest {
// A manifest is generated with a fake workspace member so the [cargo_toml::Manifest::Workspace]
// member is deseralized and is not `None`.
- let manifest = cargo_toml::Manifest::from_str(
+ cargo_toml::Manifest::from_str(
&toml::toml! {
[package]
name = DEFAULT_SPLICING_PACKAGE_NAME
@@ -524,9 +570,7 @@
}
.to_string(),
)
- .unwrap();
-
- manifest
+ .unwrap()
}
pub fn default_splicing_package_crate_id() -> CrateId {
@@ -544,9 +588,8 @@
let mut manifest = cargo_toml::Manifest::from_str(&textwrap::dedent(&format!(
r#"
[workspace]
- resolver = "{}"
+ resolver = "{resolver_version}"
"#,
- resolver_version,
)))
.unwrap();
@@ -611,7 +654,7 @@
}
// TODO(https://gitlab.com/crates.rs/cargo_toml/-/issues/3)
- let value = toml::Value::try_from(&manifest)?;
+ let value = toml::Value::try_from(manifest)?;
fs::write(path, toml::to_string(&value)?)
.context(format!("Failed to write manifest to {}", path.display()))
}
@@ -749,7 +792,7 @@
let (cargo_path, rustc_path) = get_cargo_and_rustc_paths();
- let output = MetadataCommand::new()
+ MetadataCommand::new()
.cargo_path(cargo_path)
// Cargo detects config files based on `pwd` when running so
// to ensure user provided Cargo config files are used, it's
@@ -757,25 +800,9 @@
.current_dir(manifest_dir)
.manifest_path(manifest_path)
.other_options(["--offline".to_owned()])
- .cargo_command()
.env("RUSTC", rustc_path)
- .output()
- .unwrap();
-
- if !output.status.success() {
- eprintln!("{}", String::from_utf8_lossy(&output.stderr));
- assert!(output.status.success());
- }
-
- let stdout = String::from_utf8(output.stdout).unwrap();
-
- assert!(stdout
- .lines()
- .find(|line| line.starts_with('{'))
- .ok_or(cargo_metadata::Error::NoJson)
- .is_ok());
-
- MetadataCommand::parse(stdout).unwrap()
+ .exec()
+ .unwrap()
}
fn mock_cargo_toml(path: &Path, name: &str) -> cargo_toml::Manifest {
@@ -823,14 +850,16 @@
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"url": "https://crates.io/"
}
- }
+ },
+ "features": {}
}
})
} else {
serde_json::json!({
"cargo-bazel": {
"package_prefixes": {},
- "sources": {}
+ "sources": {},
+ "features": {}
}
})
};
@@ -863,7 +892,7 @@
splicing_manifest.manifests.insert(
manifest_path,
- Label::from_str(&format!("//{}:Cargo.toml", pkg)).unwrap(),
+ Label::from_str(&format!("//{pkg}:Cargo.toml")).unwrap(),
);
}
@@ -890,27 +919,23 @@
}
let root_pkg = workspace_root.join("root_pkg");
let manifest_path = root_pkg.join("Cargo.toml");
- fs::create_dir_all(&manifest_path.parent().unwrap()).unwrap();
+ fs::create_dir_all(manifest_path.parent().unwrap()).unwrap();
fs::write(&manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
{
File::create(root_pkg.join("BUILD.bazel")).unwrap();
}
- let sub_pkg_a = root_pkg.join("sub_pkg_a");
- let sub_pkg_b = root_pkg.join("sub_pkg_b");
- {
- fs::create_dir_all(&sub_pkg_a).unwrap();
- File::create(sub_pkg_a.join("BUILD.bazel")).unwrap();
-
- fs::create_dir_all(&sub_pkg_b).unwrap();
- File::create(sub_pkg_b.join("BUILD.bazel")).unwrap();
- }
-
splicing_manifest.manifests.insert(
manifest_path,
Label::from_str("//root_pkg:Cargo.toml").unwrap(),
);
+ for sub_pkg in ["sub_pkg_a", "sub_pkg_b"] {
+ let sub_pkg_path = root_pkg.join(sub_pkg);
+ fs::create_dir_all(&sub_pkg_path).unwrap();
+ File::create(sub_pkg_path.join("BUILD.bazel")).unwrap();
+ }
+
(splicing_manifest, cache_dir)
}
@@ -925,7 +950,7 @@
splicing_manifest.manifests.insert(
manifest_path,
- Label::from_str(&format!("//{}:Cargo.toml", pkg)).unwrap(),
+ Label::from_str(&format!("//{pkg}:Cargo.toml")).unwrap(),
);
}
@@ -951,23 +976,19 @@
File::create(workspace_root.join("WORKSPACE.bazel")).unwrap();
}
let manifest_path = workspace_root.join("Cargo.toml");
- fs::create_dir_all(&manifest_path.parent().unwrap()).unwrap();
+ fs::create_dir_all(manifest_path.parent().unwrap()).unwrap();
fs::write(&manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
- let sub_pkg_a = workspace_root.join("sub_pkg_a");
- let sub_pkg_b = workspace_root.join("sub_pkg_b");
- {
- fs::create_dir_all(&sub_pkg_a).unwrap();
- File::create(sub_pkg_a.join("BUILD.bazel")).unwrap();
-
- fs::create_dir_all(&sub_pkg_b).unwrap();
- File::create(sub_pkg_b.join("BUILD.bazel")).unwrap();
- }
-
splicing_manifest
.manifests
.insert(manifest_path, Label::from_str("//:Cargo.toml").unwrap());
+ for sub_pkg in ["sub_pkg_a", "sub_pkg_b"] {
+ let sub_pkg_path = workspace_root.join(sub_pkg);
+ fs::create_dir_all(&sub_pkg_path).unwrap();
+ File::create(sub_pkg_path.join("BUILD.bazel")).unwrap();
+ }
+
(splicing_manifest, cache_dir)
}
@@ -1012,11 +1033,11 @@
if is_root {
PackageId {
- repr: format!("{} 0.0.1 (path+file://{})", name, workspace_root),
+ repr: format!("{name} 0.0.1 (path+file://{workspace_root})"),
}
} else {
PackageId {
- repr: format!("{} 0.0.1 (path+file://{}/{})", name, workspace_root, name,),
+ repr: format!("{name} 0.0.1 (path+file://{workspace_root}/{name})"),
}
}
}
@@ -1050,6 +1071,13 @@
mock_workspace_metadata(false, None)
);
+ // Since no direct packages were added to the splicing manifest, the cargo_bazel
+ // deps target should __not__ have been injected into the manifest.
+ assert!(!metadata
+ .packages
+ .iter()
+ .any(|pkg| pkg.name == DEFAULT_SPLICING_PACKAGE_NAME));
+
// Ensure lockfile was successfully spliced
cargo_lock::Lockfile::load(workspace_root.as_ref().join("Cargo.lock")).unwrap();
}
@@ -1083,6 +1111,13 @@
mock_workspace_metadata(false, None)
);
+ // Since no direct packages were added to the splicing manifest, the cargo_bazel
+ // deps target should __not__ have been injected into the manifest.
+ assert!(!metadata
+ .packages
+ .iter()
+ .any(|pkg| pkg.name == DEFAULT_SPLICING_PACKAGE_NAME));
+
// Ensure lockfile was successfully spliced
cargo_lock::Lockfile::load(workspace_root.as_ref().join("Cargo.lock")).unwrap();
}
@@ -1155,7 +1190,7 @@
fs::create_dir_all(external_manifest.parent().unwrap()).unwrap();
fs::write(
&external_manifest,
- &textwrap::dedent(
+ textwrap::dedent(
r#"
[package]
name = "external_workspace_member"
@@ -1196,6 +1231,46 @@
}
#[test]
+ fn splice_workspace_no_root_pkg() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_workspace_in_root();
+
+ // Modify the root manifest to remove the rendered package
+ fs::write(
+ cache_dir.as_ref().join("Cargo.toml"),
+ textwrap::dedent(
+ r#"
+ [workspace]
+ members = [
+ "sub_pkg_a",
+ "sub_pkg_b",
+ ]
+ "#,
+ ),
+ )
+ .unwrap();
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ let metadata = generate_metadata(workspace_manifest.as_path_buf());
+
+ // Since no direct packages were added to the splicing manifest, the cargo_bazel
+ // deps target should __not__ have been injected into the manifest.
+ assert!(!metadata
+ .packages
+ .iter()
+ .any(|pkg| pkg.name == DEFAULT_SPLICING_PACKAGE_NAME));
+
+ // Ensure lockfile was successfully spliced
+ cargo_lock::Lockfile::load(workspace_root.as_ref().join("Cargo.lock")).unwrap();
+ }
+
+ #[test]
fn splice_package() {
let (splicing_manifest, _cache_dir) = mock_splicing_manifest_with_package();
@@ -1255,8 +1330,6 @@
new_package_id("pkg_a", workspace_root.as_ref(), false),
new_package_id("pkg_b", workspace_root.as_ref(), false),
new_package_id("pkg_c", workspace_root.as_ref(), false),
- // Multi package renderings always add a root package
- new_package_id("direct-cargo-bazel-deps", workspace_root.as_ref(), true),
]
);
@@ -1304,8 +1377,6 @@
new_package_id("pkg_a", workspace_root.as_ref(), false),
new_package_id("pkg_b", workspace_root.as_ref(), false),
new_package_id("pkg_c", workspace_root.as_ref(), false),
- // Multi package renderings always add a root package
- new_package_id("direct-cargo-bazel-deps", workspace_root.as_ref(), true),
]
);
@@ -1320,6 +1391,254 @@
}
#[test]
+ fn splice_multi_package_with_direct_deps() {
+ let (mut splicing_manifest, _cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ // Add a "direct dependency" entry
+ splicing_manifest.direct_packages.insert(
+ "fake_pkg".to_owned(),
+ cargo_toml::DependencyDetail {
+ version: Some("1.2.3".to_owned()),
+ ..cargo_toml::DependencyDetail::default()
+ },
+ );
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ // Check the default resolver version
+ let cargo_manifest = cargo_toml::Manifest::from_str(
+ &fs::read_to_string(workspace_manifest.as_path_buf()).unwrap(),
+ )
+ .unwrap();
+
+ // Due to the addition of direct deps for splicing, this package should have been added to the root manfiest.
+ assert!(cargo_manifest.package.unwrap().name == DEFAULT_SPLICING_PACKAGE_NAME);
+ }
+
+ #[test]
+ fn splice_multi_package_with_patch() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ // Generate a patch entry
+ let expected = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ BTreeMap::from([(
+ "foo".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ )]),
+ )]);
+
+ // Insert the patch entry to the manifests
+ let manifest_path = cache_dir.as_ref().join("pkg_a").join("Cargo.toml");
+ let mut manifest =
+ cargo_toml::Manifest::from_str(&fs::read_to_string(&manifest_path).unwrap()).unwrap();
+ manifest.patch.extend(expected.clone());
+ fs::write(manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ // Ensure the patches match the expected value
+ let cargo_manifest = cargo_toml::Manifest::from_str(
+ &fs::read_to_string(workspace_manifest.as_path_buf()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(expected, cargo_manifest.patch);
+ }
+
+ #[test]
+ fn splice_multi_package_with_multiple_patch_registries() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ let mut expected = cargo_toml::PatchSet::new();
+
+ for pkg in ["pkg_a", "pkg_b"] {
+ // Generate a patch entry
+ let new_patch = cargo_toml::PatchSet::from([(
+ format!("{pkg}_registry"),
+ BTreeMap::from([(
+ "foo".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ )]),
+ )]);
+ expected.extend(new_patch.clone());
+
+ // Insert the patch entry to the manifests
+ let manifest_path = cache_dir.as_ref().join(pkg).join("Cargo.toml");
+ let mut manifest =
+ cargo_toml::Manifest::from_str(&fs::read_to_string(&manifest_path).unwrap())
+ .unwrap();
+ manifest.patch.extend(new_patch);
+ fs::write(manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
+ }
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ // Ensure the patches match the expected value
+ let cargo_manifest = cargo_toml::Manifest::from_str(
+ &fs::read_to_string(workspace_manifest.as_path_buf()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(expected, cargo_manifest.patch);
+ }
+
+ #[test]
+ fn splice_multi_package_with_merged_patch_registries() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ let expected = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ cargo_toml::DepsSet::from([
+ (
+ "foo-pkg_a".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ ),
+ (
+ "foo-pkg_b".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ ),
+ ]),
+ )]);
+
+ for pkg in ["pkg_a", "pkg_b"] {
+ // Generate a patch entry
+ let new_patch = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ BTreeMap::from([(
+ format!("foo-{pkg}"),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ )]),
+ )]);
+
+ // Insert the patch entry to the manifests
+ let manifest_path = cache_dir.as_ref().join(pkg).join("Cargo.toml");
+ let mut manifest =
+ cargo_toml::Manifest::from_str(&fs::read_to_string(&manifest_path).unwrap())
+ .unwrap();
+ manifest.patch.extend(new_patch);
+ fs::write(manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
+ }
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ // Ensure the patches match the expected value
+ let cargo_manifest = cargo_toml::Manifest::from_str(
+ &fs::read_to_string(workspace_manifest.as_path_buf()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(expected, cargo_manifest.patch);
+ }
+
+ #[test]
+ fn splice_multi_package_with_merged_identical_patch_registries() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ let expected = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ cargo_toml::DepsSet::from([(
+ "foo".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ )]),
+ )]);
+
+ for pkg in ["pkg_a", "pkg_b"] {
+ // Generate a patch entry
+ let new_patch = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ BTreeMap::from([(
+ "foo".to_owned(),
+ cargo_toml::Dependency::Simple("1.2.3".to_owned()),
+ )]),
+ )]);
+
+ // Insert the patch entry to the manifests
+ let manifest_path = cache_dir.as_ref().join(pkg).join("Cargo.toml");
+ let mut manifest =
+ cargo_toml::Manifest::from_str(&fs::read_to_string(&manifest_path).unwrap())
+ .unwrap();
+ manifest.patch.extend(new_patch);
+ fs::write(manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
+ }
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let workspace_manifest =
+ Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo())
+ .unwrap();
+
+ // Ensure the patches match the expected value
+ let cargo_manifest = cargo_toml::Manifest::from_str(
+ &fs::read_to_string(workspace_manifest.as_path_buf()).unwrap(),
+ )
+ .unwrap();
+ assert_eq!(expected, cargo_manifest.patch);
+ }
+
+ #[test]
+ fn splice_multi_package_with_conflicting_patch() {
+ let (splicing_manifest, cache_dir) = mock_splicing_manifest_with_multi_package();
+
+ let mut patch = 3;
+ for pkg in ["pkg_a", "pkg_b"] {
+ // Generate a patch entry
+ let new_patch = cargo_toml::PatchSet::from([(
+ "registry".to_owned(),
+ BTreeMap::from([(
+ "foo".to_owned(),
+ cargo_toml::Dependency::Simple(format!("1.2.{patch}")),
+ )]),
+ )]);
+
+ // Increment the patch semver to make the patch info unique.
+ patch += 1;
+
+ // Insert the patch entry to the manifests
+ let manifest_path = cache_dir.as_ref().join(pkg).join("Cargo.toml");
+ let mut manifest =
+ cargo_toml::Manifest::from_str(&fs::read_to_string(&manifest_path).unwrap())
+ .unwrap();
+ manifest.patch.extend(new_patch);
+ fs::write(manifest_path, toml::to_string(&manifest).unwrap()).unwrap();
+ }
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ let result = Splicer::new(workspace_root.as_ref().to_path_buf(), splicing_manifest)
+ .unwrap()
+ .splice_workspace(&cargo());
+
+ // Confirm conflicting patches have been detected
+ assert!(result.is_err());
+ let err_str = result.err().unwrap().to_string();
+ assert!(err_str.starts_with("Duplicate `[patch]` entries detected in"));
+ }
+
+ #[test]
fn cargo_config_setup() {
let (mut splicing_manifest, _cache_dir) = mock_splicing_manifest_with_workspace_in_root();
@@ -1606,6 +1925,6 @@
fn touch(path: &Path) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
- std::fs::write(path, &[]).unwrap();
+ std::fs::write(path, []).unwrap();
}
}
diff --git a/crate_universe/src/test.rs b/crate_universe/src/test.rs
index 5b67351..3717ec4 100644
--- a/crate_universe/src/test.rs
+++ b/crate_universe/src/test.rs
@@ -83,6 +83,22 @@
.unwrap()
}
+ pub fn optional_deps_disabled() -> cargo_metadata::Metadata {
+ serde_json::from_str(include_str!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/test_data/metadata/crate_optional_deps_disabled/metadata.json"
+ )))
+ .unwrap()
+ }
+
+ pub fn optional_deps_enabled() -> cargo_metadata::Metadata {
+ serde_json::from_str(include_str!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/test_data/metadata/crate_optional_deps_enabled/metadata.json"
+ )))
+ .unwrap()
+ }
+
pub fn common() -> cargo_metadata::Metadata {
serde_json::from_str(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
@@ -127,6 +143,14 @@
.unwrap()
}
+ pub fn multi_cfg_dep() -> cargo_lock::Lockfile {
+ cargo_lock::Lockfile::from_str(include_str!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/test_data/metadata/multi_cfg_dep/Cargo.lock"
+ )))
+ .unwrap()
+ }
+
pub fn no_deps() -> cargo_lock::Lockfile {
cargo_lock::Lockfile::from_str(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
diff --git a/crate_universe/src/utils.rs b/crate_universe/src/utils.rs
index 8953c81..c7f12c2 100644
--- a/crate_universe/src/utils.rs
+++ b/crate_universe/src/utils.rs
@@ -2,6 +2,8 @@
pub mod starlark;
+pub const CRATES_IO_INDEX_URL: &str = "https://github.com/rust-lang/crates.io-index";
+
/// Convert a string into a valid crate module name by applying transforms to invalid characters
pub fn sanitize_module_name(name: &str) -> String {
name.replace('-', "_")
diff --git a/crate_universe/src/utils/starlark.rs b/crate_universe/src/utils/starlark.rs
index 57007b7..d99bbab 100644
--- a/crate_universe/src/utils/starlark.rs
+++ b/crate_universe/src/utils/starlark.rs
@@ -3,10 +3,283 @@
mod glob;
mod label;
mod select;
+mod serialize;
+mod target_compatible_with;
+
+use std::collections::BTreeSet as Set;
+
+use serde::{Serialize, Serializer};
+use serde_starlark::Error as StarlarkError;
pub use glob::*;
pub use label::*;
pub use select::*;
+pub use target_compatible_with::*;
pub type SelectStringList = SelectList<String>;
pub type SelectStringDict = SelectDict<String>;
+
+#[derive(Serialize)]
+#[serde(untagged)]
+pub enum Starlark {
+ Load(Load),
+ Package(Package),
+ ExportsFiles(ExportsFiles),
+ Filegroup(Filegroup),
+ Alias(Alias),
+ CargoBuildScript(CargoBuildScript),
+ #[serde(serialize_with = "serialize::rust_proc_macro")]
+ RustProcMacro(RustProcMacro),
+ #[serde(serialize_with = "serialize::rust_library")]
+ RustLibrary(RustLibrary),
+ #[serde(serialize_with = "serialize::rust_binary")]
+ RustBinary(RustBinary),
+
+ #[serde(skip_serializing)]
+ Verbatim(String),
+}
+
+pub struct Load {
+ pub bzl: String,
+ pub items: Set<String>,
+}
+
+pub struct Package {
+ pub default_visibility: Set<String>,
+}
+
+pub struct ExportsFiles {
+ pub paths: Set<String>,
+ pub globs: Glob,
+}
+
+#[derive(Serialize)]
+#[serde(rename = "filegroup")]
+pub struct Filegroup {
+ pub name: String,
+ pub srcs: Glob,
+}
+
+#[derive(Serialize)]
+#[serde(rename = "alias")]
+pub struct Alias {
+ pub name: String,
+ pub actual: String,
+ pub tags: Set<String>,
+}
+
+#[derive(Serialize)]
+#[serde(rename = "cargo_build_script")]
+pub struct CargoBuildScript {
+ pub name: String,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub build_script_env: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(skip_serializing_if = "Data::is_empty")]
+ pub compile_data: Data,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub crate_features: SelectList<String>,
+ pub crate_name: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub crate_root: Option<String>,
+ #[serde(skip_serializing_if = "Data::is_empty")]
+ pub data: Data,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub deps: SelectList<WithOriginalConfigurations<String>>,
+ pub edition: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub linker_script: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub links: Option<String>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub rustc_env: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub rustc_env_files: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub rustc_flags: SelectList<WithOriginalConfigurations<String>>,
+ pub srcs: Glob,
+ #[serde(skip_serializing_if = "Set::is_empty")]
+ pub tags: Set<String>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub tools: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(skip_serializing_if = "Set::is_empty")]
+ pub toolchains: Set<String>,
+ pub version: String,
+ pub visibility: Set<String>,
+}
+
+#[derive(Serialize)]
+pub struct RustProcMacro {
+ pub name: String,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(flatten)]
+ pub common: CommonAttrs,
+}
+
+#[derive(Serialize)]
+pub struct RustLibrary {
+ pub name: String,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(flatten)]
+ pub common: CommonAttrs,
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
+ pub disable_pipelining: bool,
+}
+
+#[derive(Serialize)]
+pub struct RustBinary {
+ pub name: String,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(flatten)]
+ pub common: CommonAttrs,
+}
+
+#[derive(Serialize)]
+pub struct CommonAttrs {
+ #[serde(skip_serializing_if = "Data::is_empty")]
+ pub compile_data: Data,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub crate_features: SelectList<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub crate_root: Option<String>,
+ #[serde(skip_serializing_if = "Data::is_empty")]
+ pub data: Data,
+ pub edition: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub linker_script: Option<String>,
+ #[serde(
+ skip_serializing_if = "SelectDict::is_empty",
+ serialize_with = "SelectDict::serialize_starlark"
+ )]
+ pub rustc_env: SelectDict<WithOriginalConfigurations<String>>,
+ #[serde(
+ skip_serializing_if = "SelectList::is_empty",
+ serialize_with = "SelectList::serialize_starlark"
+ )]
+ pub rustc_env_files: SelectList<WithOriginalConfigurations<String>>,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ pub rustc_flags: Vec<String>,
+ pub srcs: Glob,
+ #[serde(skip_serializing_if = "Set::is_empty")]
+ pub tags: Set<String>,
+ #[serde(
+ serialize_with = "serialize_target_compatible_with",
+ skip_serializing_if = "Option::is_none"
+ )]
+ pub target_compatible_with: Option<TargetCompatibleWith>,
+ pub version: String,
+}
+
+fn serialize_target_compatible_with<S>(
+ value: &Option<TargetCompatibleWith>,
+ serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ // SAFETY: Option::is_none causes serialization to get skipped.
+ value.as_ref().unwrap().serialize_starlark(serializer)
+}
+
+pub struct Data {
+ pub glob: Glob,
+ pub select: SelectList<WithOriginalConfigurations<String>>,
+}
+
+impl Package {
+ pub fn default_visibility_public() -> Self {
+ let mut default_visibility = Set::new();
+ default_visibility.insert("//visibility:public".to_owned());
+ Package { default_visibility }
+ }
+}
+
+pub fn serialize(starlark: &[Starlark]) -> Result<String, StarlarkError> {
+ let mut content = String::new();
+ for call in starlark {
+ if !content.is_empty() {
+ content.push('\n');
+ }
+ if let Starlark::Verbatim(comment) = call {
+ content.push_str(comment);
+ } else {
+ content.push_str(&serde_starlark::to_string(call)?);
+ }
+ }
+ Ok(content)
+}
diff --git a/crate_universe/src/utils/starlark/glob.rs b/crate_universe/src/utils/starlark/glob.rs
index 23b17a5..a7bcebb 100644
--- a/crate_universe/src/utils/starlark/glob.rs
+++ b/crate_universe/src/utils/starlark/glob.rs
@@ -1,16 +1,93 @@
-use serde::{Deserialize, Serialize};
+use std::collections::BTreeSet;
+use std::fmt;
-#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer};
+use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Glob {
- pub include: Vec<String>,
- pub exclude: Vec<String>,
+ pub include: BTreeSet<String>,
+ pub exclude: BTreeSet<String>,
}
impl Glob {
pub fn new_rust_srcs() -> Self {
Self {
- include: vec!["**/*.rs".to_owned()],
- ..Default::default()
+ include: BTreeSet::from(["**/*.rs".to_owned()]),
+ exclude: BTreeSet::new(),
}
}
+
+ pub fn is_empty(&self) -> bool {
+ self.include.is_empty()
+ // Note: self.exclude intentionally not considered. A glob is empty if
+ // there are no included globs. A glob cannot have only excludes.
+ }
+}
+
+impl Serialize for Glob {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ if self.exclude.is_empty() {
+ // Serialize as glob([...]).
+ serializer.serialize_newtype_struct("glob", &self.include)
+ } else {
+ // Serialize as glob(include = [...], exclude = [...]).
+ let mut call = serializer.serialize_struct("glob", 2)?;
+ call.serialize_field("include", &self.include)?;
+ call.serialize_field("exclude", &self.exclude)?;
+ call.end()
+ }
+ }
+}
+
+struct GlobVisitor;
+
+impl<'de> Deserialize<'de> for Glob {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ deserializer.deserialize_any(GlobVisitor)
+ }
+}
+
+impl<'de> Visitor<'de> for GlobVisitor {
+ type Value = Glob;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("glob")
+ }
+
+ // Deserialize ["included","globs","only"]
+ fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'de>,
+ {
+ Ok(Glob {
+ include: BTreeSet::deserialize(SeqAccessDeserializer::new(seq))?,
+ exclude: BTreeSet::new(),
+ })
+ }
+
+ // Deserialize {"include":["included","globs"],"exclude":["excluded","globs"]}
+ fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
+ where
+ A: MapAccess<'de>,
+ {
+ #[derive(serde::Deserialize)]
+ struct GlobMap {
+ include: BTreeSet<String>,
+ exclude: BTreeSet<String>,
+ }
+
+ let glob_map = GlobMap::deserialize(MapAccessDeserializer::new(map))?;
+ Ok(Glob {
+ include: glob_map.include,
+ exclude: glob_map.exclude,
+ })
+ }
}
diff --git a/crate_universe/src/utils/starlark/label.rs b/crate_universe/src/utils/starlark/label.rs
index a21c81e..c074a62 100644
--- a/crate_universe/src/utils/starlark/label.rs
+++ b/crate_universe/src/utils/starlark/label.rs
@@ -20,16 +20,17 @@
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let re = Regex::new(r"^(@[\w\d\-_\.]*)?/{0,2}([\w\d\-_\./]+)?:?([\+\w\d\-_\./]+)$")?;
+ let re = Regex::new(r"^(@@?[\w\d\-_\.]*)?/{0,2}([\w\d\-_\./+]+)?(:([\+\w\d\-_\./]+))?$")?;
let cap = re
.captures(s)
- .with_context(|| format!("Failed to parse label from string: {}", s))?;
+ .with_context(|| format!("Failed to parse label from string: {s}"))?;
let repository = cap
.get(1)
.map(|m| m.as_str().trim_start_matches('@').to_owned());
+
let package = cap.get(2).map(|m| m.as_str().to_owned());
- let mut target = cap.get(3).map(|m| m.as_str().to_owned());
+ let mut target = cap.get(4).map(|m| m.as_str().to_owned());
if target.is_none() {
if let Some(pkg) = &package {
@@ -56,14 +57,14 @@
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Add the repository
if let Some(repo) = &self.repository {
- write!(f, "@{}", repo)?;
+ write!(f, "@{repo}")?;
}
write!(f, "//")?;
// Add the package
if let Some(pkg) = &self.package {
- write!(f, "{}", pkg)?;
+ write!(f, "{pkg}")?;
}
write!(f, ":{}", self.target)?;
@@ -192,6 +193,15 @@
use tempfile::tempdir;
#[test]
+ fn full_label_bzlmod() {
+ let label = Label::from_str("@@repo//package/sub_package:target").unwrap();
+ assert_eq!(label.to_string(), "@repo//package/sub_package:target");
+ assert_eq!(label.repository.unwrap(), "repo");
+ assert_eq!(label.package.unwrap(), "package/sub_package");
+ assert_eq!(label.target, "target");
+ }
+
+ #[test]
fn full_label() {
let label = Label::from_str("@repo//package/sub_package:target").unwrap();
assert_eq!(label.to_string(), "@repo//package/sub_package:target");
@@ -258,13 +268,25 @@
}
#[test]
+ fn label_contains_plus() {
+ let label = Label::from_str("@repo//vendor/wasi-0.11.0+wasi-snapshot-preview1:BUILD.bazel")
+ .unwrap();
+ assert_eq!(label.repository.unwrap(), "repo");
+ assert_eq!(
+ label.package.unwrap(),
+ "vendor/wasi-0.11.0+wasi-snapshot-preview1"
+ );
+ assert_eq!(label.target, "BUILD.bazel");
+ }
+
+ #[test]
fn invalid_double_colon() {
assert!(Label::from_str("::target").is_err());
}
#[test]
- fn invalid_double_at() {
- assert!(Label::from_str("@@repo//pkg:target").is_err());
+ fn invalid_triple_at() {
+ assert!(Label::from_str("@@@repo//pkg:target").is_err());
}
#[test]
@@ -282,8 +304,8 @@
let actual_file = subdir.join("greatgrandchild");
create_dir_all(subdir).unwrap();
{
- File::create(&workspace).unwrap();
- File::create(&build_file).unwrap();
+ File::create(workspace).unwrap();
+ File::create(build_file).unwrap();
File::create(&actual_file).unwrap();
}
let label = Label::from_absolute_path(&actual_file).unwrap();
@@ -300,7 +322,7 @@
let actual_file = subdir.join("greatgrandchild");
create_dir_all(subdir).unwrap();
{
- File::create(&build_file).unwrap();
+ File::create(build_file).unwrap();
File::create(&actual_file).unwrap();
}
let err = Label::from_absolute_path(&actual_file)
@@ -318,7 +340,7 @@
let actual_file = subdir.join("greatgrandchild");
create_dir_all(subdir).unwrap();
{
- File::create(&workspace).unwrap();
+ File::create(workspace).unwrap();
File::create(&actual_file).unwrap();
}
let err = Label::from_absolute_path(&actual_file)
diff --git a/crate_universe/src/utils/starlark/select.rs b/crate_universe/src/utils/starlark/select.rs
index 4a8a3cc..f01acb0 100644
--- a/crate_universe/src/utils/starlark/select.rs
+++ b/crate_universe/src/utils/starlark/select.rs
@@ -1,6 +1,11 @@
-use serde::{Deserialize, Serialize};
use std::collections::{btree_set, BTreeMap, BTreeSet};
-use std::iter::once;
+use std::iter::{once, FromIterator};
+
+use serde::ser::{SerializeMap, SerializeTupleStruct, Serializer};
+use serde::{Deserialize, Serialize};
+use serde_starlark::{FunctionCall, LineComment, MULTILINE};
+
+use crate::utils::starlark::serialize::MultilineArray;
pub trait SelectMap<T, U> {
// A selectable should also implement a `map` function allowing one type of selectable
@@ -19,8 +24,16 @@
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
pub struct SelectList<T: Ord> {
+ // Invariant: any T in `common` is not anywhere in `selects`.
common: BTreeSet<T>,
+ // Invariant: none of the sets are empty.
selects: BTreeMap<String, BTreeSet<T>>,
+ // Elements that used to be in `selects` before the most recent
+ // `remap_configurations` operation, but whose old configuration did not get
+ // mapped to any new configuration. They could be ignored, but are preserved
+ // here to generate comments that help the user understand what happened.
+ #[serde(skip_serializing_if = "BTreeSet::is_empty", default = "BTreeSet::new")]
+ unmapped: BTreeSet<T>,
}
impl<T: Ord> Default for SelectList<T> {
@@ -28,6 +41,7 @@
Self {
common: BTreeSet::new(),
selects: BTreeMap::new(),
+ unmapped: BTreeSet::new(),
}
}
}
@@ -37,25 +51,22 @@
pub fn insert(&mut self, value: T, configuration: Option<String>) {
match configuration {
None => {
+ self.selects.retain(|_, set| {
+ set.remove(&value);
+ !set.is_empty()
+ });
self.common.insert(value);
}
Some(cfg) => {
- match self.selects.get_mut(&cfg) {
- None => {
- let mut set = BTreeSet::new();
- set.insert(value);
- self.selects.insert(cfg, set);
- }
- Some(set) => {
- set.insert(value);
- }
- };
+ if !self.common.contains(&value) {
+ self.selects.entry(cfg).or_default().insert(value);
+ }
}
- };
+ }
}
// TODO: This should probably be added to the [Select] trait
- pub fn get_iter<'a>(&'a self, config: Option<&String>) -> Option<btree_set::Iter<T>> {
+ pub fn get_iter(&self, config: Option<&String>) -> Option<btree_set::Iter<T>> {
match config {
Some(conf) => self.selects.get(conf).map(|set| set.iter()),
None => Some(self.common.iter()),
@@ -63,8 +74,208 @@
}
/// Determine whether or not the select should be serialized
- pub fn should_skip_serializing(&self) -> bool {
- self.common.is_empty() && self.selects.is_empty()
+ pub fn is_empty(&self) -> bool {
+ self.common.is_empty() && self.selects.is_empty() && self.unmapped.is_empty()
+ }
+
+ /// Maps configuration names by `f`. This function must be injective
+ /// (that is `a != b --> f(a) != f(b)`).
+ pub fn map_configuration_names<F>(self, mut f: F) -> Self
+ where
+ F: FnMut(String) -> String,
+ {
+ Self {
+ common: self.common,
+ selects: self.selects.into_iter().map(|(k, v)| (f(k), v)).collect(),
+ unmapped: self.unmapped,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub struct WithOriginalConfigurations<T> {
+ value: T,
+ original_configurations: Option<BTreeSet<String>>,
+}
+
+impl<T: Ord + Clone> SelectList<T> {
+ /// Generates a new SelectList re-keyed by the given configuration mapping.
+ /// This mapping maps from configurations in the current SelectList to sets of
+ /// configurations in the new SelectList.
+ pub fn remap_configurations(
+ self,
+ mapping: &BTreeMap<String, BTreeSet<String>>,
+ ) -> SelectList<WithOriginalConfigurations<T>> {
+ // Map new configuration -> value -> old configurations.
+ let mut remapped: BTreeMap<String, BTreeMap<T, BTreeSet<String>>> = BTreeMap::new();
+ // Map value -> old configurations.
+ let mut unmapped: BTreeMap<T, BTreeSet<String>> = BTreeMap::new();
+
+ for (original_configuration, values) in self.selects {
+ match mapping.get(&original_configuration) {
+ Some(configurations) => {
+ for configuration in configurations {
+ for value in &values {
+ remapped
+ .entry(configuration.clone())
+ .or_default()
+ .entry(value.clone())
+ .or_default()
+ .insert(original_configuration.clone());
+ }
+ }
+ }
+ None => {
+ for value in values {
+ unmapped
+ .entry(value)
+ .or_default()
+ .insert(original_configuration.clone());
+ }
+ }
+ }
+ }
+
+ SelectList {
+ common: self
+ .common
+ .into_iter()
+ .map(|value| WithOriginalConfigurations {
+ value,
+ original_configurations: None,
+ })
+ .collect(),
+ selects: remapped
+ .into_iter()
+ .map(|(new_configuration, value_to_original_configuration)| {
+ (
+ new_configuration,
+ value_to_original_configuration
+ .into_iter()
+ .map(
+ |(value, original_configurations)| WithOriginalConfigurations {
+ value,
+ original_configurations: Some(original_configurations),
+ },
+ )
+ .collect(),
+ )
+ })
+ .collect(),
+ unmapped: unmapped
+ .into_iter()
+ .map(
+ |(value, original_configurations)| WithOriginalConfigurations {
+ value,
+ original_configurations: Some(original_configurations),
+ },
+ )
+ .collect(),
+ }
+ }
+}
+
+#[derive(Serialize)]
+#[serde(rename = "selects.NO_MATCHING_PLATFORM_TRIPLES")]
+struct NoMatchingPlatformTriples;
+
+// TODO: after removing the remaining tera template usages of SelectList, this
+// inherent method should become the Serialize impl.
+impl<T: Ord> SelectList<T> {
+ pub fn serialize_starlark<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ T: Serialize,
+ S: Serializer,
+ {
+ // Output looks like:
+ //
+ // [
+ // "common...",
+ // ] + select({
+ // "configuration": [
+ // "value...", # cfg(whatever)
+ // ],
+ // "//conditions:default": [],
+ // })
+ //
+ // The common part and select are each omitted if they are empty (except
+ // if the entire thing is empty, in which case we serialize the common
+ // part to get an empty array).
+ //
+ // If there are unmapped entries, we include them like this:
+ //
+ // [
+ // "common...",
+ // ] + selects.with_unmapped({
+ // "configuration": [
+ // "value...", # cfg(whatever)
+ // ],
+ // "//conditions:default": [],
+ // selects.NO_MATCHING_PLATFORM_TRIPLES: [
+ // "value...", # cfg(obscure)
+ // ],
+ // })
+
+ let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
+
+ if !self.common.is_empty() || self.selects.is_empty() && self.unmapped.is_empty() {
+ plus.serialize_field(&MultilineArray(&self.common))?;
+ }
+
+ if !self.selects.is_empty() || !self.unmapped.is_empty() {
+ struct SelectInner<'a, T: Ord>(&'a SelectList<T>);
+
+ impl<'a, T> Serialize for SelectInner<'a, T>
+ where
+ T: Ord + Serialize,
+ {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut map = serializer.serialize_map(Some(MULTILINE))?;
+ for (cfg, value) in &self.0.selects {
+ map.serialize_entry(cfg, &MultilineArray(value))?;
+ }
+ map.serialize_entry("//conditions:default", &[] as &[T])?;
+ if !self.0.unmapped.is_empty() {
+ map.serialize_entry(
+ &NoMatchingPlatformTriples,
+ &MultilineArray(&self.0.unmapped),
+ )?;
+ }
+ map.end()
+ }
+ }
+
+ let function = if self.unmapped.is_empty() {
+ "select"
+ } else {
+ "selects.with_unmapped"
+ };
+
+ plus.serialize_field(&FunctionCall::new(function, [SelectInner(self)]))?;
+ }
+
+ plus.end()
+ }
+}
+
+impl<T> Serialize for WithOriginalConfigurations<T>
+where
+ T: Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ if let Some(original_configurations) = &self.original_configurations {
+ let comment =
+ Vec::from_iter(original_configurations.iter().map(String::as_str)).join(", ");
+ LineComment::new(&self.value, &comment).serialize(serializer)
+ } else {
+ self.value.serialize(serializer)
+ }
}
}
@@ -82,21 +293,43 @@
type Mapped = SelectList<U>;
fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
+ let common: BTreeSet<U> = self.common.into_iter().map(func).collect();
+ let selects: BTreeMap<String, BTreeSet<U>> = self
+ .selects
+ .into_iter()
+ .filter_map(|(key, set)| {
+ let set: BTreeSet<U> = set
+ .into_iter()
+ .map(func)
+ .filter(|value| !common.contains(value))
+ .collect();
+ if set.is_empty() {
+ None
+ } else {
+ Some((key, set))
+ }
+ })
+ .collect();
SelectList {
- common: self.common.into_iter().map(func).collect(),
- selects: self
- .selects
- .into_iter()
- .map(|(key, map)| (key, map.into_iter().map(func).collect()))
- .collect(),
+ common,
+ selects,
+ unmapped: self.unmapped.into_iter().map(func).collect(),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
pub struct SelectDict<T: Ord> {
+ // Invariant: keys in this map are not in any of the inner maps of `selects`.
common: BTreeMap<String, T>,
+ // Invariant: none of the inner maps are empty.
selects: BTreeMap<String, BTreeMap<String, T>>,
+ // Elements that used to be in `selects` before the most recent
+ // `remap_configurations` operation, but whose old configuration did not get
+ // mapped to any new configuration. They could be ignored, but are preserved
+ // here to generate comments that help the user understand what happened.
+ #[serde(skip_serializing_if = "BTreeMap::is_empty", default = "BTreeMap::new")]
+ unmapped: BTreeMap<String, T>,
}
impl<T: Ord> Default for SelectDict<T> {
@@ -104,35 +337,200 @@
Self {
common: BTreeMap::new(),
selects: BTreeMap::new(),
+ unmapped: BTreeMap::new(),
}
}
}
impl<T: Ord> SelectDict<T> {
- // TODO: This should probably be added to the [Select] trait
- pub fn insert(&mut self, value: BTreeMap<String, T>, configuration: Option<String>) {
+ pub fn insert(&mut self, key: String, value: T, configuration: Option<String>) {
match configuration {
None => {
- self.common.extend(value);
+ self.selects.retain(|_, map| {
+ map.remove(&key);
+ !map.is_empty()
+ });
+ self.common.insert(key, value);
}
Some(cfg) => {
- match self.selects.get_mut(&cfg) {
- None => {
- let mut set = BTreeMap::new();
- set.extend(value);
- self.selects.insert(cfg, set);
- }
- Some(set) => {
- set.extend(value);
- }
- };
+ if !self.common.contains_key(&key) {
+ self.selects.entry(cfg).or_default().insert(key, value);
+ }
}
- };
+ }
}
- /// Determine whether or not the select should be serialized
- pub fn should_skip_serializing(&self) -> bool {
- self.common.is_empty() && self.selects.is_empty()
+ pub fn extend(&mut self, entries: BTreeMap<String, T>, configuration: Option<String>) {
+ for (key, value) in entries {
+ self.insert(key, value, configuration.clone());
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.common.is_empty() && self.selects.is_empty() && self.unmapped.is_empty()
+ }
+}
+
+impl<T: Ord + Clone> SelectDict<T> {
+ /// Generates a new SelectDict re-keyed by the given configuration mapping.
+ /// This mapping maps from configurations in the current SelectDict to sets
+ /// of configurations in the new SelectDict.
+ pub fn remap_configurations(
+ self,
+ mapping: &BTreeMap<String, BTreeSet<String>>,
+ ) -> SelectDict<WithOriginalConfigurations<T>> {
+ // Map new configuration -> entry -> old configurations.
+ let mut remapped: BTreeMap<String, BTreeMap<(String, T), BTreeSet<String>>> =
+ BTreeMap::new();
+ // Map entry -> old configurations.
+ let mut unmapped: BTreeMap<(String, T), BTreeSet<String>> = BTreeMap::new();
+
+ for (original_configuration, entries) in self.selects {
+ match mapping.get(&original_configuration) {
+ Some(configurations) => {
+ for configuration in configurations {
+ for (key, value) in &entries {
+ remapped
+ .entry(configuration.clone())
+ .or_default()
+ .entry((key.clone(), value.clone()))
+ .or_default()
+ .insert(original_configuration.clone());
+ }
+ }
+ }
+ None => {
+ for (key, value) in entries {
+ unmapped
+ .entry((key, value))
+ .or_default()
+ .insert(original_configuration.clone());
+ }
+ }
+ }
+ }
+
+ SelectDict {
+ common: self
+ .common
+ .into_iter()
+ .map(|(key, value)| {
+ (
+ key,
+ WithOriginalConfigurations {
+ value,
+ original_configurations: None,
+ },
+ )
+ })
+ .collect(),
+ selects: remapped
+ .into_iter()
+ .map(|(new_configuration, entry_to_original_configuration)| {
+ (
+ new_configuration,
+ entry_to_original_configuration
+ .into_iter()
+ .map(|((key, value), original_configurations)| {
+ (
+ key,
+ WithOriginalConfigurations {
+ value,
+ original_configurations: Some(original_configurations),
+ },
+ )
+ })
+ .collect(),
+ )
+ })
+ .collect(),
+ unmapped: unmapped
+ .into_iter()
+ .map(|((key, value), original_configurations)| {
+ (
+ key,
+ WithOriginalConfigurations {
+ value,
+ original_configurations: Some(original_configurations),
+ },
+ )
+ })
+ .collect(),
+ }
+ }
+}
+
+// TODO: after removing the remaining tera template usages of SelectDict, this
+// inherent method should become the Serialize impl.
+impl<T: Ord + Serialize> SelectDict<T> {
+ pub fn serialize_starlark<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ // If there are no platform-specific entries, we output just an ordinary
+ // dict.
+ //
+ // If there are platform-specific ones, we use the following. Ideally it
+ // could be done as `dicts.add({...}, select({...}))` but bazel_skylib's
+ // dicts.add does not support selects.
+ //
+ // select({
+ // "configuration": {
+ // "common-key": "common-value",
+ // "plat-key": "plat-value", # cfg(whatever)
+ // },
+ // "//conditions:default": {},
+ // })
+ //
+ // If there are unmapped entries, we include them like this:
+ //
+ // selects.with_unmapped({
+ // "configuration": {
+ // "common-key": "common-value",
+ // "plat-key": "plat-value", # cfg(whatever)
+ // },
+ // "//conditions:default": [],
+ // selects.NO_MATCHING_PLATFORM_TRIPLES: {
+ // "unmapped-key": "unmapped-value", # cfg(obscure)
+ // },
+ // })
+
+ if self.selects.is_empty() && self.unmapped.is_empty() {
+ return self.common.serialize(serializer);
+ }
+
+ struct SelectInner<'a, T: Ord>(&'a SelectDict<T>);
+
+ impl<'a, T> Serialize for SelectInner<'a, T>
+ where
+ T: Ord + Serialize,
+ {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut map = serializer.serialize_map(Some(MULTILINE))?;
+ for (cfg, value) in &self.0.selects {
+ let mut combined = BTreeMap::new();
+ combined.extend(&self.0.common);
+ combined.extend(value);
+ map.serialize_entry(cfg, &combined)?;
+ }
+ map.serialize_entry("//conditions:default", &self.0.common)?;
+ if !self.0.unmapped.is_empty() {
+ map.serialize_entry(&NoMatchingPlatformTriples, &self.0.unmapped)?;
+ }
+ map.end()
+ }
+ }
+
+ let function = if self.unmapped.is_empty() {
+ "select"
+ } else {
+ "selects.with_unmapped"
+ };
+
+ FunctionCall::new(function, [SelectInner(self)]).serialize(serializer)
}
}
@@ -146,21 +544,132 @@
}
}
-impl<T: Ord, U: Ord> SelectMap<T, U> for SelectDict<T> {
- type Mapped = SelectDict<U>;
+#[cfg(test)]
+mod test {
+ use super::*;
- fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
- SelectDict {
- common: self
- .common
- .into_iter()
- .map(|(key, val)| (key, func(val)))
- .collect(),
- selects: self
- .selects
- .into_iter()
- .map(|(key, map)| (key, map.into_iter().map(|(k, v)| (k, func(v))).collect()))
- .collect(),
- }
+ use indoc::indoc;
+
+ #[test]
+ fn remap_select_list_configurations() {
+ let mut select_list = SelectList::default();
+ select_list.insert("dep-a".to_owned(), Some("cfg(macos)".to_owned()));
+ select_list.insert("dep-b".to_owned(), Some("cfg(macos)".to_owned()));
+ select_list.insert("dep-d".to_owned(), Some("cfg(macos)".to_owned()));
+ select_list.insert("dep-a".to_owned(), Some("cfg(x86_64)".to_owned()));
+ select_list.insert("dep-c".to_owned(), Some("cfg(x86_64)".to_owned()));
+ select_list.insert("dep-e".to_owned(), Some("cfg(pdp11)".to_owned()));
+ select_list.insert("dep-d".to_owned(), None);
+
+ let mapping = BTreeMap::from([
+ (
+ "cfg(macos)".to_owned(),
+ BTreeSet::from(["x86_64-macos".to_owned(), "aarch64-macos".to_owned()]),
+ ),
+ (
+ "cfg(x86_64)".to_owned(),
+ BTreeSet::from(["x86_64-linux".to_owned(), "x86_64-macos".to_owned()]),
+ ),
+ ]);
+
+ let mut expected = SelectList::default();
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-a".to_owned(),
+ original_configurations: Some(BTreeSet::from([
+ "cfg(macos)".to_owned(),
+ "cfg(x86_64)".to_owned(),
+ ])),
+ },
+ Some("x86_64-macos".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-b".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
+ },
+ Some("x86_64-macos".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-c".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
+ },
+ Some("x86_64-macos".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-a".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
+ },
+ Some("aarch64-macos".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-b".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
+ },
+ Some("aarch64-macos".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-a".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
+ },
+ Some("x86_64-linux".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-c".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
+ },
+ Some("x86_64-linux".to_owned()),
+ );
+ expected.insert(
+ WithOriginalConfigurations {
+ value: "dep-d".to_owned(),
+ original_configurations: None,
+ },
+ None,
+ );
+
+ expected.unmapped.insert(WithOriginalConfigurations {
+ value: "dep-e".to_owned(),
+ original_configurations: Some(BTreeSet::from(["cfg(pdp11)".to_owned()])),
+ });
+
+ let select_list = select_list.remap_configurations(&mapping);
+ assert_eq!(select_list, expected);
+
+ let expected_starlark = indoc! {r#"
+ [
+ "dep-d",
+ ] + selects.with_unmapped({
+ "aarch64-macos": [
+ "dep-a", # cfg(macos)
+ "dep-b", # cfg(macos)
+ ],
+ "x86_64-linux": [
+ "dep-a", # cfg(x86_64)
+ "dep-c", # cfg(x86_64)
+ ],
+ "x86_64-macos": [
+ "dep-a", # cfg(macos), cfg(x86_64)
+ "dep-b", # cfg(macos)
+ "dep-c", # cfg(x86_64)
+ ],
+ "//conditions:default": [],
+ selects.NO_MATCHING_PLATFORM_TRIPLES: [
+ "dep-e", # cfg(pdp11)
+ ],
+ })
+ "#};
+
+ assert_eq!(
+ select_list
+ .serialize_starlark(serde_starlark::Serializer)
+ .unwrap(),
+ expected_starlark,
+ );
}
}
diff --git a/crate_universe/src/utils/starlark/serialize.rs b/crate_universe/src/utils/starlark/serialize.rs
new file mode 100644
index 0000000..4d362c7
--- /dev/null
+++ b/crate_universe/src/utils/starlark/serialize.rs
@@ -0,0 +1,125 @@
+use serde::ser::{SerializeSeq, SerializeStruct, SerializeTupleStruct, Serializer};
+use serde::Serialize;
+use serde_starlark::{FunctionCall, MULTILINE, ONELINE};
+
+use super::{
+ Data, ExportsFiles, Load, Package, RustBinary, RustLibrary, RustProcMacro, SelectList,
+};
+
+// For structs that contain #[serde(flatten)], a quirk of how Serde processes
+// that attribute is that they get serialized as a map, not struct. In Starlark
+// unlike in JSON, maps and structs are differently serialized, so we need to
+// help fill in the function name or else we'd get a Starlark map instead.
+pub fn rust_proc_macro<S>(rule: &RustProcMacro, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ FunctionCall::new("rust_proc_macro", rule).serialize(serializer)
+}
+
+pub fn rust_library<S>(rule: &RustLibrary, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ FunctionCall::new("rust_library", rule).serialize(serializer)
+}
+
+pub fn rust_binary<S>(rule: &RustBinary, serializer: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ FunctionCall::new("rust_binary", rule).serialize(serializer)
+}
+
+// Serialize an array with each element on its own line, even if there is just a
+// single element which serde_starlark would ordinarily place on the same line
+// as the array brackets.
+pub struct MultilineArray<'a, A>(pub &'a A);
+
+impl<'a, A, T> Serialize for MultilineArray<'a, A>
+where
+ &'a A: IntoIterator<Item = &'a T>,
+ T: Serialize + 'a,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut array = serializer.serialize_seq(Some(serde_starlark::MULTILINE))?;
+ for element in self.0 {
+ array.serialize_element(element)?;
+ }
+ array.end()
+ }
+}
+
+// TODO: This can go away after SelectList's derived Serialize impl (used by
+// tera) goes away and `serialize_starlark` becomes its real Serialize impl.
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct SelectListWrapper<'a, T: Ord + Serialize>(
+ #[serde(serialize_with = "SelectList::serialize_starlark")] &'a SelectList<T>,
+);
+
+impl Serialize for Load {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let line = if self.items.len() > 1 {
+ MULTILINE
+ } else {
+ ONELINE
+ };
+ let mut call = serializer.serialize_tuple_struct("load", line)?;
+ call.serialize_field(&self.bzl)?;
+ for item in &self.items {
+ call.serialize_field(item)?;
+ }
+ call.end()
+ }
+}
+
+impl Serialize for Package {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut call = serializer.serialize_struct("package", ONELINE)?;
+ call.serialize_field("default_visibility", &self.default_visibility)?;
+ call.end()
+ }
+}
+
+impl Serialize for ExportsFiles {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut call = serializer.serialize_tuple_struct("exports_files", MULTILINE)?;
+ call.serialize_field(&FunctionCall::new("+", (&self.paths, &self.globs)))?;
+ call.end()
+ }
+}
+
+impl Data {
+ pub fn is_empty(&self) -> bool {
+ self.glob.is_empty() && self.select.is_empty()
+ }
+}
+
+impl Serialize for Data {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
+ if !self.glob.is_empty() {
+ plus.serialize_field(&self.glob)?;
+ }
+ if !self.select.is_empty() || self.glob.is_empty() {
+ plus.serialize_field(&SelectListWrapper(&self.select))?;
+ }
+ plus.end()
+ }
+}
diff --git a/crate_universe/src/utils/starlark/target_compatible_with.rs b/crate_universe/src/utils/starlark/target_compatible_with.rs
new file mode 100644
index 0000000..1c58392
--- /dev/null
+++ b/crate_universe/src/utils/starlark/target_compatible_with.rs
@@ -0,0 +1,92 @@
+use std::collections::BTreeSet;
+
+use serde::ser::{SerializeMap, SerializeTupleStruct, Serializer};
+use serde::{Deserialize, Serialize};
+use serde_starlark::{FunctionCall, MULTILINE};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+pub struct TargetCompatibleWith {
+ target_triples: BTreeSet<String>,
+}
+
+impl TargetCompatibleWith {
+ pub fn new(target_triples: BTreeSet<String>) -> Self {
+ TargetCompatibleWith { target_triples }
+ }
+
+ pub fn serialize_starlark<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ // Output looks like:
+ //
+ // select({
+ // "configuration": [],
+ // "//conditions:default": ["@platforms//:incompatible"],
+ // })
+
+ let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
+
+ struct SelectInner<'a>(&'a BTreeSet<String>);
+
+ impl<'a> Serialize for SelectInner<'a> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut map = serializer.serialize_map(Some(MULTILINE))?;
+ for cfg in self.0 {
+ map.serialize_entry(cfg, &[] as &[String])?;
+ }
+ map.serialize_entry(
+ "//conditions:default",
+ &["@platforms//:incompatible".to_owned()] as &[String],
+ )?;
+ map.end()
+ }
+ }
+
+ plus.serialize_field(&FunctionCall::new(
+ "select",
+ [SelectInner(&self.target_triples)],
+ ))?;
+
+ plus.end()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use indoc::indoc;
+
+ #[test]
+ fn target_compatible_with() {
+ let target_compatible_with = TargetCompatibleWith::new(BTreeSet::from([
+ "@rules_rust//rust/platform:wasm32-unknown-unknown".to_owned(),
+ "@rules_rust//rust/platform:wasm32-wasi".to_owned(),
+ "@rules_rust//rust/platform:x86_64-apple-darwin".to_owned(),
+ "@rules_rust//rust/platform:x86_64-pc-windows-msvc".to_owned(),
+ "@rules_rust//rust/platform:x86_64-unknown-linux-gnu".to_owned(),
+ ]));
+
+ let expected_starlark = indoc! {r#"
+ select({
+ "@rules_rust//rust/platform:wasm32-unknown-unknown": [],
+ "@rules_rust//rust/platform:wasm32-wasi": [],
+ "@rules_rust//rust/platform:x86_64-apple-darwin": [],
+ "@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
+ "@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ })
+ "#};
+
+ assert_eq!(
+ target_compatible_with
+ .serialize_starlark(serde_starlark::Serializer)
+ .unwrap(),
+ expected_starlark,
+ );
+ }
+}