SERVER-122044 fix clang-tidy ide setup and coverity run (#49986)

GitOrigin-RevId: 89fe63d799c851bde830c5b9d35bc34a81f25cde
This commit is contained in:
Daniel Moody 2026-03-19 15:07:57 -05:00 committed by MongoDB Bot
parent d22d958b4a
commit 0216fb1e68
9 changed files with 524 additions and 32 deletions

View File

@ -99,16 +99,100 @@ def _format_elapsed(reference_time):
return f"{int(hours)}h {int(minutes)}m {seconds:.1f}s"
def _log_progress(message):
line = f"[compiledb +{_format_elapsed(COMPILEDB_START_TIME)}] {message}"
def _get_output_stream():
for stream in [sys.stdout, sys.stderr, sys.__stderr__]:
if not stream:
continue
try:
print(line, file=stream, flush=True)
return
if not stream.writable():
continue
return stream
except (ValueError, OSError):
continue
return None
def _log_progress(message):
line = f"[compiledb +{_format_elapsed(COMPILEDB_START_TIME)}] {message}"
stream = _get_output_stream()
if stream:
try:
print(line, file=stream, flush=True)
except (ValueError, OSError):
pass
def _run_build_command(cmd):
"""Run a bazel build, streaming output directly to the terminal.
Uses a PTY so bazel sees a real terminal (colors, progress bar
overwrites). Both stdout and stderr are routed through the PTY
and echoed to the wrapper's output stream so the output is visible
even when tools/bazel has redirected the default fds to a log file.
Falls back to a plain subprocess when no output stream is available
or the ``pty`` module is missing (e.g. Windows).
"""
stream = _get_output_stream()
out_fd = None
if stream:
try:
out_fd = stream.fileno()
except (AttributeError, OSError):
pass
if out_fd is None:
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
if proc.returncode != 0:
raise RuntimeError(
f"Command failed (rc={proc.returncode}): {' '.join(cmd)}\n"
f"--- output ---\n{proc.stdout.decode(errors='replace')}"
)
return
try:
import pty
parent_fd, child_fd = pty.openpty()
proc = subprocess.Popen(cmd, stdout=child_fd, stderr=child_fd, stdin=child_fd)
os.close(child_fd)
captured = b""
try:
while True:
try:
data = os.read(parent_fd, 4096)
except OSError as e:
if e.errno != errno.EIO:
raise
break
if not data:
break
captured += data
try:
os.write(out_fd, data)
except OSError:
pass
finally:
os.close(parent_fd)
returncode = proc.wait()
stdout = captured.decode(errors="replace")
except ModuleNotFoundError:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
captured = b""
for chunk in iter(lambda: proc.stdout.read(4096), b""):
captured += chunk
try:
os.write(out_fd, chunk)
except OSError:
pass
returncode = proc.wait()
stdout = captured.decode(errors="replace")
if returncode != 0:
raise RuntimeError(
f"Command failed (rc={returncode}): {' '.join(cmd)}\n" f"--- output ---\n{stdout}"
)
def clear_compiledb_posthook_state():
@ -205,6 +289,15 @@ def _resolve_compiledb_targets(target_scope_override=None, requested_targets=Non
return default_target_scope, build_targets, target_scope_expr
def _resolve_extra_build_targets(extra_build_targets=None, setup_clang_tidy=False):
resolved_targets = list(extra_build_targets or [])
if setup_clang_tidy:
for target in SETUP_CLANG_TIDY_BUILD_TARGETS:
if target not in resolved_targets:
resolved_targets.append(target)
return resolved_targets
def _resolve_compiledb_flags(compiledb_config, requested_build_flags=None):
build_flags = list(requested_build_flags or [])
@ -523,10 +616,14 @@ def _collect_aspect_fragment_paths(
compiledb_bazelrc,
compiledb_config,
target_scope_expr,
forwarded_startup_args=None,
):
query_cmd = (
[bazel_bin]
+ ([f"--output_base={output_base}"] if persistent_compdb else [])
+ (
forwarded_startup_args
or ([f"--output_base={output_base}"] if persistent_compdb else [])
)
+ compiledb_bazelrc
+ ["aquery"]
+ ([f"--symlink_prefix={symlink_prefix}"] if persistent_compdb else [])
@ -745,7 +842,10 @@ def _generate_compiledb_via_aspect(
target_scope_override=target_scope_override,
requested_targets=requested_targets,
)
extra_build_targets = list(extra_build_targets or [])
extra_build_targets = _resolve_extra_build_targets(
extra_build_targets=extra_build_targets,
setup_clang_tidy=setup_clang_tidy,
)
build_flags = _resolve_compiledb_flags(
compiledb_config,
requested_build_flags=requested_build_flags,
@ -756,13 +856,25 @@ def _generate_compiledb_via_aspect(
with tempfile.NamedTemporaryFile(delete=False) as buildevents:
buildevents_path = buildevents.name
# Build the startup args for the compiledb build invocation.
# When persistent_compdb, we use a dedicated output_base (overrides any
# --output_user_root in the original startup_args, which is fine).
# Otherwise, forward the caller's startup_args so the build resolves to
# the same output tree (e.g. CI passes --output_user_root).
forwarded_startup_args = list(startup_args or [])
if persistent_compdb:
forwarded_startup_args = [
arg for arg in forwarded_startup_args if not arg.startswith("--output_base=")
]
forwarded_startup_args.append(f"--output_base={output_base}")
try:
if not skip_build:
build_start = time.monotonic()
_log_progress("Generating compiledb command fragments via aspect...")
build_cmd = (
[bazel_bin]
+ ([f"--output_base={output_base}"] if persistent_compdb else [])
+ forwarded_startup_args
+ compiledb_bazelrc
+ ["build"]
+ ([f"--symlink_prefix={symlink_prefix}"] if persistent_compdb else [])
@ -771,9 +883,8 @@ def _generate_compiledb_via_aspect(
f"--build_event_json_file={buildevents_path}",
]
+ build_targets
+ extra_build_targets
)
run_pty_command(build_cmd)
_run_build_command(build_cmd)
_log_progress(
"Generated compiledb command fragments via aspect "
f"in {_format_elapsed(build_start)}"
@ -795,6 +906,7 @@ def _generate_compiledb_via_aspect(
compiledb_bazelrc=compiledb_bazelrc,
compiledb_config=build_flags,
target_scope_expr=target_scope_expr,
forwarded_startup_args=forwarded_startup_args,
)
raw_entries = load_compile_command_fragments_from_paths(fragment_paths)
if not raw_entries:
@ -825,6 +937,23 @@ def _generate_compiledb_via_aspect(
output_json.sort(key=compile_command_sort_key)
write_compile_commands(output_json, REPO_ROOT / "compile_commands.json")
if setup_clang_tidy and extra_build_targets:
_log_progress("Building clang-tidy IDE targets...")
tidy_build_cmd = (
[bazel_bin]
+ forwarded_startup_args
+ compiledb_bazelrc
+ ["build"]
+ ([f"--symlink_prefix={symlink_prefix}"] if persistent_compdb else [])
+ extra_build_targets
)
try:
_run_build_command(tidy_build_cmd)
except RuntimeError:
_log_progress(
"Warning: failed to build clang-tidy targets; " "skipping clang-tidy IDE setup."
)
setup_clang_tidy = False
if setup_clang_tidy:
setup_clang_tidy_from_built_outputs()
finally:

View File

@ -15,6 +15,7 @@ sys.path.append(str(REPO_ROOT))
from bazel.wrapper_hook.compiledb import (
clear_compiledb_posthook_state,
generate_compiledb,
prepare_compiledb_posthook_args,
)
from bazel.wrapper_hook.lint import run_rules_lint
from bazel.wrapper_hook.wrapper_debug import wrapper_debug
@ -163,6 +164,7 @@ def test_runner_interface(
plus_starts = ("+", ":+", "//:+")
skip_plus_interface = True
compiledb_target = False
compiledb_config = False
setup_clang_tidy = False
clang_tidy = False
lint_target = False
@ -235,6 +237,8 @@ def test_runner_interface(
val = config_value
if val in {"opt", "dbg", "fastbuild", "dbg_aubsan", "dbg_tsan"}:
config_mode = val
if val in ("compiledb", "compiledb-aspect"):
compiledb_config = True
if val == "clang-tidy":
clang_tidy = True
if arg.startswith(plus_starts):
@ -309,6 +313,29 @@ def test_runner_interface(
+ ["--", "ALL_PASSING"]
)
if compiledb_config and not compiledb_target and current_bazel_command == "build":
parsed_build_flags, parsed_build_targets, parsed_target_pattern_file = (
_parse_targets_and_flags(args[command_index + 1 :], {}, [], [])
)
posthook_targets = list(parsed_build_targets)
if parsed_target_pattern_file and os.path.isfile(parsed_target_pattern_file):
with open(parsed_target_pattern_file, "r", encoding="utf-8") as f:
posthook_targets.extend(line.strip() for line in f if line.strip())
return prepare_compiledb_posthook_args(
bazel_bin=args[0],
startup_args=startup_args,
command=current_bazel_command,
build_flags=parsed_build_flags,
build_targets=parsed_build_targets,
persistent_compdb=persistent_compdb,
enterprise=enterprise,
atlas=atlas,
compiledb_targets=posthook_targets or None,
setup_clang_tidy=False,
)
if skip_plus_interface and not autocomplete_query:
return args[1:]

View File

@ -15,31 +15,10 @@ PLUGIN_CANDIDATES = [
]
def _linux_distribution_id_version() -> tuple[str | None, str | None]:
if platform.system() != "Linux":
return None, None
os_release = pathlib.Path("/etc/os-release")
if not os_release.exists():
return None, None
metadata: dict[str, str] = {}
for line in os_release.read_text(encoding="utf-8").splitlines():
if "=" not in line:
continue
key, value = line.split("=", 1)
metadata[key] = value.strip().strip('"').strip("'")
return metadata.get("ID"), metadata.get("VERSION_ID")
def mongo_tidy_checks_supported_platform() -> bool:
if platform.system() != "Linux":
return False
distro_id, version_id = _linux_distribution_id_version()
return not (distro_id == "ubuntu" and version_id == "18.04")
def clang_tidy_setup_recovery_message() -> str:
if mongo_tidy_checks_supported_platform():
@ -72,7 +51,9 @@ def materialize_clang_tidy_ide_files(
plugin_src: pathlib.Path,
) -> tuple[bool, bool]:
config_changed = _copy_if_changed(config_src, repo_root / ".clang-tidy")
marker_changed = _write_if_changed(repo_root / ".mongo_checks_module_path", str(plugin_src))
marker_changed = _write_if_changed(
repo_root / ".mongo_checks_module_path", str(plugin_src.resolve())
)
return config_changed, marker_changed

View File

@ -414,26 +414,35 @@ class Tests(unittest.TestCase):
args = ["wrapper_hook", "build", "--config=compiledb", "//src/mongo/base:error_codes"]
generate_calls = []
prepare_calls = []
def fake_generate_compiledb(*call_args, **call_kwargs):
generate_calls.append((call_args, call_kwargs))
def fake_prepare_posthook(*call_args, **call_kwargs):
prepare_calls.append((call_args, call_kwargs))
return ["build", "--config=compiledb", "//src/mongo/base:error_codes"]
original_generate_compiledb = plus_interface.generate_compiledb
original_prepare_posthook = plus_interface.prepare_compiledb_posthook_args
original_wrapper_config_mode_file = plus_interface.WRAPPER_CONFIG_MODE_FILE
with tempfile.TemporaryDirectory() as tempdir:
wrapper_config_mode_file = os.path.join(tempdir, "mongo_wrapper_config_mode")
with open(wrapper_config_mode_file, "w", encoding="utf-8") as file_handle:
file_handle.write("dbg")
plus_interface.generate_compiledb = fake_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = fake_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = wrapper_config_mode_file
try:
result = test_runner_interface(args, False, buildozer_output)
finally:
plus_interface.generate_compiledb = original_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = original_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
assert result == ["build", "--config=compiledb", "//src/mongo/base:error_codes"]
assert len(generate_calls) == 0
assert len(prepare_calls) == 1
def test_config_separate_compiledb_runs_normally(self):
def buildozer_output(autocomplete_query):
@ -441,22 +450,35 @@ class Tests(unittest.TestCase):
args = ["wrapper_hook", "build", "--config", "compiledb", "//src/mongo/base:error_codes"]
generate_calls = []
prepare_calls = []
def fake_generate_compiledb(*call_args, **call_kwargs):
generate_calls.append((call_args, call_kwargs))
def fake_prepare_posthook(*call_args, **call_kwargs):
prepare_calls.append((call_args, call_kwargs))
return [
"build",
"--config",
"compiledb",
"//src/mongo/base:error_codes",
]
original_generate_compiledb = plus_interface.generate_compiledb
original_prepare_posthook = plus_interface.prepare_compiledb_posthook_args
original_wrapper_config_mode_file = plus_interface.WRAPPER_CONFIG_MODE_FILE
with tempfile.TemporaryDirectory() as tempdir:
wrapper_config_mode_file = os.path.join(tempdir, "mongo_wrapper_config_mode")
with open(wrapper_config_mode_file, "w", encoding="utf-8") as file_handle:
file_handle.write("dbg")
plus_interface.generate_compiledb = fake_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = fake_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = wrapper_config_mode_file
try:
result = test_runner_interface(args, False, buildozer_output)
finally:
plus_interface.generate_compiledb = original_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = original_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
assert result == [
@ -466,6 +488,7 @@ class Tests(unittest.TestCase):
"//src/mongo/base:error_codes",
]
assert len(generate_calls) == 0
assert len(prepare_calls) == 1
def test_config_separate_compiledb_runs_normally_with_plain_target(self):
def buildozer_output(autocomplete_query):
@ -473,22 +496,35 @@ class Tests(unittest.TestCase):
args = ["wrapper_hook", "build", "--config", "compiledb", "install-dist-test"]
generate_calls = []
prepare_calls = []
def fake_generate_compiledb(*call_args, **call_kwargs):
generate_calls.append((call_args, call_kwargs))
def fake_prepare_posthook(*call_args, **call_kwargs):
prepare_calls.append((call_args, call_kwargs))
return [
"build",
"--config",
"compiledb",
"install-dist-test",
]
original_generate_compiledb = plus_interface.generate_compiledb
original_prepare_posthook = plus_interface.prepare_compiledb_posthook_args
original_wrapper_config_mode_file = plus_interface.WRAPPER_CONFIG_MODE_FILE
with tempfile.TemporaryDirectory() as tempdir:
wrapper_config_mode_file = os.path.join(tempdir, "mongo_wrapper_config_mode")
with open(wrapper_config_mode_file, "w", encoding="utf-8") as file_handle:
file_handle.write("dbg")
plus_interface.generate_compiledb = fake_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = fake_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = wrapper_config_mode_file
try:
result = test_runner_interface(args, False, buildozer_output)
finally:
plus_interface.generate_compiledb = original_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = original_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
assert result == [
@ -498,6 +534,7 @@ class Tests(unittest.TestCase):
"install-dist-test",
]
assert len(generate_calls) == 0
assert len(prepare_calls) == 1
def test_config_separate_compiledb_runs_normally_with_target_before_config(self):
def buildozer_output(autocomplete_query):
@ -505,22 +542,35 @@ class Tests(unittest.TestCase):
args = ["wrapper_hook", "build", "install-dist-test", "--config", "compiledb"]
generate_calls = []
prepare_calls = []
def fake_generate_compiledb(*call_args, **call_kwargs):
generate_calls.append((call_args, call_kwargs))
def fake_prepare_posthook(*call_args, **call_kwargs):
prepare_calls.append((call_args, call_kwargs))
return [
"build",
"install-dist-test",
"--config",
"compiledb",
]
original_generate_compiledb = plus_interface.generate_compiledb
original_prepare_posthook = plus_interface.prepare_compiledb_posthook_args
original_wrapper_config_mode_file = plus_interface.WRAPPER_CONFIG_MODE_FILE
with tempfile.TemporaryDirectory() as tempdir:
wrapper_config_mode_file = os.path.join(tempdir, "mongo_wrapper_config_mode")
with open(wrapper_config_mode_file, "w", encoding="utf-8") as file_handle:
file_handle.write("dbg")
plus_interface.generate_compiledb = fake_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = fake_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = wrapper_config_mode_file
try:
result = test_runner_interface(args, False, buildozer_output)
finally:
plus_interface.generate_compiledb = original_generate_compiledb
plus_interface.prepare_compiledb_posthook_args = original_prepare_posthook
plus_interface.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
assert result == [
@ -530,6 +580,7 @@ class Tests(unittest.TestCase):
"compiledb",
]
assert len(generate_calls) == 0
assert len(prepare_calls) == 1
if __name__ == "__main__":

View File

@ -3,10 +3,40 @@ import unittest
sys.path.append(".")
from bazel.wrapper_hook.compiledb import _build_final_compile_command_entry
from bazel.wrapper_hook.compiledb import (
SETUP_CLANG_TIDY_BUILD_TARGETS,
_build_final_compile_command_entry,
_resolve_extra_build_targets,
)
class CompiledbOutputFormatTest(unittest.TestCase):
def test_setup_clang_tidy_targets_are_appended_once(self):
extra_build_targets = [
"//src/mongo/base:error_codes",
SETUP_CLANG_TIDY_BUILD_TARGETS[0],
]
resolved_targets = _resolve_extra_build_targets(
extra_build_targets=extra_build_targets,
setup_clang_tidy=True,
)
assert resolved_targets == [
"//src/mongo/base:error_codes",
*SETUP_CLANG_TIDY_BUILD_TARGETS,
]
def test_setup_clang_tidy_targets_are_not_added_when_disabled(self):
extra_build_targets = ["//src/mongo/base:error_codes"]
resolved_targets = _resolve_extra_build_targets(
extra_build_targets=extra_build_targets,
setup_clang_tidy=False,
)
assert resolved_targets == extra_build_targets
def test_final_entry_omits_non_standard_target_key(self):
def rewrite_exec_path(path, out_root_str, external_root_str):
if path.startswith("bazel-out/"):

View File

@ -0,0 +1,162 @@
import os
import sys
import tempfile
import unittest
from unittest import mock
sys.path.append(".")
from bazel.wrapper_hook.plus_interface import test_runner_interface
def _noop_buildozer(*_args, **_kwargs):
return ""
class CompiledbPosthookTest(unittest.TestCase):
"""Verify that the compiledb posthook is set up from both the 'compiledb' target
and the '--config=compiledb' config flag."""
PATCHES = {
"generate": "bazel.wrapper_hook.plus_interface.generate_compiledb",
"prepare": "bazel.wrapper_hook.plus_interface.prepare_compiledb_posthook_args",
"clear": "bazel.wrapper_hook.plus_interface.clear_compiledb_posthook_state",
"swap": "bazel.wrapper_hook.plus_interface.swap_default_config",
}
def _run(self, args, **kwargs):
with (
mock.patch(self.PATCHES["generate"]) as mock_gen,
mock.patch(self.PATCHES["prepare"], return_value=["build", "--done"]) as mock_prep,
mock.patch(self.PATCHES["clear"]),
mock.patch(self.PATCHES["swap"], return_value=None),
mock.patch.dict(os.environ, {"CI": "1"}, clear=False),
):
result = test_runner_interface(
args,
autocomplete_query=False,
get_buildozer_output=_noop_buildozer,
enterprise=True,
atlas=True,
**kwargs,
)
return result, mock_gen, mock_prep
# ------------------------------------------------------------------
# compiledb TARGET triggers generate_compiledb (direct path)
# ------------------------------------------------------------------
def test_compiledb_target_calls_generate_compiledb(self):
result, mock_gen, mock_prep = self._run(["bazel", "build", "compiledb"])
mock_gen.assert_called_once()
mock_prep.assert_not_called()
self.assertEqual(result, [])
def test_compiledb_target_with_colon_calls_generate_compiledb(self):
result, mock_gen, mock_prep = self._run(["bazel", "build", ":compiledb"])
mock_gen.assert_called_once()
mock_prep.assert_not_called()
def test_compiledb_target_full_label_calls_generate_compiledb(self):
result, mock_gen, mock_prep = self._run(["bazel", "build", "//:compiledb"])
mock_gen.assert_called_once()
mock_prep.assert_not_called()
# ------------------------------------------------------------------
# --config=compiledb triggers prepare_compiledb_posthook_args
# ------------------------------------------------------------------
def test_config_compiledb_calls_prepare_posthook(self):
result, mock_gen, mock_prep = self._run(
["bazel", "build", "--config=compiledb", "//src/mongo/..."]
)
mock_gen.assert_not_called()
mock_prep.assert_called_once()
call_kwargs = mock_prep.call_args
self.assertEqual(call_kwargs.kwargs["command"], "build")
self.assertFalse(call_kwargs.kwargs["setup_clang_tidy"])
def test_config_compiledb_aspect_calls_prepare_posthook(self):
result, mock_gen, mock_prep = self._run(
["bazel", "build", "--config=compiledb-aspect", "//src/mongo/..."]
)
mock_gen.assert_not_called()
mock_prep.assert_called_once()
def test_config_compiledb_with_startup_args_forwards_them(self):
result, mock_gen, mock_prep = self._run(
[
"bazel",
"--output_user_root=/tmp/cache",
"build",
"--config=compiledb",
"//src/mongo/...",
]
)
mock_prep.assert_called_once()
call_kwargs = mock_prep.call_args
self.assertEqual(call_kwargs.kwargs["startup_args"], ["--output_user_root=/tmp/cache"])
def test_config_compiledb_with_target_pattern_file(self):
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("//src/mongo/db:mongod\n//src/mongo/s:mongos\n")
pattern_file = f.name
try:
result, mock_gen, mock_prep = self._run(
[
"bazel",
"build",
"--config=compiledb",
f"--target_pattern_file={pattern_file}",
]
)
mock_prep.assert_called_once()
call_kwargs = mock_prep.call_args
self.assertEqual(call_kwargs.kwargs["build_targets"], [])
posthook_targets = call_kwargs.kwargs["compiledb_targets"]
self.assertIn("//src/mongo/db:mongod", posthook_targets)
self.assertIn("//src/mongo/s:mongos", posthook_targets)
finally:
os.unlink(pattern_file)
def test_config_compiledb_returns_prepare_result(self):
result, _, _ = self._run(["bazel", "build", "--config=compiledb", "//src/mongo/..."])
self.assertEqual(result, ["build", "--done"])
# ------------------------------------------------------------------
# Plain build (no compiledb) triggers neither
# ------------------------------------------------------------------
def test_plain_build_skips_compiledb(self):
result, mock_gen, mock_prep = self._run(["bazel", "build", "//src/mongo/..."])
mock_gen.assert_not_called()
mock_prep.assert_not_called()
self.assertEqual(result, ["build", "//src/mongo/..."])
# ------------------------------------------------------------------
# compiledb target takes precedence over --config=compiledb
# ------------------------------------------------------------------
def test_compiledb_target_takes_precedence_over_config(self):
"""When both 'compiledb' target and --config=compiledb appear,
generate_compiledb is called (target path), not prepare_posthook."""
result, mock_gen, mock_prep = self._run(
["bazel", "build", "--config=compiledb", "compiledb"]
)
mock_gen.assert_called_once()
mock_prep.assert_not_called()
# ------------------------------------------------------------------
# Non-build commands with --config=compiledb don't trigger posthook
# ------------------------------------------------------------------
def test_config_compiledb_on_test_command_does_not_trigger_posthook(self):
result, mock_gen, mock_prep = self._run(
["bazel", "test", "--config=compiledb", "//src/mongo/..."]
)
mock_gen.assert_not_called()
mock_prep.assert_not_called()
if __name__ == "__main__":
unittest.main()

View File

@ -64,6 +64,9 @@ printf ' %q' "${build_compiledb_command[@]}"
echo
"${build_compiledb_command[@]}"
echo "Setting up clang-tidy IDE files"
bazel $bazel_cache run $build_config //:setup_clang_tidy
compiledb_output_base="$(bazel $bazel_cache info output_base)"
repo_python=""
python_candidates=(

View File

@ -15,6 +15,14 @@ from typing import Any, Iterator
STANDARD_COMPILE_COMMAND_KEYS = frozenset({"arguments", "command", "directory", "file", "output"})
COMPILEDB_GENERATION_TARGETS = ["compiledb", "install-wiredtiger"]
MONGO_TIDY_PLUGIN_CANDIDATES = frozenset(
[
"libmongo_tidy_checks.so",
"libmongo_tidy_checks.dylib",
"mongo_tidy_checks.dll",
"libmongo_tidy_checks.dll",
]
)
def _get_workspace_dir() -> str:
@ -38,6 +46,48 @@ def _ensure_compiledb_exists(compdb_path: str) -> None:
subprocess.run(["bazel", "build", *COMPILEDB_GENERATION_TARGETS], check=True)
def _mongo_tidy_checks_supported_platform() -> bool:
if platform.system() != "Linux":
return False
def _validate_clang_tidy_setup(workspace_dir: str) -> None:
if not _mongo_tidy_checks_supported_platform():
return
config_path = os.path.join(workspace_dir, ".clang-tidy")
if not os.path.isfile(config_path):
raise ValueError(
"Expected '.clang-tidy' to exist in the workspace root after generating "
"compile_commands.json on this platform."
)
plugin_marker_path = os.path.join(workspace_dir, ".mongo_checks_module_path")
if not os.path.isfile(plugin_marker_path):
raise ValueError(
"Expected '.mongo_checks_module_path' to exist in the workspace root after "
"generating compile_commands.json on this platform."
)
with open(plugin_marker_path, "r", encoding="utf-8") as marker_file:
plugin_path = marker_file.read().strip()
if not plugin_path:
raise ValueError("'.mongo_checks_module_path' must contain a plugin path.")
plugin_name = os.path.basename(plugin_path)
if plugin_name not in MONGO_TIDY_PLUGIN_CANDIDATES:
raise ValueError(
f"'.mongo_checks_module_path' points to an unexpected plugin file: {plugin_name}"
)
if not os.path.isfile(plugin_path):
raise ValueError(
f"The mongo_tidy_checks plugin file recorded in '.mongo_checks_module_path' "
f"does not exist: {plugin_path}"
)
def _parse_repo_env_from_bazelrc(bazelrc_path: str, var_name: str) -> str | None:
"""Extract --repo_env=FOO=... from a .bazelrc file (best-effort)."""
if not os.path.exists(bazelrc_path):
@ -718,6 +768,11 @@ def main() -> int:
cli_args = _parse_args()
compdb_path = "compile_commands.json"
_ensure_compiledb_exists(compdb_path)
try:
_validate_clang_tidy_setup(workspace_dir)
except ValueError as e:
sys.stderr.write(f"ERROR: {e}\n")
return 1
try:
selection_count = _determine_selection_count(
default_count=10,

View File

@ -11,6 +11,60 @@ import validate_compile_commands as validator
class ValidateCompileCommandsTest(unittest.TestCase):
def test_validate_clang_tidy_setup_skips_unsupported_platforms(self):
with tempfile.TemporaryDirectory() as workspace_dir:
with mock.patch.object(
validator, "_mongo_tidy_checks_supported_platform", return_value=False
):
validator._validate_clang_tidy_setup(workspace_dir)
def test_validate_clang_tidy_setup_accepts_expected_files(self):
with tempfile.TemporaryDirectory() as workspace_dir:
plugin_dir = os.path.join(workspace_dir, "bazel-bin", "src", "mongo", "tools")
os.makedirs(plugin_dir, exist_ok=True)
plugin_path = os.path.join(plugin_dir, "libmongo_tidy_checks.so")
with open(os.path.join(workspace_dir, ".clang-tidy"), "w", encoding="utf-8") as f:
f.write("Checks: '*'\n")
with open(plugin_path, "w", encoding="utf-8") as f:
f.write("plugin")
with open(
os.path.join(workspace_dir, ".mongo_checks_module_path"), "w", encoding="utf-8"
) as f:
f.write(plugin_path)
with mock.patch.object(
validator, "_mongo_tidy_checks_supported_platform", return_value=True
):
validator._validate_clang_tidy_setup(workspace_dir)
def test_validate_clang_tidy_setup_rejects_missing_config(self):
with tempfile.TemporaryDirectory() as workspace_dir:
with mock.patch.object(
validator, "_mongo_tidy_checks_supported_platform", return_value=True
):
with self.assertRaisesRegex(ValueError, r"Expected '\.clang-tidy' to exist"):
validator._validate_clang_tidy_setup(workspace_dir)
def test_validate_clang_tidy_setup_rejects_missing_plugin(self):
with tempfile.TemporaryDirectory() as workspace_dir:
missing_plugin_path = os.path.join(
workspace_dir, "bazel-bin", "src", "mongo", "tools", "libmongo_tidy_checks.so"
)
with open(os.path.join(workspace_dir, ".clang-tidy"), "w", encoding="utf-8") as f:
f.write("Checks: '*'\n")
with open(
os.path.join(workspace_dir, ".mongo_checks_module_path"), "w", encoding="utf-8"
) as f:
f.write(missing_plugin_path)
with mock.patch.object(
validator, "_mongo_tidy_checks_supported_platform", return_value=True
):
with self.assertRaisesRegex(
ValueError, r"The mongo_tidy_checks plugin file recorded"
):
validator._validate_clang_tidy_setup(workspace_dir)
def test_accepts_standard_arguments_entry(self):
validator._validate_compiledb_entry(
{