Improve build and test experience for Make builds#14883
Conversation
Summary:
Local Make builds silently reuse object files even when build parameters
(DEBUG_LEVEL, sanitizers, ASSERT_STATUS_CHECKED, RTTI, etc.) change, since
all object files share the same paths. This leads to confusing linker
errors, sanitizer false negatives, ODR violations, and "phantom" bugs that
are easy to miss -- a pitfall for humans and especially for AI agents driving
builds non-interactively.
This change improves the Make experience on three fronts:
1. Build-parameter change detection in the Makefile. After flags are fully
resolved, we hash the effective compile/link inputs
(CC|CXX|CFLAGS|CXXFLAGS|LDFLAGS|EXEC_LDFLAGS) into a per-OBJ_DIR
.build_signature stamp and compare against the previous build:
- Default: stop with a clear error when parameters changed. This is
optimized for the expert who might not intend a full rebuild.
- AUTO_CLEAN=1: automatically run clean-rocks and proceed.
- ALLOW_BUILD_PARAMETER_CHANGE=1: skip the check (e.g. intentionally
mixing DEBUG_LEVEL=1 and DEBUG_LEVEL=2 objects).
The check is skipped for dry runs (-n) and for non-building / self-cleaning
targets (clean*, format, check-*, tags, gen-pc, check-progress, watch-log,
release, coverage, the asan_*/ubsan_* variants, etc.). make_config.mk is
removed only by the top-level `clean` target, not by clean-rocks, so the
auto-clean does not delete the make_config.mk already included by the
running make. The signature is removed last in clean-rocks so an
interrupted clean keeps change detection meaningful.
2. New build_tools/rockstest.sh and build_tools/rocksptest.sh helpers that
build and run unit tests in one step. They set AUTO_CLEAN=1 and build with
-j<NCORES> (computed like the Makefile). rocksptest.sh runs one or more
binaries under the checked-in gtest-parallel (sharing one parallel worker
pool); rockstest.sh runs a single binary directly for a small number of
cases. Both reject a leading-option first argument; `rockstest.sh install`
writes ~/bin/rockstest and ~/bin/rocksptest shims that defer to the in-tree
scripts (with a friendly message when run outside a source root). This is a
big win for AI-agent workflows: one command instead of a separate `make`
then test-run invocation, removing the overhead of monitoring two long
commands, while avoiding serial builds and serial test execution.
3. CLAUDE.md guidance updated to recommend AUTO_CLEAN=1 for manual make
invocations and the rocks(p)test.sh helpers for running tests
Test Plan:
Manual verification of the Makefile change detection:
- Confirmed the resolved-flags hash is deterministic across runs for the same
goal, and differs between configs (e.g. default/shared vs static_lib, and
with/without ASSERT_STATUS_CHECKED).
- Unchanged parameters: rebuild does not error.
- Changed parameters: errors by default; AUTO_CLEAN=1 runs clean-rocks and
proceeds; ALLOW_BUILD_PARAMETER_CHANGE=1 bypasses the check.
- Dry runs (make -n) and exempt targets (e.g. `make check-progress`,
list_all_tests, gen-pc) do not trip the check and leave the stamp untouched.
- Reproduced the ASSERT_STATUS_CHECKED=1 auto-clean path end to end and
confirmed it no longer fails with "No rule to make target 'make_config.mk'";
verified `make clean` removes make_config.mk while `clean-rocks` does not,
and that the signature is deleted as the final clean-rocks step.
Manual verification of the helper scripts:
- shellcheck-clean and `bash -n` pass for both scripts.
- Usage/guard behavior: missing argument and leading-option first argument are
rejected with a clear message.
- `rockstest.sh install` (validated against a temp HOME) generates both shims;
running a shim outside a source root prints
"(Not in a rocksdb source root directory?)" and exits non-zero.
- rocksptest.sh argument splitting verified for single binary, multiple
binaries, and binaries followed by gtest-parallel/test args.
- .gitignore patterns verified with `git check-ignore` for .build_signature
(root and jl/jls) and build_tools/__pycache__/.
✅ clang-tidy: No findings on changed linesCompleted in 51.4s. |
|
@pdillinger has imported this pull request. If you are a Meta employee, you can view this in D109713512. |
|
@pdillinger has imported this pull request. If you are a Meta employee, you can view this in D109713512. |
🟡 Codex Code ReviewAuto-triggered after CI passed — reviewing commit 8ee16d9 ❌ Codex review failed before producing findings. ℹ️ About this responseGenerated by Codex CLI. Limitations:
Commands:
|
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 8ee16d9 SummaryWell-designed build infrastructure improvement that addresses a real pain point (stale object files from mixed build modes). The approach of hashing resolved compile/link flags is sound and the implementation is thorough, with good handling of edge cases (dry runs, sub-makes, clean targets, Java OBJ_DIRs). The helper scripts are clean and well-guarded. High-severity findings (1):
Full review (click to expand)Findings🔴 HIGHH1. Shell injection / breakage via single quotes in build flags --
|
| Context | Affected? | Assessment |
|---|---|---|
| CI (pr-jobs.yml) | Yes | Safe. Fresh checkout has no .build_signature. |
make check |
Yes | Correct. Sub-makes skip via MAKELEVEL guard. |
release/asan_check/ubsan_check |
No | Correctly in NONBUILD_GOALS (clean before build). |
Java builds (jl/jls) |
Yes | Per-OBJ_DIR signatures handle correctly. |
make -n (dry run) |
No | Correctly detected via MAKEFLAGS parsing. |
| macOS (old Make 3.81) | Pre-existing | .SHELLSTATUS already used at line 271. |
Positive Observations
- Thorough edge case handling: MAKELEVEL guard, dry-run detection, per-OBJ_DIR signatures.
- Clean-rocks ordering: Removing
.build_signatureLAST preserves detection across interrupted cleans. make_config.mkseparation: Prevents AUTO_CLEAN from breaking the running Makefile -- subtle but important.- Script quality: Well-guarded with
set -euo pipefail, argument validation, and consistent NCORES computation.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/code_review.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
|
@pdillinger merged this pull request in f6fabdb. |
Summary:
Local Make builds silently reuse object files even when build parameters (DEBUG_LEVEL, sanitizers, ASSERT_STATUS_CHECKED, RTTI, etc.) change, since all object files share the same paths. This leads to confusing linker errors, sanitizer false negatives, ODR violations, and "phantom" bugs that are easy to miss -- a pitfall for humans and especially for AI agents driving builds non-interactively.
This change improves the Make experience on three fronts:
Build-parameter change detection in the Makefile. After flags are fully resolved, we hash the effective compile/link inputs (CC|CXX|CFLAGS|CXXFLAGS|LDFLAGS|EXEC_LDFLAGS) into a per-OBJ_DIR .build_signature stamp and compare against the previous build:
The check is skipped for dry runs (-n) and for non-building / self-cleaning targets (clean*, format, check-, tags, gen-pc, check-progress, watch-log, release, coverage, the asan_/ubsan_* variants, etc.). make_config.mk is removed only by the top-level
cleantarget, not by clean-rocks, so the auto-clean does not delete the make_config.mk already included by the running make. The signature is removed last in clean-rocks so an interrupted clean keeps change detection meaningful.New build_tools/rockstest.sh and build_tools/rocksptest.sh helpers that build and run unit tests in one step. They set AUTO_CLEAN=1 and build with -j (computed like the Makefile). rocksptest.sh runs one or more binaries under the checked-in gtest-parallel (sharing one parallel worker pool); rockstest.sh runs a single binary directly for a small number of cases. Both reject a leading-option first argument;
rockstest.sh installwrites ~/bin/rockstest and ~/bin/rocksptest shims that defer to the in-tree scripts (with a friendly message when run outside a source root). This is a big win for AI-agent workflows: one command instead of a separatemakethen test-run invocation, removing the overhead of monitoring two long commands, while avoiding serial builds and serial test execution.CLAUDE.md guidance updated to recommend AUTO_CLEAN=1 for manual make invocations and the rocks(p)test.sh helpers for running tests
Although there might be drive to consolidate around the BUCK build for internal use, I'll note that last I checked it was around ~20 minutes to run all the unit tests under buck and ~2 minutes under
make check.Bonus: the first revision of this had copyright/license mistakes, and this change corrects similar errors elsewhere, with CLAUDE.md advice updated.
Test Plan:
Manual verification of the Makefile change detection:
make check-progress, list_all_tests, gen-pc) do not trip the check and leave the stamp untouched.make cleanremoves make_config.mk whileclean-rocksdoes not, and that the signature is deleted as the final clean-rocks step.Manual verification of the helper scripts:
bash -npass for both scripts.rockstest.sh install(validated against a temp HOME) generates both shims; running a shim outside a source root prints "(Not in a rocksdb source root directory?)" and exits non-zero.git check-ignorefor .build_signature (root and jl/jls) and build_tools/pycache/.