Skip to content

Commit cc8ebb4

Browse files
committed
Fix compilation after ninja format has been run
1 parent 875f7e0 commit cc8ebb4

3 files changed

Lines changed: 85 additions & 107 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ set(BROTLI_HEADERS
168168
"${PROJECT_SOURCE_DIR}/rules/brotli/brotli/types.h")
169169

170170
set(PREPROCESSOR_BIN "${CMAKE_BINARY_DIR}/rule-preprocessor-target/release/rule-preprocessor")
171+
set(PREPROCESSOR_ARTIFACTS "${CMAKE_BINARY_DIR}/rule-preprocessor-target/artifacts.json")
171172
set(RULES_LIB "${CMAKE_BINARY_DIR}/rules-target/release/librules.rlib")
172173

173174
file(GLOB_RECURSE rule_preprocessor_sources
@@ -201,10 +202,12 @@ add_custom_command(
201202
)
202203

203204
add_custom_command(
204-
OUTPUT ${PREPROCESSOR_BIN}
205+
OUTPUT ${PREPROCESSOR_BIN} ${PREPROCESSOR_ARTIFACTS}
205206
COMMAND ${CARGO_ENV_RULE_PREPROCESSOR}
206207
cargo +${RUST_NIGHTLY_VERSION} build --release
208+
--message-format=json-render-diagnostics
207209
--manifest-path "${PROJECT_SOURCE_DIR}/rule-preprocessor/Cargo.toml"
210+
> ${PREPROCESSOR_ARTIFACTS}
208211
DEPENDS ${rule_preprocessor_sources}
209212
${libcc2rs_sources}
210213
"${RUST_STAMP_FILE}"
@@ -257,11 +260,12 @@ foreach(_rule_dir IN LISTS rule_subdirs)
257260
OUTPUT ${_rust_out}
258261
COMMAND ${CMAKE_COMMAND} -E make_directory ${_out_dir}
259262
COMMAND ${CARGO_ENV_RULE_PREPROCESSOR}
263+
RULE_PREPROCESSOR_ARTIFACTS=${PREPROCESSOR_ARTIFACTS}
260264
RUSTUP_TOOLCHAIN=${RUST_NIGHTLY_VERSION}
261265
LD_LIBRARY_PATH=${NIGHTLY_SYSROOT}/lib
262266
DYLD_LIBRARY_PATH=${NIGHTLY_SYSROOT}/lib
263267
${PREPROCESSOR_BIN} ${_out_dir} ${_rule_dir}
264-
DEPENDS ${_rust_srcs} ${PREPROCESSOR_BIN} ${RULES_LIB}
268+
DEPENDS ${_rust_srcs} ${PREPROCESSOR_BIN} ${PREPROCESSOR_ARTIFACTS} ${RULES_LIB}
265269
VERBATIM
266270
)
267271
list(APPEND rust_rules_ir_outputs ${_rust_out})

‎docs/src/rules/preprocessors.md‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,24 @@ compiler's own libraries (`rustc_driver`, `rustc_middle`, ...). It processes the
6161
whole rules tree in one invocation:
6262

6363
```bash
64+
CARGO_TARGET_DIR=<target> cargo +nightly build --release \
65+
--message-format=json-render-diagnostics \
66+
--manifest-path rule-preprocessor/Cargo.toml > <target>/artifacts.json
67+
RULE_PREPROCESSOR_ARTIFACTS=<target>/artifacts.json \
6468
CARGO_TARGET_DIR=<target> cargo +nightly run --release \
6569
--manifest-path rule-preprocessor/Cargo.toml -- <build>/rules [rules-dir]
6670
```
6771

6872
The environment is load-bearing:
6973

70-
- `CARGO_TARGET_DIR` must be set (the tool aborts otherwise): the rlibs of the
71-
rule dependencies (`libcc2rs`, `libc`, `nix`, ...) are looked up in
72-
`$CARGO_TARGET_DIR/<profile>/deps`, which the `cargo run` above populates. The
73-
crate list is hardcoded, so a new dependency in `rules/Cargo.toml` also needs
74-
an entry in `rule-preprocessor/src/semantic.rs`.
75-
76-
> [!WARNING]
77-
>
78-
> Stale rlibs from an earlier build can be picked up silently. Run
79-
> `ninja clean` to fix this.
74+
- `RULE_PREPROCESSOR_ARTIFACTS` must be set (the tool aborts otherwise): it
75+
points to cargo's JSON build output, from which the rlibs of the rule
76+
dependencies (`libcc2rs`, `libc`, `nix`, ...) are taken. The target dir is not
77+
scanned directly because it may hold stale copies of the same crates with
78+
different hashes (e.g., from `cargo clippy` or an older toolchain), and mixing
79+
them makes type checking fail. The crate list is hardcoded, so a new
80+
dependency in `rules/Cargo.toml` also needs an entry in
81+
`rule-preprocessor/src/semantic.rs`.
8082

8183
- The sysroot comes from running `rustc --print=sysroot`, so the `rustc` on
8284
`PATH` must be the same nightly the preprocessor was built with (running
@@ -86,10 +88,11 @@ The environment is load-bearing:
8688

8789
CMake drives all of this via the `preprocess-rust-rules` target: it first builds
8890
the `rules` crate with the stable toolchain (which also regenerates
89-
`rules/src/modules.rs`), then runs the preprocessor with
90-
`CARGO_TARGET_DIR=<build>/rule-preprocessor-target`. That initial `cargo build`
91-
of the `rules` crate is what actually gates the build on rule bodies
92-
type-checking (see below). The preprocessor works in two phases.
91+
`rules/src/modules.rs`), then builds the preprocessor in
92+
`<build>/rule-preprocessor-target`, saving cargo's output to `artifacts.json`
93+
there, and runs it. That initial `cargo build` of the `rules` crate is what
94+
actually gates the build on rule bodies type-checking (see below). The
95+
preprocessor works in two phases.
9396

9497
**Phase 1, syntactic.** Each `tgt_*.rs` file is parsed with rust-analyzer's
9598
parser, and functions whose `#[cfg]` does not match the host are dropped. Every

‎rule-preprocessor/src/semantic.rs‎

Lines changed: 62 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ fn write_crate_root(ir: &RulesIR, out_dir: &Path) -> PathBuf {
4545

4646
fn build_rustc_args(crate_root: &Path) -> Vec<String> {
4747
let sysroot = get_sysroot();
48-
let build_dir = find_build_dir();
4948

5049
let mut args = vec![
5150
"rustc".to_string(),
@@ -59,19 +58,19 @@ fn build_rustc_args(crate_root: &Path) -> Vec<String> {
5958
format!("--sysroot={}", sysroot.display()),
6059
];
6160

62-
// Add -L for all out/ directories within the build dir so transitive deps
63-
// are discoverable. Also support legacy flat deps/ layout.
64-
let legacy_deps = build_dir
65-
.parent()
66-
.map(|p| p.join("deps"))
67-
.filter(|p| p.is_dir());
68-
if let Some(ref deps) = legacy_deps {
69-
args.push("-L".to_string());
70-
args.push(format!("dependency={}", deps.display()));
71-
}
72-
for out_dir in find_all_out_dirs(&build_dir) {
61+
let artifacts = read_cargo_artifacts();
62+
63+
// Make transitive deps discoverable, as cargo does.
64+
let mut lib_dirs: Vec<&Path> = artifacts
65+
.values()
66+
.flatten()
67+
.filter_map(|lib| lib.parent())
68+
.collect();
69+
lib_dirs.sort();
70+
lib_dirs.dedup();
71+
for dir in lib_dirs {
7372
args.push("-L".to_string());
74-
args.push(format!("dependency={}", out_dir.display()));
73+
args.push(format!("dependency={}", dir.display()));
7574
}
7675

7776
for dep in &[
@@ -84,15 +83,13 @@ fn build_rustc_args(crate_root: &Path) -> Vec<String> {
8483
"jiff",
8584
"xattr",
8685
] {
87-
if let Some(lib) = find_artifact(&build_dir, dep) {
88-
args.push("--extern".to_string());
89-
args.push(format!("{}={}", dep, lib.display()));
90-
} else if let Some(ref deps) = legacy_deps
91-
&& let Some(lib) = find_artifact_in_dir(deps, dep)
92-
{
93-
args.push("--extern".to_string());
94-
args.push(format!("{}={}", dep, lib.display()));
95-
}
86+
let lib = match artifacts.get(*dep).map(Vec::as_slice) {
87+
Some([lib]) => lib,
88+
Some([]) | None => panic!("no artifact for crate `{dep}` in cargo's build output"),
89+
Some(libs) => panic!("multiple artifacts for crate `{dep}`: {libs:?}"),
90+
};
91+
args.push("--extern".to_string());
92+
args.push(format!("{}={}", dep, lib.display()));
9693
}
9794

9895
args
@@ -106,78 +103,52 @@ fn get_sysroot() -> PathBuf {
106103
PathBuf::from(String::from_utf8(output.stdout).unwrap().trim())
107104
}
108105

109-
fn find_build_dir() -> PathBuf {
110-
let target_dir = std::env::var("CARGO_TARGET_DIR").expect("CARGO_TARGET_DIR must be set");
111-
let profile = if cfg!(debug_assertions) {
112-
"debug"
113-
} else {
114-
"release"
115-
};
116-
PathBuf::from(target_dir).join(profile).join("build")
117-
}
118-
119-
/// Collect all `<pkg>/<hash>/out/` directories within the build dir.
120-
fn find_all_out_dirs(build_dir: &Path) -> Vec<PathBuf> {
121-
let mut out_dirs = Vec::new();
122-
if let Ok(pkgs) = std::fs::read_dir(build_dir) {
123-
for pkg in pkgs.flatten() {
124-
if !pkg.file_type().map(|t| t.is_dir()).unwrap_or(false) {
125-
continue;
126-
}
127-
if let Ok(hashes) = std::fs::read_dir(pkg.path()) {
128-
for hash_dir in hashes.flatten() {
129-
let out = hash_dir.path().join("out");
130-
if out.is_dir() {
131-
out_dirs.push(out);
132-
}
133-
}
134-
}
135-
}
136-
}
137-
out_dirs
138-
}
139-
140-
/// Find an artifact in the new Cargo build layout: build/<pkg>/<hash>/out/
141-
fn find_artifact(build_dir: &Path, crate_name: &str) -> Option<PathBuf> {
142-
// Package directory name uses hyphens where crate name uses underscores
143-
let pkg_name = crate_name.replace('_', "-");
144-
let pkg_dir = build_dir.join(&pkg_name);
145-
if let Ok(entries) = std::fs::read_dir(&pkg_dir) {
146-
for entry in entries.flatten() {
147-
let out_dir = entry.path().join("out");
148-
if out_dir.is_dir()
149-
&& let Some(path) = find_artifact_in_dir(&out_dir, crate_name)
150-
{
151-
return Some(path);
152-
}
106+
/// Read the output of `cargo build --message-format=json` for this crate and
107+
/// map each library crate name to the artifacts that build produced. The
108+
/// target dir may also hold stale artifacts of the same crates (e.g., from an
109+
/// older toolchain or from `cargo clippy`), so it cannot simply be scanned.
110+
fn read_cargo_artifacts() -> HashMap<String, Vec<PathBuf>> {
111+
let path = std::env::var("RULE_PREPROCESSOR_ARTIFACTS")
112+
.expect("RULE_PREPROCESSOR_ARTIFACTS must be set");
113+
let contents =
114+
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {path}: {e}"));
115+
116+
let mut artifacts: HashMap<String, Vec<PathBuf>> = HashMap::new();
117+
for line in contents.lines() {
118+
let Ok(msg) = serde_json::from_str::<serde_json::Value>(line) else {
119+
continue;
120+
};
121+
if msg["reason"] != "compiler-artifact" {
122+
continue;
153123
}
154-
}
155-
None
156-
}
157-
158-
/// Find an artifact by crate name in a specific directory.
159-
fn find_artifact_in_dir(dir: &Path, crate_name: &str) -> Option<PathBuf> {
160-
let prefixes = [format!("{}-", crate_name), format!("lib{}-", crate_name)];
161-
let mut fallback = None;
162-
if let Ok(entries) = std::fs::read_dir(dir) {
163-
for entry in entries.flatten() {
164-
let name = entry.file_name().to_string_lossy().to_string();
165-
if !prefixes.iter().any(|p| name.starts_with(p)) {
166-
continue;
167-
}
168-
if name.ends_with(".rmeta")
169-
|| name.ends_with(".so")
170-
|| name.ends_with(".dylib")
171-
|| name.ends_with(".dll")
172-
{
173-
return Some(entry.path());
174-
}
175-
if name.ends_with(".rlib") {
176-
fallback = Some(entry.path());
177-
}
124+
let Some(name) = msg["target"]["name"].as_str() else {
125+
continue;
126+
};
127+
let Some(filenames) = msg["filenames"].as_array() else {
128+
continue;
129+
};
130+
let filenames: Vec<PathBuf> = filenames
131+
.iter()
132+
.filter_map(|f| f.as_str())
133+
.map(PathBuf::from)
134+
.collect();
135+
let find = |ext: &str| {
136+
filenames
137+
.iter()
138+
.find(|f| f.extension().is_some_and(|e| e == ext))
139+
};
140+
let Some(lib) = find("rmeta")
141+
.or_else(|| find("rlib"))
142+
.or_else(|| find(std::env::consts::DLL_EXTENSION))
143+
else {
144+
continue;
145+
};
146+
let libs = artifacts.entry(name.to_string()).or_default();
147+
if !libs.contains(lib) {
148+
libs.push(lib.clone());
178149
}
179150
}
180-
fallback
151+
artifacts
181152
}
182153

183154
struct FnDecl<'tcx> {

0 commit comments

Comments
 (0)