SERVER-82638 add ninja to bazel integration support.

This commit is contained in:
Daniel Moody 2023-11-11 14:02:02 +00:00 committed by Evergreen Agent
parent 1e636ac4c5
commit c431f99d1f
9 changed files with 259 additions and 29 deletions

2
.gitignore vendored
View File

@ -274,6 +274,8 @@ bazel-out
bazel-testlogs
bazelisk
.bazelrc.local
.bazel_info_for_ninja.txt
.ninja_last_command_line_targets.txt
# generated configs for external fixture suites
docker_compose/

View File

@ -6591,3 +6591,16 @@ for i, s in enumerate(BUILD_TARGETS):
# SConscripts have been read but before building begins.
libdeps.LibdepLinter(env).final_checks()
libdeps.generate_libdeps_graph(env)
# We put this next section at the end of the SConstruct since all the targets
# have been declared, and we know all possible bazel targets so
# we can now generate this info into a file for the ninja build to consume.
if env.get("BAZEL_BUILD_ENABLED") and env.GetOption('ninja') != "disabled":
# convert the SCons FunctioAction into a format that ninja can understand
env.NinjaRegisterFunctionHandler("bazel_builder_action", env.NinjaBazelBuilder)
# we generate the list of all targets that were labeled Bazel* builder targets
# via the emitter, this outputs a json file which will be read during the ninja
# build.
env.GenerateBazelInfoForNinja()

View File

@ -127,7 +127,7 @@ use_wait_for_debugger = rule(
# =========
use_disable_ref_track_provider = provider(
doc = "Disables runtime tracking of REF state changes for pages within wiredtiger. "
doc = "Disables runtime tracking of REF state changes for pages within wiredtiger. " +
"Tracking the REF state changes is useful for debugging but there is a small performance cost.",
fields = ["enabled"],
)

View File

@ -3262,6 +3262,29 @@ tasks:
ninja_file: "opt.ninja"
targets: "install-devcore compiledb"
- name: compile_ninja_bazel
tags: []
depends_on:
- name: version_expansions_gen
variant: generate-tasks-for-version
commands:
- func: "scons compile"
vars:
generating_for_ninja: true
separate_debug: off
task_compile_flags: >-
--build-profile=fast
BAZEL_BUILD_ENABLED=1
BAZEL_INTEGRATION_DEBUG=1
BAZEL_FLAGS=--config=local
CCACHE=
ICECC=
- *f_expansions_write
- func: "ninja compile"
vars:
ninja_file: "fast.ninja"
targets: "install-wiredtiger compiledb"
- name: compile_ninja_fast_profile
tags: []
depends_on:
@ -9449,6 +9472,7 @@ task_groups:
- compile_bazel_c_and_asm_targets
- compile_bazel_libunwind
- compile_bazel_program
- compile_ninja_bazel
- run_bazel_program
- <<: *compile_bazel_task_group_template
@ -9456,6 +9480,7 @@ task_groups:
tasks:
- compile_bazel_dist_test_windows
- run_bazel_program_windows
- compile_ninja_bazel
- <<: *compile_task_group_template
name: compile_upload_benchmarks_TG

18
poetry.lock generated
View File

@ -1694,6 +1694,16 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@ -1769,13 +1779,13 @@ test = ["pytest", "pytest-cov"]
[[package]]
name = "mongo-ninja-python"
version = "1.11.1.5"
version = "1.11.1.6"
description = ""
optional = false
python-versions = "*"
files = [
{file = "mongo_ninja_python-1.11.1.5-py2.py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1dfee79cb0daec9db926925ff2508c14709a260072540be694dd91b39913b52c"},
{file = "mongo_ninja_python-1.11.1.5-py2.py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:e02e1c1cbddeeebd579602dc3e7b5d171ca588574d8dbd021df95c5cc56ab05a"},
{file = "mongo_ninja_python-1.11.1.6-py2.py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0be71b6b84aaa8520f90aad6104da41cb88208d1c132080901770ea7b2307eda"},
{file = "mongo_ninja_python-1.11.1.6-py2.py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:c74cee08c776e22edaa558c99d144fa5cb1e6783605d9fedbf2a9a3647901f96"},
]
[package.dependencies]
@ -4344,4 +4354,4 @@ libdeps = ["cxxfilt", "eventlet", "flask", "flask-cors", "gevent", "lxml", "prog
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4.0"
content-hash = "a71db329bfe251a27ae909e027c9eceb8fd073ac088e5ee20aa0171a8b5f3ba1"
content-hash = "b0d2ab7c0f63d8da8afa303df939ed0af898453b57740733f189f0bdc29e6e83"

View File

@ -112,7 +112,7 @@ cryptography = [
{ version = "36.0.2", markers = "platform_machine != 's390x' and platform_machine != 'ppc64le'" },
]
mongo-ninja-python = [
{ version = "1.11.1.5", markers = "sys_platform == 'linux' and platform_machine != 's390x' and platform_machine != 'ppc64le'" },
{ version = "1.11.1.6", markers = "sys_platform == 'linux' and platform_machine != 's390x' and platform_machine != 'ppc64le'" },
]
ninja = [
{ version = ">=1.10.0", markers = "sys_platform != 'linux' or platform_machine == 's390x' or platform_machine == 'ppc64le'" },

View File

@ -0,0 +1,93 @@
import subprocess
import sys
import json
import os
import shutil
import argparse
parser = argparse.ArgumentParser(description='Ninja Bazel builder.')
parser.add_argument('--ninja-file', type=str, help="The ninja file in use", default="build.ninja")
parser.add_argument('--debug', action='store_true', help="Turn on extra debug output")
args = parser.parse_args()
# This corresponds to BAZEL_INTEGRATION_DEBUG=1 from SCons command line
if args.debug:
def print_debug(msg):
print("[BAZEL_INTEGRATION_DEBUG] " + msg)
else:
def print_debug(msg):
pass
# our ninja python module intercepts the command lines and
# prints out the targets everytime ninja is executed
ninja_command_line_targets = []
try:
ninja_last_cmd_file = '.ninja_last_command_line_targets.txt'
with open(ninja_last_cmd_file) as f:
ninja_command_line_targets = [target.strip() for target in f.readlines() if target.strip()]
except OSError as exc:
print(
f"Failed to open {ninja_last_cmd_file}, this is expected to be generated on ninja execution by the mongo-ninja-python module."
)
raise exc
print_debug(f"NINJA COMMAND LINE TARGETS:{os.linesep}{os.linesep.join(ninja_command_line_targets)}")
# Our ninja generation process generates all the build info related to
# the specific ninja file
ninja_build_info = dict()
try:
bazel_info_file = '.bazel_info_for_ninja.txt'
with open(bazel_info_file) as f:
ninja_build_info = json.load(f)
except OSError as exc:
print(
f"Failed to open {bazel_info_file}, this is expected to be generated by scons during ninja generation."
)
raise exc
# flip the targets map for optimized use later
bazel_out_to_bazel_target = dict()
for bazel_t in ninja_build_info['targets'].values():
bazel_out_to_bazel_target[bazel_t['bazel_output']] = bazel_t['bazel_target']
# run ninja and get the deps from the passed command line targets so we can check if any deps are bazel targets
ninja_inputs_cmd = ['ninja', '-f', args.ninja_file, '-t', 'inputs'] + ninja_command_line_targets
print_debug(f"NINJA GET INPUTS CMD: {' '.join(ninja_inputs_cmd)}")
ninja_proc = subprocess.run(ninja_inputs_cmd, capture_output=True, text=True, check=True)
deps = ninja_proc.stdout.split(os.linesep)
print_debug(f"COMMAND LINE DEPS:{os.linesep}{os.linesep.join(deps)}")
# isolate just the raw output files for the list intersection
bazel_outputs = [bazel_t['bazel_output'] for bazel_t in ninja_build_info['targets'].values()]
print_debug(f"BAZEL OUTPUTS:{os.linesep}{os.linesep.join(bazel_outputs)}")
# now out of possible bazel outputs find which are deps of the requested command line targets
outputs_to_build = list(set(deps).intersection(bazel_outputs))
print_debug(f"BAZEL OUTPUTS TO BUILD: {outputs_to_build}")
# convert from outputs (raw files) to bazel targets (bazel labels i.e //src/db/mongo:target)
targets_to_build = [bazel_out_to_bazel_target[out] for out in outputs_to_build]
if not targets_to_build:
print("Did not find any bazel targets to build, bazel should not have been invoked.")
print(f"correspondig raw output files to build: {outputs_to_build}")
print(f"command line targets: {ninja_command_line_targets}")
print(f"input target deps: {deps}")
sys.exit(1)
# ninja will automatically create directories for any outputs, but in this case
# bazel will be creating a symlink for the bazel-out dir to its cache. We don't want
# ninja to interfere so delete the dir if it was not a link (made by bazel)
if not os.path.islink("bazel-out"):
shutil.rmtree("bazel-out")
# now we are ready to build all bazel buildable files
print_debug(f"BAZEL TARGETS TO BUILD:{os.linesep}{os.linesep.join(targets_to_build)}")
print(f"{' '.join(ninja_build_info['bazel_cmd'] + targets_to_build)}")
bazel_proc = subprocess.run(ninja_build_info['bazel_cmd'] + targets_to_build, check=True)

View File

@ -1,5 +1,6 @@
import atexit
import functools
import json
import os
import platform
import queue
@ -9,7 +10,7 @@ import stat
import subprocess
import threading
import time
from typing import List, Dict, Set, Tuple
from typing import List, Dict, Set, Tuple, Any
import urllib.request
import sys
@ -27,8 +28,8 @@ _SUPPORTED_PLATFORM_MATRIX = [
class Globals:
# key: scons target, value: bazel target
scons2bazel_targets: Dict[str, str] = dict()
# key: scons target, value: {bazel target, bazel output}
scons2bazel_targets: Dict[str, Dict[str, str]] = dict()
# key: scons output, value: bazel outputs
scons_output_to_bazel_outputs: Dict[str, List[str]] = dict()
@ -85,14 +86,6 @@ def bazel_target_emitter(
env: SCons.Environment.Environment) -> Tuple[List[SCons.Node.Node], List[SCons.Node.Node]]:
"""This emitter will map any scons outputs to bazel outputs so copy can be done later."""
# sometimes there are multiple outputs, here we are mapping all the
# bazel outputs to the first scons output as the key. Later the targets
# will be lined up for the copy.
Globals.scons_output_to_bazel_outputs[target[0]] = []
# only the first target in a multi target node will represent the nodes
Globals.scons2bazel_targets[target[0].abspath] = convert_scons_node_to_bazel_target(target[0])
for t in target:
# normally scons emitters conveniently build-ify the target paths so it will
@ -105,7 +98,10 @@ def bazel_target_emitter(
# output location and then set that as the new source for the builders.
bazel_out_dir = env.get("BAZEL_OUT_DIR")
bazel_out_target = f'{bazel_out_dir}/{bazel_dir}/{os.path.basename(bazel_path)}'
Globals.scons_output_to_bazel_outputs[target[0]] += [bazel_out_target]
Globals.scons2bazel_targets[t.path] = {
'bazel_target': convert_scons_node_to_bazel_target(t), 'bazel_output': bazel_out_target
}
return (target, source)
@ -136,8 +132,9 @@ def bazel_builder_action(env: SCons.Environment.Environment, target: List[SCons.
return False
bazel_target = Globals.scons2bazel_targets[target[0].abspath]
bazel_debug(f"Checking if {bazel_target} is done...")
bazel_output = Globals.scons2bazel_targets[target[0].path]['bazel_output']
bazel_target = Globals.scons2bazel_targets[target[0].path]['bazel_target']
bazel_debug(f"Checking if {bazel_output} is done...")
# put the target into the work queue the poll until its
# been placed into the done queue
@ -159,7 +156,8 @@ def bazel_builder_action(env: SCons.Environment.Environment, target: List[SCons.
# now copy all the targets out to the scons tree, note that target is a
# list of nodes so we need to stringify it for copyfile
for s, t in zip(Globals.scons_output_to_bazel_outputs[target[0]], target):
for t in target:
s = Globals.scons2bazel_targets[t.path]['bazel_output']
bazel_debug(f"Copying {s} from bazel tree to {t} in the scons tree.")
shutil.copyfile(s, str(t))
@ -170,6 +168,35 @@ BazelCopyOutputsAction = SCons.Action.FunctionAction(
)
# the ninja tool has some API that doesn't support using SCons env methods
# instead of adding more API to the ninja tool which has a short life left
# we just add the unused arg _dup_env
def ninja_bazel_builder(env: SCons.Environment.Environment, _dup_env: SCons.Environment.Environment,
node: SCons.Node.Node) -> Dict[str, Any]:
"""
Translator for ninja which turns the scons bazel_builder_action
into a build node that ninja can digest.
"""
outs = env.NinjaGetOutputs(node)
ins = [Globals.scons2bazel_targets[out]['bazel_output'] for out in outs]
# this represents the values the ninja_syntax.py will use to generate to real
# ninja syntax defined in the ninja manaul: https://ninja-build.org/manual.html#ref_ninja_file
return {
"outputs": outs,
"inputs": ins,
"rule": "CMD",
"variables": {
"cmd":
' '.join([
f"$COPY {input_node} {output_node};"
for input_node, output_node in zip(ins, outs)
])
},
}
def bazel_batch_build_thread(log_dir: str) -> None:
"""This thread continuelly runs bazel when ever new targets are found."""
@ -261,7 +288,7 @@ def bazel_batch_build_thread(log_dir: str) -> None:
raise exc
def create_bazel_builder(builder):
def create_bazel_builder(builder: SCons.Builder.Builder) -> SCons.Builder.Builder:
return SCons.Builder.Builder(
action=BazelCopyOutputsAction,
prefix=builder.prefix,
@ -287,6 +314,38 @@ def create_program_builder(env: SCons.Environment.Environment) -> None:
env['BUILDERS']['BazelProgram'] = create_bazel_builder(env['BUILDERS']["Program"])
def generate_bazel_info_for_ninja(env: SCons.Environment.Environment) -> None:
# create a json file which contains all the relevant info from this generation
# that bazel will need to construct the correct command line for any given targets
ninja_bazel_build_json = {
'bazel_cmd': Globals.bazel_base_build_command,
'defaults': [str(t) for t in SCons.Script.DEFAULT_TARGETS],
'targets': Globals.scons2bazel_targets
}
with open('.bazel_info_for_ninja.txt', 'w') as f:
json.dump(ninja_bazel_build_json, f)
# we also store the outputs in the env (the passed env is intended to be
# the same main env ninja tool is constructed with) so that ninja can
# use these to contruct a build node for running bazel where bazel list the
# correct bazel outputs to be copied to the scons tree. We also handle
# calculating the inputs. This will be the all the inputs of the outs,
# but and input can not also be an output. If a node is found in both
# inputs and outputs, remove it from the inputs, as it will be taken care
# internally by bazel build.
ninja_bazel_outs = []
ninja_bazel_ins = []
for scons_t, bazel_t in Globals.scons2bazel_targets.items():
ninja_bazel_outs += [bazel_t['bazel_output']]
ninja_bazel_ins += env.NinjaGetInputs(env.File(scons_t))
if scons_t in ninja_bazel_ins:
ninja_bazel_ins.remove(scons_t)
# This is to be used directly by ninja later during generation of the ninja file
env["NINJA_BAZEL_OUTPUTS"] = ninja_bazel_outs
env["NINJA_BAZEL_INPUTS"] = ninja_bazel_ins
# Establishes logic for BazelLibrary build rule
def generate(env: SCons.Environment.Environment) -> None:
@ -372,15 +431,21 @@ def generate(env: SCons.Environment.Environment) -> None:
create_library_builder(env)
create_program_builder(env)
def shutdown_bazel_builer():
Globals.kill_bazel_thread_flag = True
if env.GetOption('ninja') == "disabled":
atexit.register(shutdown_bazel_builer)
def shutdown_bazel_builer():
Globals.kill_bazel_thread_flag = True
bazel_build_thread = threading.Thread(target=bazel_batch_build_thread,
args=(env.Dir("$BUILD_ROOT/scons/bazel").path, ))
bazel_build_thread.daemon = True
bazel_build_thread.start()
atexit.register(shutdown_bazel_builer)
# ninja will handle the build so do not launch the bazel batch thread
bazel_build_thread = threading.Thread(target=bazel_batch_build_thread,
args=(env.Dir("$BUILD_ROOT/scons/bazel").path, ))
bazel_build_thread.daemon = True
bazel_build_thread.start()
env.AddMethod(generate_bazel_info_for_ninja, "GenerateBazelInfoForNinja")
env.AddMethod(ninja_bazel_builder, "NinjaBazelBuilder")
else:
env['BUILDERS']['BazelLibrary'] = env['BUILDERS']['Library']
env['BUILDERS']['BazelProgram'] = env['BUILDERS']['Program']

View File

@ -643,6 +643,20 @@ class NinjaState:
"restat": 1,
},
}
if self.env.get('BAZEL_BUILD_ENABLED'):
self.rules.update({
"RUN_BAZEL_BUILD": {
"command": (
f"{sys.executable} " + "site_scons/mongo/ninja_bazel_build.py " +
f"--ninja-file={self.env.get('NINJA_PREFIX')}.{self.env.get('NINJA_SUFFIX')} "
+ "--debug" if self.env.get('BAZEL_INTEGRATION_DEBUG') else ""),
"description": "Running bazel build",
"pool": "console",
"restat": 1,
}
})
num_jobs = self.env.get('NINJA_MAX_JOBS', self.env.GetOption("num_jobs"))
self.pools = {
"local_pool": num_jobs,
@ -1008,6 +1022,14 @@ class NinjaState:
},
)
if self.env.get("BAZEL_BUILD_ENABLED"):
ninja_sorted_build(
ninja,
outputs=self.env["NINJA_BAZEL_OUTPUTS"],
inputs=self.env["NINJA_BAZEL_INPUTS"],
rule="RUN_BAZEL_BUILD",
)
# This sets up a dependency edge between build.ninja.in and build.ninja
# without actually taking any action to transform one into the other
# because we write both files ourselves later.