SERVER-117726 move compiledb generation into the bazel graph (#49797)
GitOrigin-RevId: ed2b1096fd0a6b1eea8405df897756feda930f5d
This commit is contained in:
parent
04b80e7822
commit
7fbd72d6f0
9
.bazelrc
9
.bazelrc
@ -544,6 +544,15 @@ common:mod-scanner --output_groups=report
|
||||
common:mod-scanner --aspects //modules_poc:mod_scanner.bzl%mod_scanner_aspect
|
||||
common:mod-scanner --remote_download_regex=.*\.mod_scanner_decls.json$
|
||||
|
||||
--config=compiledb
|
||||
common:compiledb --aspects //bazel/compiledb:compiledb_aspect.bzl%compiledb_aspect
|
||||
common:compiledb --output_groups=compiledb_report
|
||||
common:compiledb --keep_going
|
||||
common:compiledb --remote_download_regex=.*\.compile_command\.json$
|
||||
|
||||
--config=compiledb-aspect
|
||||
common:compiledb-aspect --config=compiledb
|
||||
|
||||
--config=symbol-checker
|
||||
common:symbol-checker --aspects //bazel/symbol_checker:symbol_checker.bzl%symbol_checker_aspect
|
||||
common:symbol-checker --output_groups=symbol_checker
|
||||
|
||||
25
BUILD.bazel
25
BUILD.bazel
@ -93,6 +93,11 @@ alias(
|
||||
actual = "//src/mongo/tools/mongo_tidy_checks/tests:MongoTidyCheck_unittest",
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "setup_clang_tidy",
|
||||
actual = "//buildscripts:setup_clang_tidy",
|
||||
)
|
||||
|
||||
setup_mongo_toolchain_aliases()
|
||||
|
||||
setup_gdb_toolchain_aliases()
|
||||
@ -125,7 +130,7 @@ render_template(
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "compiledb",
|
||||
name = "compiledb_only",
|
||||
srcs = ["compile_commands.json"],
|
||||
outs = ["compile_commands_done"],
|
||||
cmd = "echo noop > $(location :compile_commands_done)",
|
||||
@ -135,6 +140,24 @@ genrule(
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "compiledb",
|
||||
srcs = ["compile_commands.json"] + select({
|
||||
"@platforms//os:windows": [],
|
||||
"//conditions:default": [
|
||||
":setup_clang_tidy",
|
||||
":clang_tidy_config",
|
||||
"//src/mongo/tools/mongo_tidy_checks",
|
||||
],
|
||||
}),
|
||||
outs = ["compiledb_setup_done"],
|
||||
cmd = "echo noop > $(location :compiledb_setup_done)",
|
||||
tags = [
|
||||
"no-cache",
|
||||
"no-remote-exec",
|
||||
],
|
||||
)
|
||||
|
||||
# This sets up targets for install-wiredtiger and archive-wiredtiger
|
||||
mongo_install(
|
||||
name = "wiredtiger",
|
||||
|
||||
@ -26,6 +26,17 @@ def dedupe_stable(xs):
|
||||
out.append(x)
|
||||
return out
|
||||
|
||||
_AUTO_HEADER_EXTENSIONS = {
|
||||
".c": True,
|
||||
".cc": True,
|
||||
".cpp": True,
|
||||
".cxx": True,
|
||||
".h": True,
|
||||
".hh": True,
|
||||
".hpp": True,
|
||||
".hxx": True,
|
||||
}
|
||||
|
||||
def _fg_name_for_filename(name):
|
||||
# NEW: collapse to leaf to match Python generator
|
||||
leaf = name.rsplit("/", 1)[-1]
|
||||
@ -75,6 +86,10 @@ def _is_third_party_pkg(pkg):
|
||||
"third_party/" in pkg # safety
|
||||
)
|
||||
|
||||
def _has_auto_header_extension(name):
|
||||
dot = name.rfind(".")
|
||||
return dot != -1 and name[dot:] in _AUTO_HEADER_EXTENSIONS
|
||||
|
||||
def maybe_compute_auto_headers(srcs):
|
||||
# Only handle plain list-of-strings; if configurable/mixed, return None
|
||||
if type(srcs) != "list":
|
||||
@ -90,12 +105,12 @@ def maybe_compute_auto_headers(srcs):
|
||||
out.append(s)
|
||||
continue
|
||||
|
||||
pkg, name = _split_label_or_file(s)
|
||||
if _is_third_party_pkg(pkg):
|
||||
# Skip external repos entirely.
|
||||
if s.startswith("@"):
|
||||
continue
|
||||
|
||||
# Skip external repos and any third_party package entirely
|
||||
if s.startswith("@"):
|
||||
pkg, name = _split_label_or_file(s)
|
||||
if _is_third_party_pkg(pkg):
|
||||
continue
|
||||
|
||||
# If *_gen listed in srcs, add its auto-header (transitive headers),
|
||||
@ -105,8 +120,7 @@ def maybe_compute_auto_headers(srcs):
|
||||
continue
|
||||
|
||||
# Regular mapping for files we care about
|
||||
if (name.endswith(".c") or name.endswith(".cc") or name.endswith(".cpp") or name.endswith(".cxx") or
|
||||
name.endswith(".h") or name.endswith(".hh") or name.endswith(".hpp") or name.endswith(".hxx")):
|
||||
if _has_auto_header_extension(name):
|
||||
out.append(_auto_header_label(pkg, name))
|
||||
continue
|
||||
|
||||
@ -261,6 +275,7 @@ def build_selects_and_flat_files(srcs_select, *, lib_name, debug = False):
|
||||
return [], []
|
||||
select_objs = []
|
||||
flat_files = []
|
||||
seen_flat_files = {}
|
||||
for i, condmap in enumerate(srcs_select):
|
||||
if type(condmap) != type({}):
|
||||
fail("mongo_cc macro({}): srcs_select[{}] must be a dict of {cond: [srcs]}."
|
||||
@ -276,6 +291,8 @@ def build_selects_and_flat_files(srcs_select, *, lib_name, debug = False):
|
||||
if type(s) != "string":
|
||||
fail("mongo_cc macro({}): srcs_select[{}][{}] item must be string, got {}"
|
||||
.format(lib_name, i, cond, type(s)))
|
||||
flat_files.extend(src_list)
|
||||
if s not in seen_flat_files:
|
||||
seen_flat_files[s] = True
|
||||
flat_files.append(s)
|
||||
select_objs.append(select(condmap))
|
||||
return select_objs, dedupe_preserve_order(flat_files)
|
||||
return select_objs, flat_files
|
||||
|
||||
0
bazel/compiledb/BUILD.bazel
Normal file
0
bazel/compiledb/BUILD.bazel
Normal file
422
bazel/compiledb/compiledb_aspect.bzl
Normal file
422
bazel/compiledb/compiledb_aspect.bzl
Normal file
@ -0,0 +1,422 @@
|
||||
"""Aspect-based compile_commands fragment generation."""
|
||||
|
||||
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
|
||||
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
|
||||
|
||||
_SOURCE_EXTENSIONS = {
|
||||
"c": True,
|
||||
"cc": True,
|
||||
"cpp": True,
|
||||
"cxx": True,
|
||||
"c++": True,
|
||||
"C": True,
|
||||
}
|
||||
|
||||
CompileCommandInfo = provider(
|
||||
"Transitive compile_commands fragment files.",
|
||||
fields = {
|
||||
"files": "depset of compile command fragments",
|
||||
"required_inputs": "depset of generated compile inputs that must be materialized",
|
||||
},
|
||||
)
|
||||
|
||||
def _is_cpp_source(src):
|
||||
return src.extension in _SOURCE_EXTENSIONS
|
||||
|
||||
def _rule_sources(ctx):
|
||||
srcs = []
|
||||
if hasattr(ctx.rule.files, "srcs"):
|
||||
srcs.extend(ctx.rule.files.srcs)
|
||||
if hasattr(ctx.rule.file, "src") and ctx.rule.file.src:
|
||||
srcs.append(ctx.rule.file.src)
|
||||
elif hasattr(ctx.rule.attr, "srcs"):
|
||||
for src in ctx.rule.attr.srcs:
|
||||
srcs.extend(src.files.to_list())
|
||||
|
||||
seen = {}
|
||||
filtered = []
|
||||
for src in srcs:
|
||||
if not _is_cpp_source(src):
|
||||
continue
|
||||
if src.path in seen:
|
||||
continue
|
||||
seen[src.path] = True
|
||||
filtered.append(src)
|
||||
return filtered
|
||||
|
||||
def _expand_flags(ctx, flags):
|
||||
needs_location_expansion = False
|
||||
needs_make_expansion = False
|
||||
for flag in flags:
|
||||
if "$(" in flag:
|
||||
needs_make_expansion = True
|
||||
if "$(location" in flag:
|
||||
needs_location_expansion = True
|
||||
|
||||
if not needs_make_expansion:
|
||||
return flags
|
||||
|
||||
location_targets = []
|
||||
if needs_location_expansion:
|
||||
seen_labels = {}
|
||||
for attr_name in [
|
||||
"srcs",
|
||||
"hdrs",
|
||||
"textual_hdrs",
|
||||
"deps",
|
||||
"implementation_deps",
|
||||
"additional_compiler_inputs",
|
||||
"data",
|
||||
"binary_with_debug",
|
||||
]:
|
||||
if not hasattr(ctx.rule.attr, attr_name):
|
||||
continue
|
||||
attr_value = getattr(ctx.rule.attr, attr_name)
|
||||
if type(attr_value) == "list":
|
||||
values = attr_value
|
||||
elif attr_value != None:
|
||||
values = [attr_value]
|
||||
else:
|
||||
values = []
|
||||
|
||||
for value in values:
|
||||
if not hasattr(value, "label"):
|
||||
continue
|
||||
label = str(value.label)
|
||||
if label in seen_labels:
|
||||
continue
|
||||
seen_labels[label] = True
|
||||
location_targets.append(value)
|
||||
|
||||
expanded = []
|
||||
for flag in flags:
|
||||
if needs_location_expansion and "$(location" in flag:
|
||||
flag = ctx.expand_location(flag, location_targets)
|
||||
if "$(" in flag:
|
||||
flag = ctx.expand_make_variables("compiledb_expand_flags", flag, ctx.var)
|
||||
expanded.append(flag)
|
||||
return expanded
|
||||
|
||||
def _compile_variables(cc_toolchain, feature_configuration, compilation_context, user_compile_flags):
|
||||
return cc_common.create_compile_variables(
|
||||
feature_configuration = feature_configuration,
|
||||
cc_toolchain = cc_toolchain,
|
||||
user_compile_flags = user_compile_flags,
|
||||
include_directories = compilation_context.includes,
|
||||
quote_include_directories = compilation_context.quote_includes,
|
||||
system_include_directories = depset(
|
||||
transitive = [
|
||||
compilation_context.system_includes,
|
||||
compilation_context.external_includes,
|
||||
],
|
||||
),
|
||||
framework_include_directories = compilation_context.framework_includes,
|
||||
preprocessor_defines = depset(
|
||||
transitive = [
|
||||
compilation_context.defines,
|
||||
compilation_context.local_defines,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def _rule_compile_flags(ctx):
|
||||
common_flags = []
|
||||
cxx_flags = []
|
||||
|
||||
if hasattr(ctx.rule.attr, "copts"):
|
||||
common_flags.extend(ctx.rule.attr.copts)
|
||||
if hasattr(ctx.rule.attr, "cxxopts"):
|
||||
cxx_flags.extend(ctx.rule.attr.cxxopts)
|
||||
|
||||
return common_flags, cxx_flags
|
||||
|
||||
def _requested_and_unsupported_features(ctx):
|
||||
requested_features = list(ctx.features)
|
||||
unsupported_features = list(ctx.disabled_features)
|
||||
|
||||
if hasattr(ctx.rule.attr, "features"):
|
||||
for feature in ctx.rule.attr.features:
|
||||
if feature.startswith("-"):
|
||||
unsupported_features.append(feature[1:])
|
||||
else:
|
||||
requested_features.append(feature)
|
||||
|
||||
return requested_features, unsupported_features
|
||||
|
||||
def _toolchain_flags(feature_configuration, action_name, compile_variables):
|
||||
return cc_common.get_memory_inefficient_command_line(
|
||||
feature_configuration = feature_configuration,
|
||||
action_name = action_name,
|
||||
variables = compile_variables,
|
||||
)
|
||||
|
||||
def _compiler_path(cc_toolchain):
|
||||
compiler = cc_toolchain.compiler_executable
|
||||
if hasattr(compiler, "path"):
|
||||
return compiler.path
|
||||
return compiler
|
||||
|
||||
def _should_materialize_artifact(path):
|
||||
return path.startswith("bazel-out/")
|
||||
|
||||
def _collect_dep_compile_command_info(ctx):
|
||||
output_files = []
|
||||
required_inputs = []
|
||||
for attr_name in ["deps", "implementation_deps", "binary_with_debug"]:
|
||||
if not hasattr(ctx.rule.attr, attr_name):
|
||||
continue
|
||||
|
||||
attr_value = getattr(ctx.rule.attr, attr_name)
|
||||
if type(attr_value) == "list":
|
||||
deps = attr_value
|
||||
else:
|
||||
deps = [attr_value]
|
||||
|
||||
for dep in deps:
|
||||
if dep != None and CompileCommandInfo in dep:
|
||||
output_files.append(dep[CompileCommandInfo].files)
|
||||
required_inputs.append(dep[CompileCommandInfo].required_inputs)
|
||||
|
||||
return depset(transitive = output_files), depset(transitive = required_inputs)
|
||||
|
||||
def _required_compile_inputs(ctx, compilation_context, srcs):
|
||||
direct = []
|
||||
seen = {}
|
||||
|
||||
if hasattr(ctx.rule.files, "srcs"):
|
||||
for artifact in ctx.rule.files.srcs:
|
||||
if _should_materialize_artifact(artifact.path) and artifact.path not in seen:
|
||||
seen[artifact.path] = True
|
||||
direct.append(artifact)
|
||||
|
||||
if hasattr(ctx.rule.file, "src") and ctx.rule.file.src:
|
||||
artifact = ctx.rule.file.src
|
||||
if _should_materialize_artifact(artifact.path) and artifact.path not in seen:
|
||||
seen[artifact.path] = True
|
||||
direct.append(artifact)
|
||||
|
||||
for src in srcs:
|
||||
if _should_materialize_artifact(src.path) and src.path not in seen:
|
||||
seen[src.path] = True
|
||||
direct.append(src)
|
||||
|
||||
for header in compilation_context.headers.to_list():
|
||||
if _should_materialize_artifact(header.path) and header.path not in seen:
|
||||
seen[header.path] = True
|
||||
direct.append(header)
|
||||
|
||||
for attr_name in ["hdrs", "textual_hdrs", "additional_compiler_inputs"]:
|
||||
if not hasattr(ctx.rule.files, attr_name):
|
||||
continue
|
||||
for artifact in getattr(ctx.rule.files, attr_name):
|
||||
if _should_materialize_artifact(artifact.path) and artifact.path not in seen:
|
||||
seen[artifact.path] = True
|
||||
direct.append(artifact)
|
||||
|
||||
return depset(direct = direct)
|
||||
|
||||
def _package_root(label):
|
||||
parts = []
|
||||
if label.workspace_root:
|
||||
parts.append(label.workspace_root)
|
||||
if label.package:
|
||||
parts.append(label.package)
|
||||
return "/".join(parts)
|
||||
|
||||
def _hex32(val):
|
||||
v = val & 0xFFFFFFFF
|
||||
s = "%x" % v
|
||||
return ("0" * (8 - len(s))) + s
|
||||
|
||||
def _fragment_file_id(target, src):
|
||||
return src.basename + "." + _hex32(hash(src.path)) + "." + _hex32(hash(str(target.label)))
|
||||
|
||||
def _output_path(ctx, target, src):
|
||||
package_root = _package_root(target.label)
|
||||
parts = [ctx.bin_dir.path]
|
||||
if package_root:
|
||||
parts.append(package_root)
|
||||
src_parts = src.short_path.split("/")
|
||||
parts.extend([
|
||||
"_compiledb_objs",
|
||||
target.label.name,
|
||||
])
|
||||
parts.extend(src_parts[:-1])
|
||||
parts.append(src_parts[-1] + ".o")
|
||||
return "/".join(parts)
|
||||
|
||||
def _rewrite_msvc_external_include_flags(args, compilation_context):
|
||||
external_include_paths = {
|
||||
path: True
|
||||
for path in compilation_context.external_includes.to_list()
|
||||
}
|
||||
if not external_include_paths:
|
||||
return args
|
||||
|
||||
rewritten = []
|
||||
skip_next = False
|
||||
for index in range(len(args)):
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
else:
|
||||
arg = args[index]
|
||||
if arg == "/I" and index + 1 < len(args):
|
||||
include_path = args[index + 1]
|
||||
if include_path in external_include_paths:
|
||||
rewritten.extend([
|
||||
"/external:I" + include_path,
|
||||
"/external:W0",
|
||||
])
|
||||
else:
|
||||
rewritten.extend([arg, include_path])
|
||||
skip_next = True
|
||||
elif arg.startswith("/I") and arg[2:] in external_include_paths:
|
||||
rewritten.extend([
|
||||
"/external:I" + arg[2:],
|
||||
"/external:W0",
|
||||
])
|
||||
else:
|
||||
rewritten.append(arg)
|
||||
|
||||
return rewritten
|
||||
|
||||
def _command_line_args(target, src, compiler, is_msvc, toolchain_flags, compilation_context, ctx):
|
||||
output_path = _output_path(ctx, target, src)
|
||||
|
||||
args = [compiler]
|
||||
args.extend(toolchain_flags)
|
||||
if is_msvc:
|
||||
args = _rewrite_msvc_external_include_flags(args, compilation_context)
|
||||
|
||||
if is_msvc:
|
||||
args.extend(["/c", src.path, "/Fo" + output_path])
|
||||
else:
|
||||
args.extend(["-c", src.path, "-o", output_path])
|
||||
|
||||
return args, output_path
|
||||
|
||||
def _emit_compile_command(ctx, target, src, command_line, output_path):
|
||||
file_id = _fragment_file_id(target, src)
|
||||
output = ctx.actions.declare_file("compiledb/" + file_id + ".compile_command.json")
|
||||
ctx.actions.write(
|
||||
output = output,
|
||||
content = json.encode({
|
||||
"file": src.path,
|
||||
"arguments": command_line,
|
||||
"output": output_path,
|
||||
"target": str(target.label),
|
||||
}),
|
||||
)
|
||||
return output
|
||||
|
||||
def _compiledb_aspect_impl(target, ctx):
|
||||
dep_outputs, dep_required_inputs = _collect_dep_compile_command_info(ctx)
|
||||
if CcInfo not in target:
|
||||
return [
|
||||
CompileCommandInfo(files = dep_outputs, required_inputs = dep_required_inputs),
|
||||
OutputGroupInfo(compiledb_report = depset(transitive = [dep_outputs, dep_required_inputs])),
|
||||
]
|
||||
|
||||
compilation_context = target[CcInfo].compilation_context
|
||||
srcs = _rule_sources(ctx)
|
||||
if not srcs:
|
||||
return [
|
||||
CompileCommandInfo(files = dep_outputs, required_inputs = dep_required_inputs),
|
||||
OutputGroupInfo(compiledb_report = depset(transitive = [dep_outputs, dep_required_inputs])),
|
||||
]
|
||||
|
||||
cc_toolchain = find_cpp_toolchain(ctx)
|
||||
requested_features, unsupported_features = _requested_and_unsupported_features(ctx)
|
||||
feature_configuration = cc_common.configure_features(
|
||||
ctx = ctx,
|
||||
cc_toolchain = cc_toolchain,
|
||||
requested_features = requested_features,
|
||||
unsupported_features = unsupported_features,
|
||||
)
|
||||
rule_compile_flags, rule_cxx_flags = _rule_compile_flags(ctx)
|
||||
c_user_compile_flags = _expand_flags(
|
||||
ctx,
|
||||
ctx.fragments.cpp.conlyopts + ctx.fragments.cpp.copts + rule_compile_flags,
|
||||
)
|
||||
cpp_user_compile_flags = _expand_flags(
|
||||
ctx,
|
||||
ctx.fragments.cpp.cxxopts + ctx.fragments.cpp.copts + rule_compile_flags + rule_cxx_flags,
|
||||
)
|
||||
c_compile_variables = _compile_variables(
|
||||
cc_toolchain,
|
||||
feature_configuration,
|
||||
compilation_context,
|
||||
c_user_compile_flags,
|
||||
)
|
||||
cpp_compile_variables = _compile_variables(
|
||||
cc_toolchain,
|
||||
feature_configuration,
|
||||
compilation_context,
|
||||
cpp_user_compile_flags,
|
||||
)
|
||||
compiler = _compiler_path(cc_toolchain)
|
||||
is_msvc = compiler.endswith("cl.exe") or compiler.endswith("/cl") or compiler.endswith("\\cl.exe")
|
||||
c_toolchain_flags = None
|
||||
cpp_toolchain_flags = None
|
||||
outputs = []
|
||||
for src in srcs:
|
||||
if src.extension == "c":
|
||||
if c_toolchain_flags == None:
|
||||
c_toolchain_flags = _toolchain_flags(
|
||||
feature_configuration,
|
||||
ACTION_NAMES.c_compile,
|
||||
c_compile_variables,
|
||||
)
|
||||
toolchain_flags = c_toolchain_flags
|
||||
else:
|
||||
if cpp_toolchain_flags == None:
|
||||
cpp_toolchain_flags = _toolchain_flags(
|
||||
feature_configuration,
|
||||
ACTION_NAMES.cpp_compile,
|
||||
cpp_compile_variables,
|
||||
)
|
||||
toolchain_flags = cpp_toolchain_flags
|
||||
|
||||
command_line, output_path = _command_line_args(
|
||||
target,
|
||||
src,
|
||||
compiler,
|
||||
is_msvc,
|
||||
toolchain_flags,
|
||||
compilation_context,
|
||||
ctx,
|
||||
)
|
||||
outputs.append(
|
||||
_emit_compile_command(
|
||||
ctx,
|
||||
target,
|
||||
src,
|
||||
command_line,
|
||||
output_path,
|
||||
),
|
||||
)
|
||||
all_outputs = depset(direct = outputs, transitive = [dep_outputs])
|
||||
required_inputs = depset(
|
||||
transitive = [
|
||||
dep_required_inputs,
|
||||
_required_compile_inputs(ctx, compilation_context, srcs),
|
||||
],
|
||||
)
|
||||
|
||||
return [
|
||||
CompileCommandInfo(files = all_outputs, required_inputs = required_inputs),
|
||||
OutputGroupInfo(compiledb_report = depset(transitive = [all_outputs, required_inputs])),
|
||||
]
|
||||
|
||||
compiledb_aspect = aspect(
|
||||
implementation = _compiledb_aspect_impl,
|
||||
fragments = ["cpp"],
|
||||
attrs = {
|
||||
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
|
||||
},
|
||||
toolchains = [
|
||||
"@bazel_tools//tools/cpp:toolchain_type",
|
||||
],
|
||||
attr_aspects = ["deps", "implementation_deps", "binary_with_debug"],
|
||||
required_providers = [CcInfo],
|
||||
)
|
||||
@ -13,6 +13,26 @@ load("//bazel/install_rules:providers.bzl", "TestBinaryInfo")
|
||||
load("//bazel/toolchains/cc:mongo_errors.bzl", "DWP_ERROR_MESSAGE")
|
||||
load("//bazel:transitions.bzl", "extensions_transition")
|
||||
|
||||
_WINDOWS_BINARY_EXTENSIONS = {
|
||||
".dll": True,
|
||||
".exe": True,
|
||||
".pdb": True,
|
||||
".ps1": True,
|
||||
}
|
||||
|
||||
_WINDOWS_DEBUG_EXTENSIONS = {
|
||||
".pdb": True,
|
||||
}
|
||||
|
||||
_LINUX_DEBUG_EXTENSIONS = {
|
||||
".debug": True,
|
||||
".dwp": True,
|
||||
}
|
||||
|
||||
_MACOS_DEBUG_EXTENSIONS = {
|
||||
".dSYM": True,
|
||||
}
|
||||
|
||||
# Used to skip rules on certain OS architectures
|
||||
def _empty_rule_impl(ctx):
|
||||
pass
|
||||
@ -108,7 +128,30 @@ def get_constraints(ctx):
|
||||
windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]
|
||||
return linux_constraint, macos_constraint, windows_constraint
|
||||
|
||||
def is_binary_file(ctx, basename):
|
||||
def _platform_kind(ctx):
|
||||
linux_constraint, macos_constraint, windows_constraint = get_constraints(ctx)
|
||||
if ctx.target_platform_has_constraint(linux_constraint):
|
||||
return "linux"
|
||||
if ctx.target_platform_has_constraint(macos_constraint):
|
||||
return "macos"
|
||||
if ctx.target_platform_has_constraint(windows_constraint):
|
||||
return "windows"
|
||||
ctx.fail("Unknown OS")
|
||||
return ""
|
||||
|
||||
def _basename(path):
|
||||
slash = path.rfind("/")
|
||||
if slash == -1:
|
||||
return path
|
||||
return path[slash + 1:]
|
||||
|
||||
def _extension(basename):
|
||||
dot = basename.rfind(".")
|
||||
if dot == -1:
|
||||
return ""
|
||||
return basename[dot:]
|
||||
|
||||
def is_binary_file(platform_kind, basename):
|
||||
"""Check if file looks like a binary
|
||||
|
||||
Args:
|
||||
@ -118,18 +161,16 @@ def is_binary_file(ctx, basename):
|
||||
Returns:
|
||||
True if it looks like a binary, False otherwise
|
||||
"""
|
||||
linux_constraint, macos_constraint, windows_constraint = get_constraints(ctx)
|
||||
if ctx.target_platform_has_constraint(linux_constraint):
|
||||
if platform_kind == "linux":
|
||||
return not (basename.startswith("lib") or basename.startswith("mongo_crypt_v") or basename.startswith("stitch_support.so"))
|
||||
elif ctx.target_platform_has_constraint(macos_constraint):
|
||||
elif platform_kind == "macos":
|
||||
return not (basename.startswith("lib") or basename.startswith("mongo_crypt_v") or basename.startswith("stitch_support.dylib"))
|
||||
elif ctx.target_platform_has_constraint(windows_constraint):
|
||||
return basename.endswith(".exe") or basename.endswith(".pdb") or basename.endswith(".dll") or basename.endswith(".ps1")
|
||||
elif platform_kind == "windows":
|
||||
return _extension(basename) in _WINDOWS_BINARY_EXTENSIONS
|
||||
else:
|
||||
ctx.fail("Unknown OS")
|
||||
return False
|
||||
|
||||
def is_debug_file(ctx, basename):
|
||||
def is_debug_file(platform_kind, basename):
|
||||
"""Check if file looks a debug file
|
||||
|
||||
Args:
|
||||
@ -139,15 +180,14 @@ def is_debug_file(ctx, basename):
|
||||
Returns:
|
||||
True if it looks like a debug file, False otherwise
|
||||
"""
|
||||
linux_constraint, macos_constraint, windows_constraint = get_constraints(ctx)
|
||||
if ctx.target_platform_has_constraint(linux_constraint):
|
||||
return basename.endswith(".debug") or basename.endswith(".dwp")
|
||||
elif ctx.target_platform_has_constraint(macos_constraint):
|
||||
return basename.endswith(".dSYM")
|
||||
elif ctx.target_platform_has_constraint(windows_constraint):
|
||||
return basename.endswith(".pdb")
|
||||
ext = _extension(basename)
|
||||
if platform_kind == "linux":
|
||||
return ext in _LINUX_DEBUG_EXTENSIONS
|
||||
elif platform_kind == "macos":
|
||||
return ext in _MACOS_DEBUG_EXTENSIONS
|
||||
elif platform_kind == "windows":
|
||||
return ext in _WINDOWS_DEBUG_EXTENSIONS
|
||||
else:
|
||||
ctx.fail("Unknown OS")
|
||||
return False
|
||||
|
||||
def declare_output(ctx, output, is_directory):
|
||||
@ -166,7 +206,7 @@ def declare_output(ctx, output, is_directory):
|
||||
else:
|
||||
return ctx.actions.declare_file(output)
|
||||
|
||||
def sort_file(ctx, file, install_dir, file_map, is_directory):
|
||||
def sort_file(ctx, file, basename, install_dir, file_map, is_directory, platform_kind):
|
||||
"""Determine location a file should be installed to
|
||||
|
||||
Args:
|
||||
@ -177,24 +217,28 @@ def sort_file(ctx, file, install_dir, file_map, is_directory):
|
||||
is_directory: determines if the file is a directory
|
||||
|
||||
"""
|
||||
_, macos_constraint, _ = get_constraints(ctx)
|
||||
basename = paths.basename(file)
|
||||
bin_install = install_dir + "/bin/" + basename
|
||||
if bin_install.endswith(".dwp"):
|
||||
install_basename = basename
|
||||
ext = _extension(basename)
|
||||
if ext == ".dwp":
|
||||
# Due to us creating our binaries using the _with_debug name
|
||||
# the dwp files also contain it. Strip the _with_debug from the name
|
||||
bin_install = bin_install.replace("_with_debug.dwp", ".dwp")
|
||||
install_basename = install_basename.replace("_with_debug.dwp", ".dwp")
|
||||
|
||||
lib_install = install_dir + "/lib/" + basename
|
||||
bin_install = install_dir + "/bin/" + install_basename
|
||||
|
||||
if is_binary_file(ctx, basename) or basename.endswith(".py"):
|
||||
if not is_debug_file(ctx, basename):
|
||||
lib_install = install_dir + "/lib/" + install_basename
|
||||
is_binary = is_binary_file(platform_kind, basename)
|
||||
is_debug = is_debug_file(platform_kind, basename)
|
||||
is_python = ext == ".py"
|
||||
|
||||
if is_binary or is_python:
|
||||
if not is_debug:
|
||||
if ctx.attr.debug != "debug":
|
||||
file_map["binaries"][file] = declare_output(ctx, bin_install, is_directory)
|
||||
elif ctx.attr.debug != "stripped" or ctx.attr.publish_debug_in_stripped:
|
||||
file_map["binaries_debug"][file] = declare_output(ctx, bin_install, is_directory)
|
||||
|
||||
elif not is_debug_file(ctx, basename):
|
||||
elif not is_debug:
|
||||
if ctx.attr.debug != "debug":
|
||||
file_map["dynamic_libs"][file] = declare_output(ctx, lib_install, is_directory)
|
||||
|
||||
@ -225,19 +269,24 @@ def mongo_install_rule_impl(ctx):
|
||||
outputs = []
|
||||
dwps = []
|
||||
install_dir = ctx.label.name
|
||||
platform_kind = _platform_kind(ctx)
|
||||
install_script = ctx.attr._install_script.files.to_list()[0]
|
||||
|
||||
# sort direct sources
|
||||
for input_bin in ctx.attr.srcs:
|
||||
if DebugPackageInfo in input_bin and ctx.attr.create_dwp and ctx.attr.debug != "stripped":
|
||||
bin = input_bin[DebugPackageInfo].dwp_file
|
||||
dwps.append(bin)
|
||||
sort_file(ctx, bin.path, install_dir, file_map, bin.is_directory)
|
||||
test_files.extend(input_bin[TestBinaryInfo].test_binaries.to_list())
|
||||
for bin in input_bin.files.to_list():
|
||||
sort_file(ctx, bin.path, install_dir, file_map, bin.is_directory)
|
||||
sort_file(ctx, bin.path, bin.basename, install_dir, file_map, bin.is_directory, platform_kind)
|
||||
input_test_binaries = input_bin[TestBinaryInfo].test_binaries.to_list()
|
||||
input_files = input_bin.files.to_list()
|
||||
test_files.extend(input_test_binaries)
|
||||
for bin in input_files:
|
||||
sort_file(ctx, bin.path, bin.basename, install_dir, file_map, bin.is_directory, platform_kind)
|
||||
|
||||
for input_label, output_folder in ctx.attr.root_files.items():
|
||||
for file in input_label.files.to_list():
|
||||
label_files = input_label.files.to_list()
|
||||
for file in label_files:
|
||||
file_map["root_files"][file.path] = declare_output(ctx, install_dir + "/" + output_folder + "/" + file.basename, file.is_directory)
|
||||
|
||||
for input_label, output_path in ctx.attr.include_files.items():
|
||||
@ -246,24 +295,25 @@ def mongo_install_rule_impl(ctx):
|
||||
|
||||
# sort dependency install files
|
||||
for dep in ctx.attr.deps:
|
||||
test_files.extend(dep[TestBinaryInfo].test_binaries.to_list())
|
||||
dep_test_binaries = dep[TestBinaryInfo].test_binaries.to_list()
|
||||
dep_default_files = dep[DefaultInfo].files.to_list()
|
||||
dep_src_map_file = dep[MongoInstallInfo].src_map.to_list()[0]
|
||||
test_files.extend(dep_test_binaries)
|
||||
|
||||
# Create a map of filename to if its a directory, ie. { coolfolder: True, coolfile: False } as the json loses that info
|
||||
file_directory_map = {file_dep.basename: file_dep.is_directory for file_dep in dep[DefaultInfo].files.to_list()}
|
||||
src_map = json.decode(dep[MongoInstallInfo].src_map.to_list()[0])
|
||||
files = []
|
||||
file_directory_map = {file_dep.basename: file_dep.is_directory for file_dep in dep_default_files}
|
||||
src_map = json.decode(dep_src_map_file)
|
||||
for key in src_map:
|
||||
if key != "roots":
|
||||
files.extend(src_map[key])
|
||||
for file in files:
|
||||
filename = file.split("/")[-1]
|
||||
for file in src_map[key]:
|
||||
filename = _basename(file)
|
||||
|
||||
# Due to us creating our binaries using the _with_debug name
|
||||
# the dwp files also contain it. Strip the _with_debug from the name
|
||||
filename = filename.replace("_with_debug.dwp", ".dwp")
|
||||
sort_file(ctx, file, install_dir, file_map, file_directory_map[filename])
|
||||
# Due to us creating our binaries using the _with_debug name
|
||||
# the dwp files also contain it. Strip the _with_debug from the name
|
||||
filename = filename.replace("_with_debug.dwp", ".dwp")
|
||||
sort_file(ctx, file, filename, install_dir, file_map, file_directory_map[filename], platform_kind)
|
||||
for file, folder in src_map["roots"].items():
|
||||
filename = file.split("/")[-1]
|
||||
filename = _basename(file)
|
||||
file_map["root_files"][file] = declare_output(ctx, install_dir + "/" + folder + "/" + filename, file_directory_map[filename])
|
||||
|
||||
# aggregate based on type of installs
|
||||
@ -287,8 +337,9 @@ def mongo_install_rule_impl(ctx):
|
||||
input_deps = []
|
||||
installed_tests = []
|
||||
for file in test_files:
|
||||
if not is_debug_file(ctx, file.basename) and ctx.attr.debug != "debug":
|
||||
if is_binary_file(ctx, file.basename) or file.basename.endswith(".py"):
|
||||
file_basename = file.basename
|
||||
if not is_debug_file(platform_kind, file_basename) and ctx.attr.debug != "debug":
|
||||
if is_binary_file(platform_kind, file_basename) or _extension(file_basename) == ".py":
|
||||
test_path = file_map["binaries"][file.path].path
|
||||
|
||||
# point at the binaries in bazel-bin/install/ rather than bazel-out/<some-arch>/bin/<some-install>/
|
||||
@ -378,7 +429,7 @@ def mongo_install_rule_impl(ctx):
|
||||
outputs = outputs,
|
||||
inputs = inputs,
|
||||
arguments = [
|
||||
ctx.attr._install_script.files.to_list()[0].path,
|
||||
install_script.path,
|
||||
"--depfile=" + deps_file.path,
|
||||
"--install-dir=" + full_install_dir,
|
||||
] + ["--depfile=" + str(dep[MongoInstallInfo].deps_files.to_list()[0].path) for dep in ctx.attr.deps],
|
||||
|
||||
@ -34,6 +34,8 @@ load("//bazel/config:generate_config_header.bzl", "generate_config_header")
|
||||
load("//bazel/auto_header:auto_header.bzl", "binary_srcs_with_all_headers", "build_selects_and_flat_files", "concat_selects", "dedupe_preserve_order", "maybe_all_headers", "maybe_compute_auto_headers", "strings_only")
|
||||
load("//bazel:test_exec_properties.bzl", "test_exec_properties")
|
||||
|
||||
COMPILEDB_TAG = "mongo_compiledb"
|
||||
|
||||
# These will throw an error if the following condition is not met:
|
||||
# (libunwind == on && os == linux) || libunwind == off || libunwind == auto
|
||||
LIBUNWIND_DEPS = select({
|
||||
@ -456,7 +458,7 @@ def mongo_cc_library(
|
||||
copts = copts,
|
||||
cxxopts = cxxopts,
|
||||
data = data,
|
||||
tags = tags + ["mongo_library", "check_symbol_target"],
|
||||
tags = tags + ["mongo_library", "check_symbol_target", COMPILEDB_TAG],
|
||||
linkopts = linkopts,
|
||||
linkstatic = select({
|
||||
"@platforms//os:windows": True,
|
||||
@ -708,7 +710,7 @@ def _mongo_cc_binary_and_test(
|
||||
# we dont want the intermediate build targets to be picked up by tags
|
||||
# so we empty it out
|
||||
original_tags = list(args["tags"])
|
||||
args["tags"] = ["intermediate_debug"] + [
|
||||
args["tags"] = ["intermediate_debug", COMPILEDB_TAG] + [
|
||||
tag + "_debug" if
|
||||
# Transformations via `test_exec_properties` have already been applied at this point.
|
||||
# Need to leave cpu tags unchanged, since more parsing validation is done deeper in bazel.
|
||||
|
||||
@ -31,17 +31,20 @@ def get_inputs_and_outputs(ctx, shared_ext, static_ext, debug_ext):
|
||||
if len(input_files) == 0:
|
||||
return None, None, None, None
|
||||
if ctx.attr.type == "library":
|
||||
for file in ctx.attr.binary_with_debug.files.to_list():
|
||||
shared_input = None
|
||||
for file in input_files:
|
||||
if file.path.endswith(WITH_DEBUG_SUFFIX + static_ext):
|
||||
static_lib = file
|
||||
elif file.path.endswith(WITH_DEBUG_SUFFIX + shared_ext):
|
||||
shared_input = file
|
||||
|
||||
if ctx.attr.cc_shared_library != None:
|
||||
for file in ctx.attr.cc_shared_library.files.to_list():
|
||||
if file.path.endswith(WITH_DEBUG_SUFFIX + shared_ext):
|
||||
shared_lib = file
|
||||
|
||||
if file.path.endswith(WITH_DEBUG_SUFFIX + shared_ext) or shared_lib:
|
||||
basename = file.basename[:-len(WITH_DEBUG_SUFFIX + shared_ext)]
|
||||
if shared_input or shared_lib:
|
||||
basename = shared_input.basename[:-len(WITH_DEBUG_SUFFIX + shared_ext)] if shared_input else ""
|
||||
if shared_lib:
|
||||
basename = shared_lib.basename[:-len(WITH_DEBUG_SUFFIX + shared_ext + CC_SHARED_LIBRARY_SUFFIX)]
|
||||
|
||||
@ -53,7 +56,7 @@ def get_inputs_and_outputs(ctx, shared_ext, static_ext, debug_ext):
|
||||
else:
|
||||
debug_info = None
|
||||
output_bin = ctx.actions.declare_file(basename + shared_ext)
|
||||
input_bin = file
|
||||
input_bin = shared_input
|
||||
if shared_lib:
|
||||
input_bin = shared_lib
|
||||
else:
|
||||
@ -61,7 +64,7 @@ def get_inputs_and_outputs(ctx, shared_ext, static_ext, debug_ext):
|
||||
output_bin = None
|
||||
input_bin = None
|
||||
elif ctx.attr.type == "program":
|
||||
program_bin = ctx.attr.binary_with_debug.files.to_list()[0]
|
||||
program_bin = input_files[0]
|
||||
|
||||
basename = program_bin.basename[:-len(WITH_DEBUG_SUFFIX)]
|
||||
if ctx.attr.enabled:
|
||||
@ -157,6 +160,7 @@ def create_new_ccinfo_library(ctx, cc_toolchain, shared_lib, static_lib, cc_shar
|
||||
linker_input_deps.append(dep[CcInfo].linking_context.linker_inputs)
|
||||
|
||||
if shared_lib or static_lib:
|
||||
binary_linker_inputs = ctx.attr.binary_with_debug[CcInfo].linking_context.linker_inputs.to_list()
|
||||
if shared_lib:
|
||||
so_path = shared_lib.path.replace(ctx.bin_dir.path + "/", "")
|
||||
else:
|
||||
@ -180,8 +184,8 @@ def create_new_ccinfo_library(ctx, cc_toolchain, shared_lib, static_lib, cc_shar
|
||||
#
|
||||
# This solution may break in the case where a base dependency contains only one positional argument,
|
||||
# but this should never happen since we will always inject at least one non positional argument globally.
|
||||
cur_flags = ctx.attr.binary_with_debug[CcInfo].linking_context.linker_inputs.to_list()[0].user_link_flags
|
||||
for dep in ctx.attr.binary_with_debug[CcInfo].linking_context.linker_inputs.to_list()[1:]:
|
||||
cur_flags = list(binary_linker_inputs[0].user_link_flags)
|
||||
for dep in binary_linker_inputs[1:]:
|
||||
for i in range(len(cur_flags)):
|
||||
dep_flags = dep.user_link_flags
|
||||
if dep_flags and cur_flags:
|
||||
@ -237,7 +241,8 @@ def create_new_cc_shared_library_info(ctx, cc_toolchain, output_shared_lib, orig
|
||||
# cc_library's linkopts field for both static and dynamic transitive link opts
|
||||
# cc_shared_library's user_link_flags field for dynamic non-transitive link opts
|
||||
all_user_link_flags = dict()
|
||||
for input in ctx.attr.binary_with_debug[CcInfo].linking_context.linker_inputs.to_list():
|
||||
binary_linker_inputs = ctx.attr.binary_with_debug[CcInfo].linking_context.linker_inputs.to_list()
|
||||
for input in binary_linker_inputs:
|
||||
for flag in input.user_link_flags:
|
||||
all_user_link_flags[flag] = True
|
||||
|
||||
@ -350,14 +355,15 @@ def linux_extraction(ctx, cc_toolchain, inputs):
|
||||
# build-without-the-bytes enabled, these aren't downloaded. Manually collect them and add them to the
|
||||
# output set.
|
||||
dynamic_deps_runfiles = ctx.runfiles(files = [])
|
||||
transitive_debug_files = get_transitive_debug_files(ctx.attr.deps)
|
||||
if ctx.attr.type == "program":
|
||||
dynamic_deps = get_transitive_dyn_libs(ctx.attr.deps)
|
||||
dynamic_deps_runfiles = ctx.attr.binary_with_debug[DefaultInfo].data_runfiles.merge(ctx.runfiles(files = get_transitive_dyn_libs(ctx.attr.deps)))
|
||||
dynamic_deps_runfiles = ctx.attr.binary_with_debug[DefaultInfo].data_runfiles.merge(ctx.runfiles(files = dynamic_deps))
|
||||
outputs.extend(dynamic_deps)
|
||||
|
||||
provided_info = [
|
||||
DefaultInfo(
|
||||
files = depset(outputs, transitive = [depset(get_transitive_debug_files(ctx.attr.deps))]),
|
||||
files = depset(outputs, transitive = [depset(transitive_debug_files)]),
|
||||
runfiles = dynamic_deps_runfiles,
|
||||
executable = output_bin if ctx.attr.type == "program" else None,
|
||||
),
|
||||
@ -437,7 +443,7 @@ def macos_extraction(ctx, cc_toolchain, inputs):
|
||||
dynamic_deps_runfiles = ctx.runfiles(files = [])
|
||||
if ctx.attr.type == "program":
|
||||
dynamic_deps = get_transitive_dyn_libs(ctx.attr.deps)
|
||||
dynamic_deps_runfiles = ctx.attr.binary_with_debug[DefaultInfo].data_runfiles.merge(ctx.runfiles(files = get_transitive_dyn_libs(ctx.attr.deps)))
|
||||
dynamic_deps_runfiles = ctx.attr.binary_with_debug[DefaultInfo].data_runfiles.merge(ctx.runfiles(files = dynamic_deps))
|
||||
outputs.extend(dynamic_deps)
|
||||
|
||||
provided_info = [
|
||||
@ -483,10 +489,10 @@ def windows_extraction(ctx, cc_toolchain, inputs):
|
||||
output_dynamic_library = None
|
||||
|
||||
if len(input_file):
|
||||
basename = ctx.attr.binary_with_debug.files.to_list()[0].basename[:-len(WITH_DEBUG_SUFFIX + ext)]
|
||||
basename = input_file[0].basename[:-len(WITH_DEBUG_SUFFIX + ext)]
|
||||
output = ctx.actions.declare_file(basename + ext)
|
||||
|
||||
for input in ctx.attr.binary_with_debug.files.to_list():
|
||||
for input in input_file:
|
||||
ext = "." + input.extension
|
||||
|
||||
basename = input.basename[:-len(WITH_DEBUG_SUFFIX + ext)]
|
||||
@ -516,6 +522,7 @@ def windows_extraction(ctx, cc_toolchain, inputs):
|
||||
)
|
||||
|
||||
if pdb:
|
||||
pdb_files = pdb.to_list()
|
||||
if ctx.attr.cc_shared_library != None:
|
||||
basename = input.basename[:-len(WITH_DEBUG_SUFFIX + ".pdb")]
|
||||
pdb_output = ctx.actions.declare_file(basename + ".dll.pdb")
|
||||
@ -526,7 +533,7 @@ def windows_extraction(ctx, cc_toolchain, inputs):
|
||||
|
||||
ctx.actions.symlink(
|
||||
output = pdb_output,
|
||||
target_file = pdb.to_list()[0],
|
||||
target_file = pdb_files[0],
|
||||
)
|
||||
|
||||
if ctx.attr.shared_archive:
|
||||
|
||||
@ -60,18 +60,29 @@ def get_bazel_labels_from_tags(args, bazel, tag):
|
||||
extra_args = [arg for arg in args if arg.startswith("--")]
|
||||
# The .cquery file is used to get info on which targets are compatible
|
||||
# with our current config. Without it dependent targets would just be skipped.
|
||||
query_args = [
|
||||
f"kind(extract_debuginfo_test, attr(tags, '\\b{tag}\\b', //src/...))",
|
||||
"--output=starlark",
|
||||
"--starlark:file=bazel/wrapper_hook/target_compatable.cquery",
|
||||
]
|
||||
proc = subprocess.run(
|
||||
[bazel, "cquery", "--config=local"]
|
||||
+ extra_args
|
||||
+ [
|
||||
f"kind(extract_debuginfo_test, attr(tags, '\\b{tag}\\b', //src/...))",
|
||||
"--output=starlark",
|
||||
"--starlark:file=bazel/wrapper_hook/target_compatable.cquery",
|
||||
],
|
||||
[bazel, "cquery", "--remote_executor="] + extra_args + query_args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
print(
|
||||
"WARNING: Autogenerated query failed with remote cache/downloader enabled; "
|
||||
"retrying with `--config=local`.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
proc = subprocess.run(
|
||||
[bazel, "cquery", "--config=local"] + extra_args + query_args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
print("ERROR: Autogenerated query failed:")
|
||||
print(proc.stderr)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
182
bazel/wrapper_hook/compiledb_postprocess.py
Normal file
182
bazel/wrapper_hook/compiledb_postprocess.py
Normal file
@ -0,0 +1,182 @@
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
|
||||
COMPILE_COMMAND_FRAGMENT_EXT = ".compile_command.json"
|
||||
|
||||
|
||||
def _path_for_open(path):
|
||||
normalized = os.path.normpath(path)
|
||||
if os.name != "nt":
|
||||
return normalized
|
||||
|
||||
# Bazel fragment paths can exceed MAX_PATH on Windows even when the build succeeds.
|
||||
# Use the extended-length path prefix so Python can open them reliably.
|
||||
if normalized.startswith("\\\\?\\"):
|
||||
return normalized
|
||||
if not os.path.isabs(normalized):
|
||||
return normalized
|
||||
if normalized.startswith("\\\\"):
|
||||
return "\\\\?\\UNC\\" + normalized[2:]
|
||||
return "\\\\?\\" + normalized
|
||||
|
||||
|
||||
def _bep_file_path(file_entry):
|
||||
if "pathPrefix" in file_entry and "name" in file_entry:
|
||||
return os.path.normpath(
|
||||
os.path.join(*(file_entry.get("pathPrefix", []) + [file_entry["name"]]))
|
||||
)
|
||||
|
||||
uri = file_entry.get("uri")
|
||||
if not uri:
|
||||
return None
|
||||
|
||||
parsed = urlparse(uri)
|
||||
if parsed.scheme != "file":
|
||||
return None
|
||||
path = unquote(parsed.path)
|
||||
if os.name == "nt" and len(path) >= 3 and path[0] == "/" and path[2] == ":":
|
||||
path = path[1:]
|
||||
return os.path.normpath(path)
|
||||
|
||||
|
||||
def collect_compile_command_fragments(build_event_json):
|
||||
fragment_paths = set()
|
||||
with open(_path_for_open(build_event_json), "r", encoding="utf-8") as events:
|
||||
for line in events:
|
||||
if not line.strip():
|
||||
continue
|
||||
event = json.loads(line)
|
||||
named_set = event.get("namedSetOfFiles")
|
||||
if not named_set:
|
||||
continue
|
||||
for file_entry in named_set.get("files", []):
|
||||
path = _bep_file_path(file_entry)
|
||||
if path and path.endswith(COMPILE_COMMAND_FRAGMENT_EXT):
|
||||
fragment_paths.add(path)
|
||||
|
||||
return sorted(fragment_paths)
|
||||
|
||||
|
||||
def _resolve_fragment_path(fragment_path, output_base=None):
|
||||
path = pathlib.Path(fragment_path)
|
||||
if path.is_absolute() or path.exists():
|
||||
return str(path)
|
||||
if output_base is None:
|
||||
return str(path)
|
||||
|
||||
output_base_path = pathlib.Path(output_base)
|
||||
if fragment_path.startswith("external/"):
|
||||
return str(output_base_path / fragment_path)
|
||||
return str(output_base_path / "execroot" / "_main" / fragment_path)
|
||||
|
||||
|
||||
def collect_compile_command_fragments_from_roots(search_roots):
|
||||
fragment_paths = set()
|
||||
for root in search_roots:
|
||||
root_path = pathlib.Path(root)
|
||||
if not root_path.exists():
|
||||
continue
|
||||
for fragment in root_path.rglob(f"*{COMPILE_COMMAND_FRAGMENT_EXT}"):
|
||||
fragment_paths.add(str(fragment))
|
||||
return sorted(fragment_paths)
|
||||
|
||||
|
||||
def load_compile_command_fragments(build_event_json, search_roots=None, output_base=None):
|
||||
fragment_paths = collect_compile_command_fragments(build_event_json)
|
||||
if not fragment_paths and search_roots:
|
||||
fragment_paths = collect_compile_command_fragments_from_roots(search_roots)
|
||||
|
||||
entries = []
|
||||
for fragment in fragment_paths:
|
||||
resolved_fragment = _resolve_fragment_path(fragment, output_base=output_base)
|
||||
with open(_path_for_open(resolved_fragment), "r", encoding="utf-8") as infile:
|
||||
fragment_data = json.load(infile)
|
||||
if isinstance(fragment_data, list):
|
||||
entries.extend(fragment_data)
|
||||
else:
|
||||
entries.append(fragment_data)
|
||||
return entries
|
||||
|
||||
|
||||
def load_compile_command_fragments_from_paths(fragment_paths):
|
||||
entries = []
|
||||
for fragment in sorted(fragment_paths):
|
||||
with open(_path_for_open(fragment), "r", encoding="utf-8") as infile:
|
||||
fragment_data = json.load(infile)
|
||||
if isinstance(fragment_data, list):
|
||||
entries.extend(fragment_data)
|
||||
else:
|
||||
entries.append(fragment_data)
|
||||
return entries
|
||||
|
||||
|
||||
def _entry_key(entry):
|
||||
return (
|
||||
entry.get("file", ""),
|
||||
entry.get("output", ""),
|
||||
entry.get("target", ""),
|
||||
)
|
||||
|
||||
|
||||
def compile_command_sort_key(entry):
|
||||
return (
|
||||
entry.get("file", ""),
|
||||
entry.get("output", ""),
|
||||
entry.get("target", ""),
|
||||
entry.get("arguments", []),
|
||||
)
|
||||
|
||||
|
||||
def merge_compile_commands(existing_entries, new_entries):
|
||||
updated_targets = {
|
||||
entry.get("target")
|
||||
for entry in new_entries
|
||||
if isinstance(entry.get("target"), str) and entry.get("target")
|
||||
}
|
||||
new_keys = {_entry_key(entry) for entry in new_entries}
|
||||
new_file_output_keys = {
|
||||
(
|
||||
entry.get("file", ""),
|
||||
entry.get("output", ""),
|
||||
)
|
||||
for entry in new_entries
|
||||
}
|
||||
|
||||
merged = []
|
||||
for entry in existing_entries:
|
||||
if _entry_key(entry) in new_keys:
|
||||
continue
|
||||
if (
|
||||
entry.get("file", ""),
|
||||
entry.get("output", ""),
|
||||
) in new_file_output_keys:
|
||||
continue
|
||||
if updated_targets and entry.get("target") in updated_targets:
|
||||
continue
|
||||
merged.append(entry)
|
||||
|
||||
merged.extend(new_entries)
|
||||
merged.sort(key=compile_command_sort_key)
|
||||
return merged
|
||||
|
||||
|
||||
def write_compile_commands(entries, output_path):
|
||||
output = pathlib.Path(output_path)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
json_str = json.dumps(entries, separators=(",", ":"), ensure_ascii=False)
|
||||
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
dir=output.parent,
|
||||
delete=False,
|
||||
) as tmp:
|
||||
tmp.write(json_str)
|
||||
tmp_path = pathlib.Path(tmp.name)
|
||||
|
||||
os.replace(tmp_path, output)
|
||||
return True
|
||||
@ -12,7 +12,10 @@ WRAPPER_CONFIG_MODE_FILE = f"{REPO_ROOT}/.tmp/mongo_wrapper_config_mode"
|
||||
|
||||
sys.path.append(str(REPO_ROOT))
|
||||
|
||||
from bazel.wrapper_hook.compiledb import generate_compiledb
|
||||
from bazel.wrapper_hook.compiledb import (
|
||||
clear_compiledb_posthook_state,
|
||||
generate_compiledb,
|
||||
)
|
||||
from bazel.wrapper_hook.lint import run_rules_lint
|
||||
from bazel.wrapper_hook.wrapper_debug import wrapper_debug
|
||||
|
||||
@ -63,13 +66,62 @@ def check_bazel_command_type(args):
|
||||
return arg
|
||||
|
||||
|
||||
def swap_default_config(args, command, config_mode, compiledb_target, clang_tidy):
|
||||
def _read_target_pattern_file(path):
|
||||
with open(path, "r", encoding="utf-8") as target_file:
|
||||
return [
|
||||
line.strip()
|
||||
for line in target_file
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
]
|
||||
|
||||
|
||||
def _parse_targets_and_flags(args, replacements, compiledb_targets, compiledb_only_targets):
|
||||
build_flags = []
|
||||
build_targets = []
|
||||
target_pattern_file = None
|
||||
parsing_targets = True
|
||||
expect_target_pattern_file_arg = False
|
||||
|
||||
for arg in args:
|
||||
if expect_target_pattern_file_arg:
|
||||
target_pattern_file = arg
|
||||
build_flags.append(arg)
|
||||
expect_target_pattern_file_arg = False
|
||||
continue
|
||||
if arg == "--":
|
||||
parsing_targets = False
|
||||
continue
|
||||
if arg in replacements:
|
||||
continue
|
||||
if parsing_targets and arg.startswith("-"):
|
||||
if arg == "--target_pattern_file":
|
||||
build_flags.append(arg)
|
||||
expect_target_pattern_file_arg = True
|
||||
continue
|
||||
if arg.startswith("--target_pattern_file="):
|
||||
target_pattern_file = arg.split("=", 1)[1]
|
||||
if arg == "--config=compiledb-aspect":
|
||||
arg = "--config=compiledb"
|
||||
build_flags.append(arg)
|
||||
elif parsing_targets:
|
||||
if arg in compiledb_targets or arg in compiledb_only_targets:
|
||||
continue
|
||||
build_targets.append(arg)
|
||||
|
||||
return build_flags, build_targets, target_pattern_file
|
||||
|
||||
|
||||
def swap_default_config(
|
||||
args, command, config_mode, compiledb_target, clang_tidy, user_specified_config
|
||||
):
|
||||
# Remember the user's last specified config mode to prevent invalidating cache on run or lint commands.
|
||||
if os.path.exists(f"{REPO_ROOT}/.bazelrc.local"):
|
||||
return config_mode
|
||||
|
||||
try:
|
||||
if config_mode is None:
|
||||
if user_specified_config:
|
||||
return config_mode
|
||||
if os.path.exists(WRAPPER_CONFIG_MODE_FILE):
|
||||
# Reset to fastbuild if it's been more than 2 days since the file was written,
|
||||
# since we don't want users to stay locked on dbg/opt if they forgot to change it back
|
||||
@ -111,10 +163,13 @@ def test_runner_interface(
|
||||
plus_starts = ("+", ":+", "//:+")
|
||||
skip_plus_interface = True
|
||||
compiledb_target = False
|
||||
setup_clang_tidy = False
|
||||
clang_tidy = False
|
||||
lint_target = False
|
||||
persistent_compdb = True
|
||||
compiledb_targets = ["//:compiledb", ":compiledb", "compiledb"]
|
||||
compiledb_only_targets = ["//:compiledb_only", ":compiledb_only", "compiledb_only"]
|
||||
compiledb_target_scope = None
|
||||
lint_targets = ["//:lint", ":lint", "lint"]
|
||||
sources_to_bin = {}
|
||||
select_sources = {}
|
||||
@ -128,6 +183,11 @@ def test_runner_interface(
|
||||
source_targets = {}
|
||||
|
||||
current_bazel_command = check_bazel_command_type(args)
|
||||
command_index = next(
|
||||
(i for i, arg in enumerate(args) if arg == current_bazel_command),
|
||||
1,
|
||||
)
|
||||
startup_args = args[1:command_index]
|
||||
|
||||
if autocomplete_query:
|
||||
str_args = " ".join(args)
|
||||
@ -138,17 +198,41 @@ def test_runner_interface(
|
||||
persistent_compdb = False
|
||||
|
||||
config_mode = None
|
||||
for arg in args:
|
||||
user_specified_config = False
|
||||
for index, arg in enumerate(args):
|
||||
if index > 0 and args[index - 1] == "--config":
|
||||
continue
|
||||
if arg in compiledb_targets:
|
||||
compiledb_target = True
|
||||
setup_clang_tidy = True
|
||||
replacements[arg] = []
|
||||
skip_plus_interface = False
|
||||
if arg in compiledb_only_targets:
|
||||
compiledb_target = True
|
||||
replacements[arg] = []
|
||||
skip_plus_interface = False
|
||||
if arg in lint_targets:
|
||||
lint_target = True
|
||||
if arg.startswith("--compiledb-target-scope="):
|
||||
compiledb_target_scope = arg.split("=", 1)[1]
|
||||
replacements[arg] = []
|
||||
skip_plus_interface = False
|
||||
if arg.startswith("--compiledb_target_scope="):
|
||||
compiledb_target_scope = arg.split("=", 1)[1]
|
||||
replacements[arg] = []
|
||||
skip_plus_interface = False
|
||||
if arg == "--intree_compdb":
|
||||
replacements[arg] = []
|
||||
persistent_compdb = False
|
||||
skip_plus_interface = False
|
||||
if "--config=" in arg:
|
||||
val = arg.split("=")[1]
|
||||
config_value = None
|
||||
if arg.startswith("--config="):
|
||||
config_value = arg.split("=", 1)[1]
|
||||
elif arg == "--config" and index + 1 < len(args):
|
||||
config_value = args[index + 1]
|
||||
if config_value is not None:
|
||||
user_specified_config = True
|
||||
val = config_value
|
||||
if val in {"opt", "dbg", "fastbuild", "dbg_aubsan", "dbg_tsan"}:
|
||||
config_mode = val
|
||||
if val == "clang-tidy":
|
||||
@ -159,8 +243,17 @@ def test_runner_interface(
|
||||
catch_all_target = True
|
||||
|
||||
config_mode = swap_default_config(
|
||||
args, current_bazel_command, config_mode, compiledb_target, clang_tidy
|
||||
args,
|
||||
current_bazel_command,
|
||||
config_mode,
|
||||
compiledb_target,
|
||||
clang_tidy,
|
||||
user_specified_config,
|
||||
)
|
||||
clear_compiledb_posthook_state()
|
||||
|
||||
if platform.system() == "Windows":
|
||||
setup_clang_tidy = False
|
||||
|
||||
for arg in args:
|
||||
if arg.startswith("--runs_per_test=") and catch_all_target:
|
||||
@ -175,8 +268,32 @@ def test_runner_interface(
|
||||
except ValueError:
|
||||
pass # Non-integer value, let bazel handle the error
|
||||
|
||||
parsed_build_flags = None
|
||||
parsed_build_targets = None
|
||||
parsed_target_pattern_file = None
|
||||
if current_bazel_command == "build" and compiledb_target:
|
||||
parsed_build_flags, parsed_build_targets, parsed_target_pattern_file = (
|
||||
_parse_targets_and_flags(
|
||||
args[command_index + 1 :], replacements, compiledb_targets, compiledb_only_targets
|
||||
)
|
||||
)
|
||||
|
||||
if compiledb_target:
|
||||
generate_compiledb(args[0], persistent_compdb, enterprise, atlas)
|
||||
generate_compiledb(
|
||||
args[0],
|
||||
persistent_compdb,
|
||||
enterprise,
|
||||
atlas,
|
||||
target_scope_override=compiledb_target_scope,
|
||||
setup_clang_tidy=setup_clang_tidy,
|
||||
startup_args=startup_args,
|
||||
)
|
||||
if (
|
||||
current_bazel_command == "build"
|
||||
and not parsed_build_targets
|
||||
and not parsed_target_pattern_file
|
||||
):
|
||||
return []
|
||||
|
||||
if lint_target:
|
||||
for lint_arg in lint_targets:
|
||||
|
||||
@ -15,6 +15,7 @@ BAZEL_CI_NAMESPACE = "ci-prod"
|
||||
def main():
|
||||
install_modules(sys.argv[1], sys.argv[1:])
|
||||
|
||||
from bazel.wrapper_hook.compiledb import finalize_compiledb_posthook
|
||||
from bazel.wrapper_hook.flag_sync import sync_flags
|
||||
|
||||
if os.environ.get("NO_FLAG_SYNC") is None:
|
||||
@ -23,6 +24,10 @@ def main():
|
||||
else:
|
||||
sync_flags(BAZEL_CI_NAMESPACE)
|
||||
|
||||
enterprise = (REPO_ROOT / "src" / "mongo" / "db" / "modules" / "enterprise").exists()
|
||||
atlas = (REPO_ROOT / "src" / "mongo" / "db" / "modules" / "atlas").exists()
|
||||
finalize_compiledb_posthook(sys.argv[1], enterprise=enterprise, atlas=atlas)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -160,8 +160,9 @@ def main():
|
||||
|
||||
os.chmod(os.environ.get("MONGO_BAZEL_WRAPPER_ARGS"), 0o644)
|
||||
with open(os.environ.get("MONGO_BAZEL_WRAPPER_ARGS"), "w") as f:
|
||||
f.write("\n".join(args))
|
||||
f.write("\n")
|
||||
if args:
|
||||
f.write("\n".join(args))
|
||||
f.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -80,6 +80,25 @@ py_binary(
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "setup_clang_tidy",
|
||||
srcs = ["setup_clang_tidy.py"],
|
||||
args = [
|
||||
"--config-rlocation=$(rlocationpath //:clang_tidy_config)",
|
||||
],
|
||||
data = [
|
||||
"//:clang_tidy_config",
|
||||
"//src/mongo/tools/mongo_tidy_checks",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
dependency(
|
||||
"bazel-runfiles",
|
||||
group = "testing",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "install_bazel",
|
||||
srcs = [
|
||||
|
||||
@ -23,6 +23,7 @@ import yaml
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from mongo_toolchain import get_mongo_toolchain
|
||||
from setup_clang_tidy import clang_tidy_setup_recovery_message
|
||||
from simple_report import make_report, put_report, try_combine_reports
|
||||
|
||||
checks_so = None
|
||||
@ -32,7 +33,11 @@ if os.path.exists(".mongo_checks_module_path"):
|
||||
|
||||
|
||||
config_file = ""
|
||||
for config in ["/tmp/compiledb-bin/.clang-tidy.strict", "bazel-bin/.clang-tidy.strict"]:
|
||||
for config in [
|
||||
".clang-tidy",
|
||||
"/tmp/compiledb-bin/.clang-tidy.strict",
|
||||
"bazel-bin/.clang-tidy.strict",
|
||||
]:
|
||||
if os.path.exists(config):
|
||||
config_file = config
|
||||
break
|
||||
@ -181,7 +186,7 @@ def _run_tidy(args, parser_defaults):
|
||||
if args.compile_commands == parser_defaults.compile_commands:
|
||||
print(
|
||||
f"Could not find compile commands: '{args.compile_commands}', to generate it, use the build command:\n\n"
|
||||
+ "bazel build compiledb\n"
|
||||
+ "bazel build --config=compiledb //src/...\n"
|
||||
)
|
||||
else:
|
||||
print(f"Could not find compile commands: {args.compile_commands}")
|
||||
@ -194,7 +199,8 @@ def _run_tidy(args, parser_defaults):
|
||||
if args.clang_tidy_cfg == parser_defaults.clang_tidy_cfg:
|
||||
print(
|
||||
f"Could not find config file: '{args.clang_tidy_cfg}', to generate it, use the build command:\n\n"
|
||||
+ "bazel build compiledb\n"
|
||||
+ clang_tidy_setup_recovery_message()
|
||||
+ "\n"
|
||||
)
|
||||
else:
|
||||
print(f"Could not find config file: {args.clang_tidy_cfg}")
|
||||
|
||||
@ -33,6 +33,7 @@ import sys
|
||||
import time
|
||||
|
||||
from mongo_toolchain import get_mongo_toolchain
|
||||
from setup_clang_tidy import clang_tidy_setup_recovery_message
|
||||
|
||||
CLTCONFIG = """
|
||||
# This file is intended to document the configuration options available
|
||||
@ -99,7 +100,7 @@ def main():
|
||||
if checks_so and os.path.isfile(checks_so):
|
||||
clang_tidy_cmd += [f"-load={checks_so}"]
|
||||
else:
|
||||
print("ERROR: failed to find mongo tidy checks, run `bazel build compiledb'")
|
||||
print(f"ERROR: failed to find mongo tidy checks. {clang_tidy_setup_recovery_message()}")
|
||||
sys.exit(1)
|
||||
|
||||
files_to_check = []
|
||||
@ -125,7 +126,10 @@ def main():
|
||||
)
|
||||
|
||||
if not os.path.exists("compile_commands.json"):
|
||||
print("ERROR: failed to find compile_commands.json, run 'bazel build compiledb'")
|
||||
print(
|
||||
"ERROR: failed to find compile_commands.json, run "
|
||||
"`bazel build --config=compiledb //src/...`"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
with open("compile_commands.json") as f:
|
||||
|
||||
156
buildscripts/setup_clang_tidy.py
Normal file
156
buildscripts/setup_clang_tidy.py
Normal file
@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Materialize clang-tidy IDE integration files from Bazel outputs."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import sys
|
||||
|
||||
PLUGIN_CANDIDATES = [
|
||||
"libmongo_tidy_checks.so",
|
||||
"libmongo_tidy_checks.dylib",
|
||||
"mongo_tidy_checks.dll",
|
||||
"libmongo_tidy_checks.dll",
|
||||
]
|
||||
|
||||
|
||||
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():
|
||||
return "Run `bazel run //:setup_clang_tidy` to materialize the clang-tidy config and mongo_tidy_checks plugin."
|
||||
|
||||
return (
|
||||
"clang-tidy setup via Bazel is not supported on this platform. "
|
||||
"The mongo_tidy_checks plugin is only supported on Linux excluding Ubuntu 18.04."
|
||||
)
|
||||
|
||||
|
||||
def _copy_if_changed(src: pathlib.Path, dst: pathlib.Path) -> bool:
|
||||
src_bytes = src.read_bytes()
|
||||
if dst.exists() and dst.read_bytes() == src_bytes:
|
||||
return False
|
||||
dst.write_bytes(src_bytes)
|
||||
return True
|
||||
|
||||
|
||||
def _write_if_changed(path: pathlib.Path, contents: str) -> bool:
|
||||
if path.exists() and path.read_text(encoding="utf-8") == contents:
|
||||
return False
|
||||
path.write_text(contents, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def materialize_clang_tidy_ide_files(
|
||||
repo_root: pathlib.Path,
|
||||
config_src: pathlib.Path,
|
||||
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))
|
||||
return config_changed, marker_changed
|
||||
|
||||
|
||||
def _resolve_runfile(r, rlocation_path: str) -> pathlib.Path:
|
||||
resolved = r.Rlocation(rlocation_path)
|
||||
if not resolved:
|
||||
raise FileNotFoundError(f"Failed to resolve Bazel runfile: {rlocation_path}")
|
||||
path = pathlib.Path(resolved)
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Resolved runfile is not a file: {path}")
|
||||
return path.resolve()
|
||||
|
||||
|
||||
def _resolve_plugin(
|
||||
r, workspace_prefix: str, package_path: str = "src/mongo/tools/mongo_tidy_checks"
|
||||
) -> pathlib.Path:
|
||||
for candidate in PLUGIN_CANDIDATES:
|
||||
resolved = r.Rlocation(f"{workspace_prefix}/{package_path}/{candidate}")
|
||||
if resolved and pathlib.Path(resolved).is_file():
|
||||
return pathlib.Path(resolved).resolve()
|
||||
|
||||
candidate_list = ", ".join(PLUGIN_CANDIDATES)
|
||||
raise FileNotFoundError(
|
||||
"Failed to resolve mongo_tidy_checks plugin from Bazel runfiles. "
|
||||
f"Tried: {candidate_list}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
import runfiles
|
||||
except ModuleNotFoundError:
|
||||
print(
|
||||
"The `bazel-runfiles` dependency is required to run `bazel run //:setup_clang_tidy`.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--config-rlocation", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY")
|
||||
if not workspace_dir:
|
||||
print("This tool must be run with `bazel run //:setup_clang_tidy`.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
repo_root = pathlib.Path(workspace_dir).resolve()
|
||||
config_rlocation = args.config_rlocation
|
||||
if "/" not in config_rlocation:
|
||||
print(
|
||||
f"Unexpected config runfile path: {config_rlocation}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
workspace_prefix = config_rlocation.split("/", 1)[0]
|
||||
r = runfiles.Create()
|
||||
if r is None:
|
||||
print("Failed to initialize Bazel runfiles support.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
config_src = _resolve_runfile(r, config_rlocation)
|
||||
plugin_src = _resolve_plugin(r, workspace_prefix)
|
||||
|
||||
config_changed, marker_changed = materialize_clang_tidy_ide_files(
|
||||
repo_root,
|
||||
config_src,
|
||||
plugin_src,
|
||||
)
|
||||
|
||||
print(f"Configured clang-tidy for IDE use at {repo_root / '.clang-tidy'}")
|
||||
print(f"Configured mongo tidy checks plugin at {plugin_src}")
|
||||
if not config_changed and not marker_changed:
|
||||
print("clang-tidy IDE files were already up to date.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,10 +1,13 @@
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stderr
|
||||
from io import StringIO
|
||||
|
||||
sys.path.append(".")
|
||||
|
||||
import bazel.wrapper_hook.plus_interface as plus_interface
|
||||
from bazel.wrapper_hook.plus_interface import (
|
||||
BinAndSourceIncompatible,
|
||||
DuplicateSourceNames,
|
||||
@ -299,6 +302,235 @@ class Tests(unittest.TestCase):
|
||||
stderr_output = stderr_capture.getvalue()
|
||||
validate_first_suggestion(stderr_output, "+bson_obj_test")
|
||||
|
||||
def test_compiledb_target_runs_separately_and_leaves_other_targets(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "compiledb", "//src/mongo/base:error_codes"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
original_swap_default_config = plus_interface.swap_default_config
|
||||
plus_interface.generate_compiledb = fake_generate_compiledb
|
||||
plus_interface.swap_default_config = (
|
||||
lambda args,
|
||||
command,
|
||||
config_mode,
|
||||
compiledb_target,
|
||||
clang_tidy,
|
||||
user_specified_config: config_mode
|
||||
)
|
||||
try:
|
||||
result = test_runner_interface(args, False, buildozer_output)
|
||||
finally:
|
||||
plus_interface.generate_compiledb = original_generate_compiledb
|
||||
plus_interface.swap_default_config = original_swap_default_config
|
||||
|
||||
assert result == ["build", "//src/mongo/base:error_codes"]
|
||||
assert len(generate_calls) == 1
|
||||
assert "requested_build_flags" not in generate_calls[0][1]
|
||||
|
||||
def test_compiledb_only_target_skips_final_bazel_invocation(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "compiledb_only"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
original_swap_default_config = plus_interface.swap_default_config
|
||||
plus_interface.generate_compiledb = fake_generate_compiledb
|
||||
plus_interface.swap_default_config = (
|
||||
lambda args,
|
||||
command,
|
||||
config_mode,
|
||||
compiledb_target,
|
||||
clang_tidy,
|
||||
user_specified_config: config_mode
|
||||
)
|
||||
try:
|
||||
result = test_runner_interface(args, False, buildozer_output)
|
||||
finally:
|
||||
plus_interface.generate_compiledb = original_generate_compiledb
|
||||
plus_interface.swap_default_config = original_swap_default_config
|
||||
|
||||
assert result == []
|
||||
assert len(generate_calls) == 1
|
||||
|
||||
def test_compiledb_target_preserves_define_flag_value(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = [
|
||||
"wrapper_hook",
|
||||
"build",
|
||||
"compiledb",
|
||||
"--define",
|
||||
"MONGO_VERSION=1",
|
||||
"--keep_going",
|
||||
"//src/mongo/base:error_codes",
|
||||
]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
original_swap_default_config = plus_interface.swap_default_config
|
||||
plus_interface.generate_compiledb = fake_generate_compiledb
|
||||
plus_interface.swap_default_config = (
|
||||
lambda args,
|
||||
command,
|
||||
config_mode,
|
||||
compiledb_target,
|
||||
clang_tidy,
|
||||
user_specified_config: config_mode
|
||||
)
|
||||
try:
|
||||
result = test_runner_interface(args, False, buildozer_output)
|
||||
finally:
|
||||
plus_interface.generate_compiledb = original_generate_compiledb
|
||||
plus_interface.swap_default_config = original_swap_default_config
|
||||
|
||||
assert result == [
|
||||
"build",
|
||||
"--define",
|
||||
"MONGO_VERSION=1",
|
||||
"--keep_going",
|
||||
"//src/mongo/base:error_codes",
|
||||
]
|
||||
assert len(generate_calls) == 1
|
||||
assert "requested_build_flags" not in generate_calls[0][1]
|
||||
|
||||
def test_config_equals_compiledb_runs_normally(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "--config=compiledb", "//src/mongo/base:error_codes"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
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.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.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
|
||||
|
||||
assert result == ["build", "--config=compiledb", "//src/mongo/base:error_codes"]
|
||||
assert len(generate_calls) == 0
|
||||
|
||||
def test_config_separate_compiledb_runs_normally(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "--config", "compiledb", "//src/mongo/base:error_codes"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
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.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.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
|
||||
|
||||
assert result == [
|
||||
"build",
|
||||
"--config",
|
||||
"compiledb",
|
||||
"//src/mongo/base:error_codes",
|
||||
]
|
||||
assert len(generate_calls) == 0
|
||||
|
||||
def test_config_separate_compiledb_runs_normally_with_plain_target(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "--config", "compiledb", "install-dist-test"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
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.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.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
|
||||
|
||||
assert result == [
|
||||
"build",
|
||||
"--config",
|
||||
"compiledb",
|
||||
"install-dist-test",
|
||||
]
|
||||
assert len(generate_calls) == 0
|
||||
|
||||
def test_config_separate_compiledb_runs_normally_with_target_before_config(self):
|
||||
def buildozer_output(autocomplete_query):
|
||||
return ""
|
||||
|
||||
args = ["wrapper_hook", "build", "install-dist-test", "--config", "compiledb"]
|
||||
generate_calls = []
|
||||
|
||||
def fake_generate_compiledb(*call_args, **call_kwargs):
|
||||
generate_calls.append((call_args, call_kwargs))
|
||||
|
||||
original_generate_compiledb = plus_interface.generate_compiledb
|
||||
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.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.WRAPPER_CONFIG_MODE_FILE = original_wrapper_config_mode_file
|
||||
|
||||
assert result == [
|
||||
"build",
|
||||
"install-dist-test",
|
||||
"--config",
|
||||
"compiledb",
|
||||
]
|
||||
assert len(generate_calls) == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
42
buildscripts/tests/test_compiledb_output_format.py
Normal file
42
buildscripts/tests/test_compiledb_output_format.py
Normal file
@ -0,0 +1,42 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.append(".")
|
||||
|
||||
from bazel.wrapper_hook.compiledb import _build_final_compile_command_entry
|
||||
|
||||
|
||||
class CompiledbOutputFormatTest(unittest.TestCase):
|
||||
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/"):
|
||||
return out_root_str + "/" + path[len("bazel-out/") :]
|
||||
return path
|
||||
|
||||
entry = {
|
||||
"file": "bazel-out/k8/bin/src/mongo/base/error_codes.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/base/error_codes.cpp"],
|
||||
"output": "bazel-out/k8/bin/src/mongo/base/error_codes.cpp.o",
|
||||
"target": "//src/mongo/base:error_codes",
|
||||
}
|
||||
|
||||
formatted_entry = _build_final_compile_command_entry(
|
||||
entry=entry,
|
||||
arguments=entry["arguments"],
|
||||
repo_root_resolved="/repo",
|
||||
rewrite_exec_path=rewrite_exec_path,
|
||||
out_root_str="/real/bazel-out",
|
||||
external_root_str="/real/external",
|
||||
)
|
||||
|
||||
assert formatted_entry == {
|
||||
"file": "/real/bazel-out/k8/bin/src/mongo/base/error_codes.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/base/error_codes.cpp"],
|
||||
"directory": "/repo",
|
||||
"output": "/real/bazel-out/k8/bin/src/mongo/base/error_codes.cpp.o",
|
||||
}
|
||||
assert "target" not in formatted_entry
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -160,6 +160,13 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=fastbuild
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: hydrate_bazel_profile_all_headers
|
||||
tags:
|
||||
@ -185,6 +192,14 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=fastbuild
|
||||
--all_headers=True
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: build_source_graph_index
|
||||
tags: ["assigned_to_jira_team_devprod_build", "auxiliary"]
|
||||
@ -230,6 +245,13 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=opt
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: hydrate_bazel_profile_dbg
|
||||
tags:
|
||||
@ -253,6 +275,13 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=dbg
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: hydrate_bazel_profile_dbg_aubsan
|
||||
tags:
|
||||
@ -276,6 +305,13 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=dbg_aubsan
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: hydrate_bazel_profile_dbg_tsan
|
||||
tags:
|
||||
@ -299,6 +335,13 @@ tasks:
|
||||
--output_groups=compilation_outputs
|
||||
--keep_going
|
||||
--build_tag_filters=${bazel_filters_for_cache_hydration}
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
bazel_args: >-
|
||||
--config=dbg_tsan
|
||||
--define GIT_COMMIT_HASH=nogitversion
|
||||
--keep_going
|
||||
|
||||
- name: hydrate_bazel_unit_tests
|
||||
tags:
|
||||
@ -395,6 +438,22 @@ tasks:
|
||||
vars:
|
||||
target: //evergreen:validate_compile_commands
|
||||
|
||||
- name: full_bazel_compiledb
|
||||
tags: ["assigned_to_jira_team_devprod_build", "auxiliary"]
|
||||
exec_timeout_secs: 43200 # 12 hour timeout: validates every compile_commands entry.
|
||||
depends_on:
|
||||
- name: version_expansions_gen
|
||||
variant: generate-tasks-for-version
|
||||
commands:
|
||||
- func: "do bazel setup"
|
||||
- func: "bazel compile"
|
||||
vars:
|
||||
targets: compiledb
|
||||
- func: bazel run
|
||||
vars:
|
||||
target: //evergreen:validate_compile_commands
|
||||
args: -- --run-all
|
||||
|
||||
- name: compile_upload_benchmarks
|
||||
tags: ["assigned_to_jira_team_devprod_build", "auxiliary"]
|
||||
depends_on:
|
||||
|
||||
@ -452,6 +452,10 @@ buildvariants:
|
||||
- name: test_packages
|
||||
distros:
|
||||
- ubuntu2204-arm64-m8g-4xlarge
|
||||
- name: full_bazel_compiledb
|
||||
cron: "0 4 * * 0" # From the ${project_weekly_cron} parameter.
|
||||
distros:
|
||||
- amazon2023-arm64-latest-m8gd-4xlarge
|
||||
- name: .development_critical !.requires_large_host
|
||||
- name: .development_critical .requires_large_host
|
||||
distros:
|
||||
|
||||
@ -102,6 +102,10 @@ buildvariants:
|
||||
- name: run_unit_tests_no_sandbox_TG
|
||||
distros:
|
||||
- windows-2022-xxlarge
|
||||
- name: full_bazel_compiledb
|
||||
cron: "0 4 * * 0" # From the ${project_weekly_cron} parameter.
|
||||
distros:
|
||||
- windows-2022-xxxlarge-compile
|
||||
- name: .development_critical !.requires_large_host !.incompatible_windows
|
||||
- name: .development_critical .requires_large_host !.incompatible_windows
|
||||
distros:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
load("@poetry//:dependencies.bzl", "dependency")
|
||||
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
|
||||
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@ -9,6 +9,14 @@ py_binary(
|
||||
main = "validate_compile_commands.py",
|
||||
)
|
||||
|
||||
py_test(
|
||||
name = "validate_compile_commands_test",
|
||||
srcs = [
|
||||
"validate_compile_commands.py",
|
||||
"validate_compile_commands_test.py",
|
||||
],
|
||||
)
|
||||
|
||||
sh_binary(
|
||||
name = "wiki_page_minimized_agg_query_fuzzer",
|
||||
srcs = ["wiki_page_minimized_agg_query_fuzzer.sh"],
|
||||
|
||||
@ -17,29 +17,108 @@ activate_venv
|
||||
export MONGO_WRAPPER_OUTPUT_ALL=1
|
||||
# number of parallel jobs to use for build.
|
||||
# Even with scale=0 (the default), bc command adds decimal digits in case of multiplication. Division by 1 gives us a whole number with scale=0
|
||||
bazel_jobs=$(bc <<<"$(grep -c '^processor' /proc/cpuinfo) * .85 / 1")
|
||||
cov_jobs=$(bc <<<"$(grep -c '^processor' /proc/cpuinfo) * .40 / 1")
|
||||
coverity_config_dir="$workdir/coverity/config"
|
||||
coverity_config_file="$coverity_config_dir/coverity_config.xml"
|
||||
|
||||
build_config="--config=local --jobs=$bazel_jobs --build_atlas=True --compiler_type=gcc --opt=off --dbg=False --allocator=system --define=MONGO_VERSION=${version}"
|
||||
bazel_query='mnemonic("CppCompile|LinkCompile", filter(//src/mongo, deps(//:install-core)))'
|
||||
build_config="--config=local --build_atlas=True --compiler_type=gcc --opt=off --dbg=False --allocator=system --define=MONGO_VERSION=${version}"
|
||||
bazel_cache="--output_user_root=$workdir/bazel_cache"
|
||||
compiledb_target_pattern_file="$(mktemp "$workdir/install-core-compiledb-targets.XXXXXX")"
|
||||
query_stderr_file="$(mktemp "$workdir/install-core-compiledb-query-stderr.XXXXXX")"
|
||||
trap 'rm -f "$compiledb_target_pattern_file" "$query_stderr_file"' EXIT
|
||||
|
||||
python bazel/coverity/generate_coverity_targets.py --bazel_executable="bazel" --bazel_cache=$bazel_cache --bazel_query="$bazel_query" $build_config --noinclude_artifacts
|
||||
bazel $bazel_cache build $build_config --build_tag_filters=gen_source //src/...
|
||||
echo "Generating compile_commands.json for Coverity capture"
|
||||
echo "Resolving mongo_compiledb targets under //:install-core"
|
||||
query_command=(
|
||||
bazel
|
||||
$bazel_cache
|
||||
cquery
|
||||
$build_config
|
||||
'attr("tags", ".*mongo_compiledb.*", deps(//:install-core))'
|
||||
)
|
||||
printf ' %q' "${query_command[@]}"
|
||||
echo
|
||||
if ! "${query_command[@]}" \
|
||||
2>"$query_stderr_file" | grep "//src/mongo" | awk '{print $1}' | sort -u >"$compiledb_target_pattern_file"; then
|
||||
echo "Failed to resolve mongo_compiledb targets under //:install-core"
|
||||
cat "$query_stderr_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
buildCommand="bazel \
|
||||
$bazel_cache \
|
||||
build \
|
||||
$build_config \
|
||||
--target_pattern_file=coverity_targets.list"
|
||||
echo Building $buildCommand
|
||||
cat coverity_targets.list
|
||||
if ! $workdir/coverity/bin/cov-build --dir "$covIdir" --verbose 0 -j $cov_jobs --return-emit-failures --parse-error-threshold=99 --bazel \
|
||||
$buildCommand; then
|
||||
echo "Contents of $compiledb_target_pattern_file"
|
||||
cat "$compiledb_target_pattern_file"
|
||||
|
||||
if [ ! -s "$compiledb_target_pattern_file" ]; then
|
||||
echo "No mongo_compiledb targets found under //:install-core"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_compiledb_command=(
|
||||
bazel
|
||||
$bazel_cache
|
||||
build
|
||||
$build_config
|
||||
--config=compiledb
|
||||
--target_pattern_file="$compiledb_target_pattern_file"
|
||||
)
|
||||
printf ' %q' "${build_compiledb_command[@]}"
|
||||
echo
|
||||
"${build_compiledb_command[@]}"
|
||||
|
||||
compiledb_output_base="$(bazel $bazel_cache info output_base)"
|
||||
repo_python=""
|
||||
python_candidates=(
|
||||
"$compiledb_output_base/external/_main~setup_mongo_python_toolchains~py_host/dist/bin/python3"
|
||||
"$compiledb_output_base/external/py_host/dist/bin/python3"
|
||||
"$compiledb_output_base/external/_main~setup_mongo_python_toolchains~py_host/dist/python.exe"
|
||||
"$compiledb_output_base/external/py_host/dist/python.exe"
|
||||
)
|
||||
for candidate in "${python_candidates[@]}"; do
|
||||
if [ -x "$candidate" ]; then
|
||||
repo_python="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$repo_python" ]; then
|
||||
for candidate in "$compiledb_output_base"/external/*py_host*/dist/bin/python3 \
|
||||
"$compiledb_output_base"/external/*py_host*/dist/python.exe; do
|
||||
if [ -x "$candidate" ]; then
|
||||
repo_python="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo "Resolved repo-rule python: $repo_python"
|
||||
if [ -z "$repo_python" ] || [ ! -x "$repo_python" ]; then
|
||||
echo "Failed to resolve repo-rule python in Bazel output tree"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$coverity_config_dir"
|
||||
if [ ! -f "$coverity_config_file" ]; then
|
||||
echo "Configuring Coverity compiler capture with default gcc template"
|
||||
"$workdir/coverity/bin/cov-configure" --gcc --config "$coverity_config_file"
|
||||
fi
|
||||
|
||||
capture_command=(
|
||||
env
|
||||
"BUILD_WORKSPACE_DIRECTORY=$PWD"
|
||||
"VALIDATE_COMPILE_COMMANDS_RUN_ALL=1"
|
||||
"VALIDATE_COMPILE_COMMANDS_OUT_DIR=$workdir/validate_compile_commands_out"
|
||||
"$repo_python"
|
||||
evergreen/validate_compile_commands.py
|
||||
)
|
||||
|
||||
echo "Running Coverity capture via compile_commands.json replay"
|
||||
printf ' %q' "${capture_command[@]}"
|
||||
echo
|
||||
if $workdir/coverity/bin/cov-build --dir "$covIdir" --config "$coverity_config_file" --verbose 0 --return-emit-failures --parse-error-threshold=99 \
|
||||
"${capture_command[@]}"; then
|
||||
echo "cov-build was successful"
|
||||
else
|
||||
ret=$?
|
||||
echo "cov-build failed with exit code $ret"
|
||||
cat $covIdir/replay-log.txt
|
||||
if [ -f "$covIdir/replay-log.txt" ]; then
|
||||
cat "$covIdir/replay-log.txt"
|
||||
fi
|
||||
exit $ret
|
||||
else
|
||||
echo "cov-build was successful"
|
||||
fi
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import hashlib
|
||||
import heapq
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
@ -11,19 +13,29 @@ import sys
|
||||
import tempfile
|
||||
from typing import Any, Iterator
|
||||
|
||||
default_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY")
|
||||
if not default_dir:
|
||||
print(
|
||||
"This script must be run though bazel. Please run 'bazel run //evergreen:validate_compile_commands' instead."
|
||||
STANDARD_COMPILE_COMMAND_KEYS = frozenset({"arguments", "command", "directory", "file", "output"})
|
||||
COMPILEDB_GENERATION_TARGETS = ["compiledb", "install-wiredtiger"]
|
||||
|
||||
|
||||
def _get_workspace_dir() -> str:
|
||||
workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY")
|
||||
if workspace_dir:
|
||||
return workspace_dir
|
||||
raise RuntimeError(
|
||||
"This script must be run through bazel. "
|
||||
"Please run 'bazel run //evergreen:validate_compile_commands' instead."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
os.chdir(default_dir)
|
||||
|
||||
if not os.path.exists("compile_commands.json"):
|
||||
sys.stderr.write("The 'compile_commands.json' file was not found.\n")
|
||||
sys.stderr.write("Attempting to run 'bazel build compiledb' to generate it.\n")
|
||||
subprocess.run(["bazel", "build", "compiledb"], check=True)
|
||||
def _ensure_compiledb_exists(compdb_path: str) -> None:
|
||||
if os.path.exists(compdb_path):
|
||||
return
|
||||
sys.stderr.write(f"The '{compdb_path}' file was not found.\n")
|
||||
sys.stderr.write(
|
||||
"Attempting to run "
|
||||
f"'bazel build {' '.join(COMPILEDB_GENERATION_TARGETS)}' to generate it.\n"
|
||||
)
|
||||
subprocess.run(["bazel", "build", *COMPILEDB_GENERATION_TARGETS], check=True)
|
||||
|
||||
|
||||
def _parse_repo_env_from_bazelrc(bazelrc_path: str, var_name: str) -> str | None:
|
||||
@ -197,12 +209,181 @@ def _iter_compiledb_entries(path: str) -> Iterator[dict[str, Any]]:
|
||||
pos = 0
|
||||
|
||||
|
||||
def _validate_compiledb_entry(entry: dict[str, Any], *, index: int) -> None:
|
||||
extra_keys = sorted(set(entry) - STANDARD_COMPILE_COMMAND_KEYS)
|
||||
if extra_keys:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} has non-standard keys {extra_keys}. "
|
||||
f"Only {sorted(STANDARD_COMPILE_COMMAND_KEYS)} are allowed by the Clang JSON "
|
||||
"Compilation Database format."
|
||||
)
|
||||
|
||||
directory = entry.get("directory")
|
||||
if not isinstance(directory, str) or not directory:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} must contain a non-empty string 'directory'."
|
||||
)
|
||||
|
||||
file_name = entry.get("file")
|
||||
if not isinstance(file_name, str) or not file_name:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} must contain a non-empty string 'file'."
|
||||
)
|
||||
|
||||
has_arguments = "arguments" in entry
|
||||
has_command = "command" in entry
|
||||
if has_arguments == has_command:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} must contain exactly one of "
|
||||
"'arguments' or 'command'."
|
||||
)
|
||||
|
||||
if has_arguments:
|
||||
arguments = entry["arguments"]
|
||||
if (
|
||||
not isinstance(arguments, list)
|
||||
or not arguments
|
||||
or not all(isinstance(arg, str) for arg in arguments)
|
||||
):
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} 'arguments' must be a non-empty "
|
||||
"list of strings."
|
||||
)
|
||||
|
||||
if has_command:
|
||||
command = entry["command"]
|
||||
if not isinstance(command, str) or not command:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} 'command' must be a non-empty string."
|
||||
)
|
||||
|
||||
if "output" in entry:
|
||||
output = entry["output"]
|
||||
if not isinstance(output, str) or not output:
|
||||
raise ValueError(
|
||||
f"compile_commands.json entry {index} 'output' must be a non-empty string "
|
||||
"when present."
|
||||
)
|
||||
|
||||
|
||||
def _hash_file_name(file_name: str) -> int:
|
||||
# Deterministic across runs; 'file' in compile_commands is typically relative and stable.
|
||||
digest = hashlib.sha256(file_name.encode("utf-8")).digest()
|
||||
return int.from_bytes(digest[:8], byteorder="big", signed=False)
|
||||
|
||||
|
||||
def _selection_key_for_entry(entry: dict[str, Any]) -> str | None:
|
||||
"""Build a canonical key for cross-platform deterministic selection.
|
||||
|
||||
compile_commands entries may use absolute paths rooted in machine-specific Bazel
|
||||
output locations (e.g. /tmp/.../external/... or Z:/.../bazel-out/<config>/...).
|
||||
We normalize those prefixes so the same logical source tends to hash the same
|
||||
across Linux/Windows.
|
||||
"""
|
||||
file_name = entry.get("file")
|
||||
if not isinstance(file_name, str):
|
||||
return None
|
||||
|
||||
directory = entry.get("directory")
|
||||
p = file_name.replace("\\", "/")
|
||||
# Windows paths are case-insensitive; lowercasing also improves cross-OS stability.
|
||||
p = p.lower()
|
||||
|
||||
# If this is an absolute path under the entry directory, strip that prefix.
|
||||
if isinstance(directory, str):
|
||||
d = directory.replace("\\", "/").lower().rstrip("/")
|
||||
if d and p.startswith(d + "/"):
|
||||
p = p[len(d) + 1 :]
|
||||
|
||||
# Strip machine-specific prefixes while preserving meaningful roots.
|
||||
if "/execroot/_main/" in p:
|
||||
p = p.split("/execroot/_main/", 1)[1]
|
||||
elif "/external/" in p:
|
||||
p = "external/" + p.split("/external/", 1)[1]
|
||||
elif "/bazel-out/" in p:
|
||||
p = "bazel-out/" + p.split("/bazel-out/", 1)[1]
|
||||
elif "/src/" in p:
|
||||
p = "src/" + p.split("/src/", 1)[1]
|
||||
|
||||
# Normalize bazel configuration segment (platform/config differs by OS).
|
||||
p = re.sub(r"(^|/)bazel-out/[^/]+/", r"\1bazel-out/<config>/", p)
|
||||
p = re.sub(r"/+", "/", p).lstrip("./")
|
||||
return p or file_name.lower()
|
||||
|
||||
|
||||
def _is_truthy_env(value: str | None) -> bool:
|
||||
if value is None:
|
||||
return False
|
||||
return value.strip().lower() not in ("", "0", "false", "no", "off")
|
||||
|
||||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
selection_group = parser.add_mutually_exclusive_group()
|
||||
selection_group.add_argument(
|
||||
"--run-all",
|
||||
action="store_true",
|
||||
help="Validate every compile_commands entry instead of sampling.",
|
||||
)
|
||||
selection_group.add_argument(
|
||||
"--sample-size",
|
||||
type=int,
|
||||
help="Validate a fixed number of compile_commands entries.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def _determine_selection_count(
|
||||
default_count: int = 10,
|
||||
*,
|
||||
cli_run_all: bool = False,
|
||||
cli_sample_size: int | None = None,
|
||||
) -> int:
|
||||
"""Resolve how many compile_commands entries to test.
|
||||
|
||||
CLI flags take precedence over environment variables.
|
||||
|
||||
- --run-all: run all entries.
|
||||
- --sample-size N: run N entries (N > 0).
|
||||
- VALIDATE_COMPILE_COMMANDS_RUN_ALL=1: run all entries.
|
||||
- VALIDATE_COMPILE_COMMANDS_SAMPLE_SIZE=<N>: run N entries (N > 0).
|
||||
"""
|
||||
if cli_run_all:
|
||||
return 0
|
||||
|
||||
if cli_sample_size is not None:
|
||||
if cli_sample_size <= 0:
|
||||
raise ValueError(f"--sample-size must be > 0, got: {cli_sample_size}")
|
||||
return cli_sample_size
|
||||
|
||||
if _is_truthy_env(os.environ.get("VALIDATE_COMPILE_COMMANDS_RUN_ALL")):
|
||||
return 0
|
||||
|
||||
sample_size_env = os.environ.get("VALIDATE_COMPILE_COMMANDS_SAMPLE_SIZE")
|
||||
if sample_size_env is None:
|
||||
return default_count
|
||||
try:
|
||||
sample_size = int(sample_size_env)
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
f"VALIDATE_COMPILE_COMMANDS_SAMPLE_SIZE must be an integer, got: {sample_size_env!r}"
|
||||
) from exc
|
||||
if sample_size <= 0:
|
||||
raise ValueError(f"VALIDATE_COMPILE_COMMANDS_SAMPLE_SIZE must be > 0, got: {sample_size}")
|
||||
return sample_size
|
||||
|
||||
|
||||
def _should_validate_entry(entry: dict[str, Any]) -> bool:
|
||||
selection_key = _selection_key_for_entry(entry)
|
||||
if not selection_key:
|
||||
return False
|
||||
|
||||
# Keep the sample focused on MongoDB workspace sources. External repositories and
|
||||
# vendored third-party code have their own generated include layouts that are not
|
||||
# meaningful for validating the repo's compile_commands coverage.
|
||||
return selection_key.startswith("src/mongo/")
|
||||
|
||||
|
||||
def _make_test_compile_args(args: list[str]) -> list[str]:
|
||||
"""Convert a compile command into a 'test compile' command.
|
||||
|
||||
@ -252,6 +433,22 @@ def _map_writable_output_path(out_root: str, original_path: str) -> str:
|
||||
return "_"
|
||||
return comp
|
||||
|
||||
if platform.system() == "Windows":
|
||||
drive, _ = os.path.splitdrive(original_path)
|
||||
drive_tag = drive.rstrip(":")
|
||||
drive_tag = drive_tag.lstrip("\\/").replace("\\", "_").replace("/", "_")
|
||||
drive_tag = _sanitize_component(drive_tag) if drive_tag else "PATH"
|
||||
|
||||
normalized = os.path.normcase(os.path.normpath(original_path))
|
||||
digest = hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
|
||||
basename = _sanitize_component(os.path.basename(original_path)) or "out"
|
||||
stem, ext = os.path.splitext(basename)
|
||||
if len(stem) > 48:
|
||||
stem = stem[:48]
|
||||
short_name = f"{stem}-{digest}{ext}"
|
||||
|
||||
return os.path.normpath(os.path.join(out_root, "win", drive_tag, short_name))
|
||||
|
||||
drive, tail = os.path.splitdrive(original_path)
|
||||
parts: list[str] = []
|
||||
|
||||
@ -461,38 +658,83 @@ def _ensure_parent_dirs_exist_for_outputs(args: list[str], cwd: str, repo_root:
|
||||
|
||||
|
||||
def _select_entries_for_test_compile(path: str, n: int) -> tuple[int, list[dict[str, Any]]]:
|
||||
"""Pick N entries by sorting deterministic hashes of entry['file'] and taking the first N."""
|
||||
"""Pick N entries by sorting deterministic hashes of a canonicalized file key."""
|
||||
if n <= 0:
|
||||
total = 0
|
||||
selected: list[dict[str, Any]] = []
|
||||
for index, entry in enumerate(_iter_compiledb_entries(path), start=1):
|
||||
_validate_compiledb_entry(entry, index=index)
|
||||
total += 1
|
||||
file_name = entry.get("file")
|
||||
if not isinstance(file_name, str):
|
||||
continue
|
||||
selected.append(entry)
|
||||
return total, selected
|
||||
|
||||
# Keep a max-heap of the N smallest hashes.
|
||||
# IMPORTANT: include stable, comparable tie-breakers so heapq never compares dicts.
|
||||
# Tuple: (-hash, file_name, seq, entry)
|
||||
heap: list[tuple[int, str, int, dict[str, Any]]] = []
|
||||
# Tuple: (-hash, selection_key, file_name, seq, entry)
|
||||
heap: list[tuple[int, str, str, int, dict[str, Any]]] = []
|
||||
total = 0
|
||||
seq = 0
|
||||
for entry in _iter_compiledb_entries(path):
|
||||
for index, entry in enumerate(_iter_compiledb_entries(path), start=1):
|
||||
_validate_compiledb_entry(entry, index=index)
|
||||
total += 1
|
||||
file_name = entry.get("file")
|
||||
if not isinstance(file_name, str):
|
||||
continue
|
||||
h = _hash_file_name(file_name)
|
||||
item = (-h, file_name, seq, entry)
|
||||
selection_key = _selection_key_for_entry(entry)
|
||||
if not selection_key or not _should_validate_entry(entry):
|
||||
continue
|
||||
h = _hash_file_name(selection_key)
|
||||
item = (-h, selection_key, file_name, seq, entry)
|
||||
seq += 1
|
||||
if len(heap) < n:
|
||||
heapq.heappush(heap, item)
|
||||
else:
|
||||
# If this hash is smaller than the current largest in the heap, replace it.
|
||||
if item[:3] > heap[0][:3]:
|
||||
if item[:4] > heap[0][:4]:
|
||||
heapq.heapreplace(heap, item)
|
||||
|
||||
# Sort ascending by hash.
|
||||
selected = [
|
||||
e for (_neg_h, _file_name, _seq, e) in sorted(heap, key=lambda t: (-t[0], t[1], t[2]))
|
||||
e
|
||||
for (_neg_h, _selection_key, _file_name, _seq, e) in sorted(
|
||||
heap, key=lambda t: (-t[0], t[1], t[2], t[3])
|
||||
)
|
||||
]
|
||||
return total, selected
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
workspace_dir = _get_workspace_dir()
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
return 1
|
||||
|
||||
os.chdir(workspace_dir)
|
||||
|
||||
cli_args = _parse_args()
|
||||
compdb_path = "compile_commands.json"
|
||||
total, selected = _select_entries_for_test_compile(compdb_path, n=10)
|
||||
_ensure_compiledb_exists(compdb_path)
|
||||
try:
|
||||
selection_count = _determine_selection_count(
|
||||
default_count=10,
|
||||
cli_run_all=cli_args.run_all,
|
||||
cli_sample_size=cli_args.sample_size,
|
||||
)
|
||||
except ValueError as e:
|
||||
sys.stderr.write(f"ERROR: {e}\n")
|
||||
return 1
|
||||
|
||||
try:
|
||||
total, selected = _select_entries_for_test_compile(compdb_path, n=selection_count)
|
||||
except ValueError as e:
|
||||
sys.stderr.write(f"ERROR: {e}\n")
|
||||
return 1
|
||||
if selection_count <= 0:
|
||||
random.shuffle(selected)
|
||||
|
||||
if total < 1000:
|
||||
sys.stderr.write(
|
||||
@ -504,9 +746,16 @@ def main() -> int:
|
||||
sys.stderr.write("ERROR: Failed to select any entries for test compilation.\n")
|
||||
return 1
|
||||
|
||||
if selection_count <= 0:
|
||||
print(
|
||||
f"Selected all compile_commands entries for validation ({len(selected)}).", flush=True
|
||||
)
|
||||
else:
|
||||
print(f"Selected {len(selected)} compile_commands entries for validation.", flush=True)
|
||||
|
||||
out_root = os.environ.get(
|
||||
"VALIDATE_COMPILE_COMMANDS_OUT_DIR",
|
||||
os.path.join(default_dir, ".validate_compile_commands_out"),
|
||||
os.path.join(workspace_dir, ".validate_compile_commands_out"),
|
||||
)
|
||||
os.makedirs(out_root, exist_ok=True)
|
||||
|
||||
@ -550,11 +799,11 @@ def main() -> int:
|
||||
max_workers = max(1, min(max_workers, len(work)))
|
||||
|
||||
print(f"Running {len(work)} test compiles...", flush=True)
|
||||
compile_env = _maybe_add_windows_toolchain_env(os.environ.copy(), repo_root=default_dir)
|
||||
compile_env = _maybe_add_windows_toolchain_env(os.environ.copy(), repo_root=workspace_dir)
|
||||
|
||||
def _run_one(item: tuple[str, str, list[str]]) -> tuple[str, int, list[str], str, str]:
|
||||
file_name, directory, test_args = item
|
||||
_ensure_parent_dirs_exist_for_outputs(test_args, cwd=directory, repo_root=default_dir)
|
||||
_ensure_parent_dirs_exist_for_outputs(test_args, cwd=directory, repo_root=workspace_dir)
|
||||
proc = subprocess.run(
|
||||
test_args, cwd=directory, env=compile_env, capture_output=True, text=True
|
||||
)
|
||||
|
||||
91
evergreen/validate_compile_commands_test.py
Normal file
91
evergreen/validate_compile_commands_test.py
Normal file
@ -0,0 +1,91 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
import validate_compile_commands as validator
|
||||
|
||||
|
||||
class ValidateCompileCommandsTest(unittest.TestCase):
|
||||
def test_accepts_standard_arguments_entry(self):
|
||||
validator._validate_compiledb_entry(
|
||||
{
|
||||
"directory": "/repo",
|
||||
"file": "src/mongo/db/example.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/db/example.cpp"],
|
||||
"output": "bazel-out/example.o",
|
||||
},
|
||||
index=1,
|
||||
)
|
||||
|
||||
def test_accepts_standard_command_entry(self):
|
||||
validator._validate_compiledb_entry(
|
||||
{
|
||||
"directory": "/repo",
|
||||
"file": "src/mongo/db/example.cpp",
|
||||
"command": "clang++ -c src/mongo/db/example.cpp -o bazel-out/example.o",
|
||||
},
|
||||
index=1,
|
||||
)
|
||||
|
||||
def test_rejects_non_standard_keys(self):
|
||||
with self.assertRaisesRegex(ValueError, r"non-standard keys \['target'\]"):
|
||||
validator._validate_compiledb_entry(
|
||||
{
|
||||
"directory": "/repo",
|
||||
"file": "src/mongo/db/example.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/db/example.cpp"],
|
||||
"target": "//src/mongo/db:example",
|
||||
},
|
||||
index=1,
|
||||
)
|
||||
|
||||
def test_rejects_entries_with_both_command_and_arguments(self):
|
||||
with self.assertRaisesRegex(ValueError, r"exactly one of 'arguments' or 'command'"):
|
||||
validator._validate_compiledb_entry(
|
||||
{
|
||||
"directory": "/repo",
|
||||
"file": "src/mongo/db/example.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/db/example.cpp"],
|
||||
"command": "clang++ -c src/mongo/db/example.cpp",
|
||||
},
|
||||
index=1,
|
||||
)
|
||||
|
||||
def test_selection_rejects_non_standard_compile_commands_json(self):
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as compiledb:
|
||||
json.dump(
|
||||
[
|
||||
{
|
||||
"directory": "/repo",
|
||||
"file": "src/mongo/db/example.cpp",
|
||||
"arguments": ["clang++", "-c", "src/mongo/db/example.cpp"],
|
||||
"target": "//src/mongo/db:example",
|
||||
}
|
||||
],
|
||||
compiledb,
|
||||
)
|
||||
compiledb_path = compiledb.name
|
||||
|
||||
try:
|
||||
with self.assertRaisesRegex(ValueError, r"non-standard keys \['target'\]"):
|
||||
validator._select_entries_for_test_compile(compiledb_path, n=0)
|
||||
finally:
|
||||
os.remove(compiledb_path)
|
||||
|
||||
def test_ensure_compiledb_exists_builds_install_wiredtiger_too(self):
|
||||
with mock.patch.object(validator.os.path, "exists", return_value=False):
|
||||
with mock.patch.object(validator.subprocess, "run") as mock_run:
|
||||
validator._ensure_compiledb_exists("compile_commands.json")
|
||||
|
||||
mock_run.assert_called_once_with(
|
||||
["bazel", "build", "compiledb", "install-wiredtiger"], check=True
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -103,6 +103,7 @@ generate_embedded_public_key_header(
|
||||
name = "embed_mongot_key",
|
||||
embedded_key_header_path = "mongot_extension_signing_key.h",
|
||||
public_key_path = "mongot-extension.asc",
|
||||
tags = ["gen_source"],
|
||||
target_compatible_with = select({
|
||||
"@platforms//os:linux": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
|
||||
@ -310,9 +310,12 @@ else
|
||||
if [[ "$wrapper_redirect_output" == "1" ]]; then
|
||||
exec 1>&3 2>&4
|
||||
fi
|
||||
|
||||
$bazel_real "${new_args[@]}"
|
||||
bazel_exit_code=$?
|
||||
|
||||
bazel_exit_code=0
|
||||
if [[ ${#new_args[@]} -ne 0 ]]; then
|
||||
$bazel_real "${new_args[@]}"
|
||||
bazel_exit_code=$?
|
||||
fi
|
||||
( >&2 $python $REPO_ROOT/bazel/wrapper_hook/post_bazel_hook.py $bazel_real )
|
||||
exit $bazel_exit_code
|
||||
fi
|
||||
|
||||
Loading…
Reference in New Issue
Block a user