Follow-up to bee907e. Narrow-scope corrections from an independent
second review of the masquerade-wrapper handling.
Behaviour
- filter_out_paths now normalises trailing path separators on both
sides before comparing, so a PATH entry written as
'/usr/lib64/ccache/' (with trailing slash) still matches an
excluded dir derived from PathBuf::parent() (which never has one).
Without this, the defensive "already excluded but returned again"
branch fired and Bear gave up with a bogus "no real compiler past
masquerade dir" warning even when a real compiler was reachable.
New unit test filter_out_paths_matches_across_trailing_separator
protects the case.
- integration-tests/build.rs now also scans
/opt/homebrew/opt/ccache/libexec and /usr/local/opt/ccache/libexec,
the default Homebrew ccache masquerade locations on Apple Silicon
and Intel macOS. Without these, a Homebrew-equipped developer saw
host_has_ccache_masquerade silently stay unset and the recursion
integration test skipped.
Tests
- wrapper_mode_survives_masquerade_wrapper_in_path now also asserts
that the recorded compiler path is absolute, matching the
acceptance criterion's "absolute path to the real compiler"
language.
Docs
- Requirement: struck the "nested compiler invocations ... .bear/
stays at the front of the child's PATH" bullet from the
acceptance criteria. That guarantee belongs to
interception-wrapper-mechanism and is preserved here by not
modifying the child's PATH; the previous wording implied this
requirement owns a guarantee it only protects.
- Requirement: clarified that the PATH-scan path
(compiler_candidates) filters per-file rather than per-directory.
Distro-shipped masquerade dirs contain only symlinks, so the
behaviours coincide in practice; the wording now matches what
the code does.
- Requirement: added a "detection is symlink-based" entry under
non-functional constraints. Masquerade wrappers installed as
shell scripts or hard copies are out of scope and will not be
detected; if a non-symlink masquerade appears in the wild,
extend detection rather than widen the classification helper to
read file contents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to d7305ba. Addresses issues raised in an independent review
of the wrapper-recursion fix.
Behaviour
- resolve_program_path now also filters masquerade wrappers when the
supplied CC/CXX/... value is an absolute path or a relative path
with a directory component. Before, CC=/usr/lib64/ccache/gcc
(or CC=./ccache-dir/gcc) bypassed the filter entirely and Bear
would store the ccache symlink in the wrapper config, recreating
the exact loop the filter is meant to prevent. When the supplied
path IS a masquerade wrapper, resolution falls back to the
basename via PATH past the masquerade dir; if no real compiler is
found, resolution returns None and the caller logs that it is
skipping the compiler.
- resolve_past_masquerade_wrappers now emits a WARN when PATH is
exhausted after excluding one or more masquerade dirs, naming the
compiler and the dir(s). The acceptance criterion about logging
"names the compiler and the detected wrapper" was otherwise only
served by the generic "could not resolve to an executable on PATH"
warning.
Performance
- is_masquerade_wrapper short-circuits with symlink_metadata before
calling canonicalize. Without this, every executable in every
PATH directory paid a real canonicalize syscall during PATH
discovery; masquerade targets are always symlinks, so the
non-symlink path is common and should be cheap.
Tests
- New unit test resolve_program_path_falls_back_past_masquerade_for_absolute_cc
covers CC=/abs/path/to/masquerade/gcc with a real compiler
elsewhere on PATH.
- The integration test wrapper_mode_survives_masquerade_wrapper_in_path
replaces the .contains(".bear") substring check with a
Path::starts_with on the exact wrapper directory -- the loose
substring check could false-positive on any path that happens to
contain ".bear".
Docs
- Requirement wording about detection rewritten: drops the
prescriptive "read_link iteratively, not canonicalize" language
(the /usr/bin/gcc -> gcc-13 rationale applied to compiler
registration, not masquerade detection), and spells out that
canonicalize must NOT be used for the registration path.
- Striked the "nested compiler invocation" Testing scenario from
this requirement -- that guarantee belongs to
interception-wrapper-mechanism and is preserved here by not
modifying the child's PATH. Added a short note pointing there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrapper mode previously stored whatever `which(gcc)` returned as the
"real compiler" for each wrapper. On distributions with a ccache
masquerade in PATH (Fedora/Arch/Gentoo by default), that is the
ccache symlink, so the wrapper's child process was ccache. ccache
then searched PATH for gcc, skipping only symlinks to itself; Bear's
hard-linked wrapper in `.bear/` passed the self-check and was
re-executed, producing an infinite loop.
environment.rs now detects masquerade wrappers at discovery time by
canonicalising candidate paths and checking the target's basename
against a fixed set (ccache, distcc, icecc, colorgcc, buildcache).
The containing directory is stripped from the lookup PATH and
resolution retries, so the wrapper config always names the real
compiler. Both the CC-env and PATH-scan discovery paths are covered.
Other changes in the same fix:
- Requirement reworked around "resolve past masquerade wrappers at
discovery time"; the original CCACHE_COMPILER proposal is
documented as rejected, verified empirically to reproduce the
hang via CCACHE_COMPILER pointing at the ccache symlink.
- Nine new unit tests cover detection, filtering, and the
no-real-compiler fallback.
- New integration test wrapper_mode_survives_masquerade_wrapper_in_path
prepends the masquerade dir to its own child PATH so the
recursion scenario is exercised regardless of host PATH, while
keeping other tests ccache-free.
- build.rs scans well-known masquerade locations (/usr/lib/ccache,
/usr/lib64/ccache, /usr/libexec/ccache), exposes the found dir
via CCACHE_MASQUERADE_DIR, and sets cfg(host_has_ccache_masquerade)
to gate the new test.
- The manual ccache_free_path_and_compiler workaround in the
wrapper-mode tests is gone; the tests now run against the host's
real PATH and also protect this requirement.
- CI: Ubuntu job runs apt-get install ccache so the masquerade dir
exists on every PR. The job PATH is deliberately not modified --
ccache first on PATH would inflate event counts for preload-mode
tests that assert exact compiler-event counts.
Side effect: ccache is bypassed while Bear is observing. That
matches Bear's observe-don't-optimise stance and keeps
compile_commands.json recording the real compiler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the `tests:` frontmatter list in each requirement file with a
`Requirements: <id>` tag placed directly on the protecting test(s). This
gives tests a single source of truth for the link, so renaming or deleting
a test cannot silently orphan a requirement.
- Drop `tests:` from the template and all 11 requirement files.
- Document the new tag convention in requirements/CLAUDE.md and
integration-tests/CLAUDE.md; remove the unused `test_req_<id>_<desc>`
naming rule.
- Tag the existing integration tests (compilation_output, config,
exit_codes, intercept) with the requirements they protect.
- Add `requirements/check-coverage.sh`, which scans implemented
requirements and fails when any has zero tagged tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add requirement pages for the output pipeline features:
- output-append: --append flag and merge behavior
- output-atomic-write: temp file + rename pattern
- output-duplicate-detection: configurable hash-based dedup
- output-path-format: as-is/absolute/relative/canonical strategies
- output-source-directory-filter: include/exclude rules by path
Extend output-json-compilation-database with the full Clang spec
format definition, command field escaping details, and compiler
path handling.
Remove the sequential number from requirement filenames and drop
the redundant id frontmatter field. The filename itself serves as
the unique identifier for cross-references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>