ci, iwyu: skip subtrees in compilation database #36252

pull ryanofsky wants to merge 1 commits into bitcoin:master from ryanofsky:pr/iwyusub changing 1 files +11 −0
  1. ryanofsky commented at 3:45 PM on September 14, 2026: contributor

    Problem: The IWYU CI job analyzes src/ipc/libmultiprocess/ even though its results are thrown away. Every run prints IWYU suggestions for 14 libmultiprocess files in the non-enforced pass, applies them, and then reverts them with the git restore from #36112. The suggestions also disagree with what libmultiprocess's own IWYU CI reports (it runs with -Xiwyu --error and passes), because the two jobs use different IWYU versions and mapping files. libmultiprocess is the only subtree in this situation; leveldb, crc32c, minisketch and secp256k1 are already excluded from compile_commands.json with EXPORT_COMPILE_COMMANDS OFF. #35361 asked for subtrees not to be linted at all.

    Solution: Filter SHARED_EXCLUDED_SUBTREES (from test/lint/lint_ignore_dirs.py) out of compile_commands.json at the start of the IWYU section of ci/test/03_test_script.sh, so subtree sources are neither analyzed nor edited. This is done in the CI script rather than in cmake so that only IWYU is affected: clang-tidy keeps running on libmultiprocess, and no list of subtree targets has to be maintained in cmake/libmultiprocess.cmake (that approach broke -DBUILD_TESTS=OFF in #35418 and was reverted in #35523). One-file change to the CI script.

    Testing note: You can confirm this works by checking at IWYU CI logs and confirming that output like /home/runner/work/_temp/src/ipc/libmultiprocess/src/mp/gen.cpp should add these lines: no longer appears.

  2. ci, iwyu: skip subtrees in compilation database
    Drop SHARED_EXCLUDED_SUBTREES from compile_commands.json before running IWYU,
    so subtree sources are no longer analyzed. Previously the non-enforced IWYU
    pass still analyzed src/ipc/libmultiprocess/, the only subtree not already
    excluded from the compilation database with EXPORT_COMPILE_COMMANDS OFF, and
    its suggestions were reverted by the git restore added in #36112 without
    anyone acting on them.
    
    Filtering in the CI script rather than in cmake keeps clang-tidy running on
    the subtree and avoids maintaining a list of subtree targets in
    cmake/libmultiprocess.cmake, which broke -DBUILD_TESTS=OFF in #35418.
    
    Follows up on #35361, which asked not to run linters over subtrees.
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    01e3eca2d8
  3. DrahtBot commented at 3:46 PM on September 14, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36252.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK David-Uka, hebasto

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #31349 (ci: detect outbound internet traffic generated while running tests by vasild)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. hebasto commented at 3:57 PM on September 14, 2026: member

    Why not #35418#pullrequestreview-4428862711?

  5. ryanofsky commented at 5:06 PM on September 14, 2026: contributor

    Why not #35418 (review)?

    I think excluding all subtrees from IWYU is better than individually setting in CMAKE_EXPORT_COMPILE_COMMANDS inside individual subtrees because:

    • It directly addresses the fact that IWYU's instability makes it inappropriate to run on subtrees. IWYU's output changes depending on compiler version, standard libary version, IWYU version, and local mappings so it's not realistic to expect subtrees will be able to pass IWYU checks in bitcoin core, and there's not much reason to care if they do, since benefits of IWYU are faster compile times and more consistent code style, not anything safety related.

    • This approach works for all subtrees, instead of requiring every individual subtree to hardcode cmake options.

    • This approach only disables IWYU, and does not disable all other tools or checks that use the compilation database. I don't think that turning off clang-tidy, bitcoin-tidy, and other tools for all subtrees is a good idea just to prevent IWYU noise, when IWYU noise can be prevented by just running it on the right files.

  6. in ci/test/03_test_script.sh:240 in 01e3eca2d8
     233 | @@ -234,6 +234,13 @@ if [ "${RUN_TIDY}" = "true" ]; then
     234 |  fi
     235 |  
     236 |  if [[ "${RUN_IWYU}" == true ]]; then
     237 | +  # Skip subtrees. They are maintained upstream, and IWYU output for them from
     238 | +  # this job's IWYU version and mapping files can disagree with upstream (e.g.
     239 | +  # libmultiprocess runs IWYU in its own CI and passes there).
     240 | +  SUBTREES=$(python3 -c 'import runpy, sys; print("|".join(runpy.run_path(sys.argv[1])["SHARED_EXCLUDED_SUBTREES"]))' "${BASE_ROOT_DIR}/test/lint/lint_ignore_dirs.py")
    


    maflcko commented at 3:44 PM on September 17, 2026:

    Seems fine, but All of those python3 -c '...' inside bash are fine, but seem a bit hacky.

    At some point we can use vanilla Python and drop the jq dep. Something like:

    <details><summary>completely untested draft diff</summary>

     ci/test/00_setup_env_native_iwyu.sh |   2 +-
     ci/test/03_test_script.sh           |  44 +------------
     ci/test/run-iwyu.py                 | 125 ++++++++++++++++++++++++++++++++++++
     3 files changed, 127 insertions(+), 44 deletions(-)
    
    diff --git a/ci/test/00_setup_env_native_iwyu.sh b/ci/test/00_setup_env_native_iwyu.sh
    index 20240f6..9ed2a09 100755
    --- a/ci/test/00_setup_env_native_iwyu.sh
    +++ b/ci/test/00_setup_env_native_iwyu.sh
    @@ -12,3 +12,3 @@ export IWYU_LLVM_V="23"
     export APT_LLVM_V="${IWYU_LLVM_V}"
    -export PACKAGES="clang-${IWYU_LLVM_V} clang-format-${IWYU_LLVM_V} libclang-${IWYU_LLVM_V}-dev llvm-${IWYU_LLVM_V}-dev jq libboost-dev libzmq3-dev systemtap-sdt-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev libsqlite3-dev libcapnp-dev capnproto"
    +export PACKAGES="clang-${IWYU_LLVM_V} clang-format-${IWYU_LLVM_V} libclang-${IWYU_LLVM_V}-dev llvm-${IWYU_LLVM_V}-dev libboost-dev libzmq3-dev systemtap-sdt-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev libsqlite3-dev libcapnp-dev capnproto"
     export NO_DEPENDS=1
    diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh
    index 5793656..187834b 100755
    --- a/ci/test/03_test_script.sh
    +++ b/ci/test/03_test_script.sh
    @@ -236,45 +236,3 @@ fi
     if [[ "${RUN_IWYU}" == true ]]; then
    -  # TODO: Consider enforcing IWYU across the entire codebase.
    -  FILES_WITH_ENFORCED_IWYU='/src/((bench|common|consensus|crypto|index|init|kernel|primitives|rpc|script|univalue/(lib|test)|util|zmq)/.*|node/(blockstorage|interfaces|miner|mining_args|utxo_snapshot)|test/fuzz/(kitchen_sink|minisketch|parse_univalue)|clientversion|core_io|rest|signet|init)\.cpp'
    -  jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns)))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_errors.json"
    -  jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns) | not))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_warnings.json"
    -
    -  cd "${BASE_ROOT_DIR}"
    -
    -  run_iwyu() {
    -    mv "${BASE_BUILD_DIR}/$1" "${BASE_BUILD_DIR}/compile_commands.json"
    -    {
    -      python3 /include-what-you-use/mapgen/iwyu-mapgen-clang-intrin.py --lang imp "$("clang-${IWYU_LLVM_V}" -print-resource-dir)/include" > "${BASE_BUILD_DIR}/clang.intrinsics.imp"
    -      python3 /include-what-you-use/iwyu_tool.py \
    -             -p "${BASE_BUILD_DIR}" "${MAKEJOBS}" -- \
    -             -Xiwyu --cxx17ns \
    -             -Xiwyu --mapping_file="${BASE_ROOT_DIR}/contrib/devtools/iwyu/bitcoin.core.imp" \
    -             -Xiwyu --mapping_file="${BASE_BUILD_DIR}/clang.intrinsics.imp" \
    -             -Xiwyu --max_line_length=160 \
    -             -Xiwyu --check_also='*/common/types\.h' \
    -             -Xiwyu --check_also='*/consensus/*\.h' \
    -             -Xiwyu --check_also='*/interfaces/*\.h' \
    -             -Xiwyu --check_also='*/primitives/transaction_identifier\.h' \
    -             -Xiwyu --check_also='*/rpc/protocol\.h' \
    -             2>&1 || true
    -    } | tee /tmp/iwyu_ci.out
    -    python3 "/include-what-you-use/fix_includes.py" --nosafe_headers < /tmp/iwyu_ci.out
    -    python3 -c '
    -import runpy
    -import subprocess
    -
    -subtrees = runpy.run_path("test/lint/lint_ignore_dirs.py")["SHARED_EXCLUDED_SUBTREES"]
    -subprocess.run(["git", "restore", "--", *subtrees], check=True)
    -'
    -    git diff -U1 | ./contrib/devtools/clang-format-diff.py -binary="clang-format-${IWYU_LLVM_V}" -p1 -i -v
    -  }
    -
    -  run_iwyu "compile_commands_iwyu_errors.json"
    -  if ! ( git --no-pager diff --exit-code ); then
    -    echo "^^^ ⚠️ Failure generated from IWYU"
    -    false
    -  fi
    -
    -  run_iwyu "compile_commands_iwyu_warnings.json"
    -  git --no-pager diff
    +  "${BASE_ROOT_DIR}/ci/test/run-iwyu.py"
     fi
    diff --git a/ci/test/run-iwyu.py b/ci/test/run-iwyu.py
    new file mode 100755
    index 0000000..f62f789
    --- /dev/null
    +++ b/ci/test/run-iwyu.py
    @@ -0,0 +1,125 @@
    +#!/usr/bin/env python3
    +# Copyright (c) The Bitcoin Core developers
    +# Distributed under the MIT software license, see the accompanying
    +# file COPYING or https://opensource.org/license/mit/.
    +
    +import json
    +import os
    +import re
    +import runpy
    +import shlex
    +import shutil
    +import subprocess
    +import sys
    +from pathlib import Path
    +
    +
    +FILES_WITH_ENFORCED_IWYU = re.compile(
    +    r"/src/((bench|common|consensus|crypto|index|init|kernel|primitives|rpc|script|univalue/(lib|test)|util|zmq)/.*"
    +    r"|node/(blockstorage|interfaces|miner|mining_args|utxo_snapshot)"
    +    r"|test/fuzz/(kitchen_sink|minisketch|parse_univalue)"
    +    r"|clientversion|core_io|rest|signet|init)\.cpp"
    +)
    +
    +
    +def run(cmd, **kwargs):
    +    print("+ " + shlex.join(cmd), flush=True)
    +    kwargs.setdefault("check", True)
    +    try:
    +        return subprocess.run(cmd, **kwargs)
    +    except Exception as error:
    +        sys.exit(str(error))
    +
    +
    +def run_iwyu(root: Path, build_dir: Path, compile_commands: str, makejobs: str, llvm_version: str):
    +    shutil.move(build_dir / compile_commands, build_dir / "compile_commands.json")
    +    clang_resource_dir = run(
    +        [f"clang-{llvm_version}", "-print-resource-dir"],
    +        stdout=subprocess.PIPE,
    +        text=True,
    +    ).stdout.strip()
    +    run(
    +        [
    +            "python3",
    +            "/include-what-you-use/mapgen/iwyu-mapgen-clang-intrin.py",
    +            "--lang",
    +            "imp",
    +            f"{clang_resource_dir}/include",
    +        ],
    +        stdout=(build_dir / "clang.intrinsics.imp").open("w"),
    +    )
    +
    +    iwyu_output = run(
    +        [
    +            "python3",
    +            "/include-what-you-use/iwyu_tool.py",
    +            "-p",
    +            str(build_dir),
    +            makejobs,
    +            "--",
    +            "-Xiwyu",
    +            "--cxx17ns",
    +            "-Xiwyu",
    +            f"--mapping_file={root / 'contrib/devtools/iwyu/bitcoin.core.imp'}",
    +            "-Xiwyu",
    +            f"--mapping_file={build_dir / 'clang.intrinsics.imp'}",
    +            "-Xiwyu",
    +            "--max_line_length=160",
    +            "-Xiwyu",
    +            r"--check_also=*/common/types\.h",
    +            "-Xiwyu",
    +            r"--check_also=*/consensus/*\.h",
    +            "-Xiwyu",
    +            r"--check_also=*/interfaces/*\.h",
    +            "-Xiwyu",
    +            r"--check_also=*/primitives/transaction_identifier\.h",
    +            "-Xiwyu",
    +            r"--check_also=*/rpc/protocol\.h",
    +        ],
    +        stdout=subprocess.PIPE,
    +        stderr=subprocess.STDOUT,
    +        text=True,
    +        check=False,
    +    )
    +    Path("/tmp/iwyu_ci.out").write_text(iwyu_output.stdout)
    +    print(iwyu_output.stdout, end="")
    +    with Path("/tmp/iwyu_ci.out").open() as output:
    +        run(
    +            ["python3", "/include-what-you-use/fix_includes.py", "--nosafe_headers"],
    +            stdin=output,
    +        )
    +
    +    excluded_subtrees = runpy.run_path(str(root / "test/lint/lint_ignore_dirs.py"))["SHARED_EXCLUDED_SUBTREES"]
    +    run(["git", "restore", "--", *excluded_subtrees])
    +    diff = run(["git", "diff", "-U1"], stdout=subprocess.PIPE)
    +    run(
    +        ["./contrib/devtools/clang-format-diff.py", f"-binary=clang-format-{llvm_version}", "-p1", "-i", "-v"],
    +        input=diff.stdout,
    +    )
    +
    +
    +def main():
    +    root = Path(os.environ["BASE_ROOT_DIR"])
    +    os.chdir(root)
    +    build_dir = Path(os.environ["BASE_BUILD_DIR"])
    +    all_compile_commands = json.loads((build_dir / "compile_commands.json").read_text())
    +    (build_dir / "compile_commands_iwyu_errors.json").write_text(
    +        json.dumps([entry for entry in all_compile_commands if FILES_WITH_ENFORCED_IWYU.search(entry["file"])])
    +    )
    +    (build_dir / "compile_commands_iwyu_warnings.json").write_text(
    +        json.dumps([entry for entry in all_compile_commands if not FILES_WITH_ENFORCED_IWYU.search(entry["file"])])
    +    )
    +
    +    makejobs = os.environ["MAKEJOBS"]
    +    llvm_version = os.environ["IWYU_LLVM_V"]
    +    run_iwyu(root, build_dir, "compile_commands_iwyu_errors.json", makejobs, llvm_version)
    +    if run(["git", "diff", "--exit-code"], check=False).returncode:
    +        print("^^^ ⚠️ Failure generated from IWYU")
    +        raise SystemExit(1)
    +
    +    run_iwyu(root, build_dir, "compile_commands_iwyu_warnings.json", makejobs, llvm_version)
    +    run(["git", "--no-pager", "diff"])
    +
    +
    +if __name__ == "__main__":
    +    main()
    

    </details>

  7. maflcko approved
  8. maflcko commented at 3:45 PM on September 17, 2026: member

    lgtm. Seems fine for a minimal speed-up and minimally smaller ci log output?

  9. David-Uka commented at 2:07 PM on September 23, 2026: none

    Code-review ACK 01e3eca2d8ee755eed4488a44093759f0eddcb34

    Filtering SHARED_EXCLUDED_SUBTREES out of compile_commands.json before the IWYU run drops the libmultiprocess sources — including the generated test/mp/test/foo.capnp.c++ that makes IWYU (llvm-23) abort on a UsingShadowDecl, the crash surfaced in #36235. The follow-up git restore is still correctly kept for subtree headers associated with non-subtree TUs. I didn't run the IWYU job locally; reasoning from the diff plus @hebasto's repro and green CI.

  10. hebasto approved
  11. hebasto commented at 2:08 PM on September 25, 2026: member

    ACK 01e3eca2d8ee755eed4488a44093759f0eddcb34.

  12. in ci/test/03_test_script.sh:242 in 01e3eca2d8
     233 | @@ -234,6 +234,13 @@ if [ "${RUN_TIDY}" = "true" ]; then
     234 |  fi
     235 |  
     236 |  if [[ "${RUN_IWYU}" == true ]]; then
     237 | +  # Skip subtrees. They are maintained upstream, and IWYU output for them from
     238 | +  # this job's IWYU version and mapping files can disagree with upstream (e.g.
     239 | +  # libmultiprocess runs IWYU in its own CI and passes there).
     240 | +  SUBTREES=$(python3 -c 'import runpy, sys; print("|".join(runpy.run_path(sys.argv[1])["SHARED_EXCLUDED_SUBTREES"]))' "${BASE_ROOT_DIR}/test/lint/lint_ignore_dirs.py")
     241 | +  jq --arg patterns "$SUBTREES" 'map(select(.file | test($patterns) | not))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_no_subtrees.json"
     242 | +  mv "${BASE_BUILD_DIR}/compile_commands_no_subtrees.json" "${BASE_BUILD_DIR}/compile_commands.json"
    


    hebasto commented at 3:50 PM on September 25, 2026:

    FWIW, we can use the --exclude option of the iwyu_tool.py tool:

    --- a/ci/test/03_test_script.sh
    +++ b/ci/test/03_test_script.sh
    @@ -242,8 +242,9 @@ if [[ "${RUN_IWYU}" == true ]]; then
         mv "${BASE_BUILD_DIR}/$1" "${BASE_BUILD_DIR}/compile_commands.json"
         {
           python3 /include-what-you-use/mapgen/iwyu-mapgen-clang-intrin.py --lang imp "$("clang-${IWYU_LLVM_V}" -print-resource-dir)/include" > "${BASE_BUILD_DIR}/clang.intrinsics.imp"
    +      mapfile -t IWYU_EXCLUDES < <(python3 -c 'import runpy; print("\n".join("--exclude=" + d for d in runpy.run_path("test/lint/lint_ignore_dirs.py")["SHARED_EXCLUDED_SUBTREES"]))')
           python3 /include-what-you-use/iwyu_tool.py \
    -             -p "${BASE_BUILD_DIR}" "${MAKEJOBS}" -- \
    +             -p "${BASE_BUILD_DIR}" "${MAKEJOBS}" "${IWYU_EXCLUDES[@]}" -- \
                  -Xiwyu --cxx17ns \
                  -Xiwyu --mapping_file="${BASE_ROOT_DIR}/contrib/devtools/iwyu/bitcoin.core.imp" \
                  -Xiwyu --mapping_file="${BASE_BUILD_DIR}/clang.intrinsics.imp" \
    

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-06 13:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me