SERVER-122044 fix clang-tidy ide setup and coverity run (#49986)
GitOrigin-RevId: 89fe63d799c851bde830c5b9d35bc34a81f25cde
This commit is contained in:
parent
d22d958b4a
commit
0216fb1e68
@ -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:
|
||||
|
||||
@ -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:]
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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__":
|
||||
|
||||
@ -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/"):
|
||||
|
||||
162
buildscripts/tests/test_compiledb_posthook.py
Normal file
162
buildscripts/tests/test_compiledb_posthook.py
Normal 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()
|
||||
@ -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=(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user