Added a common framework for built tools (#559)

Co-authored-by: James Sharpe <james.sharpe@zenotech.com>
This commit is contained in:
UebelAndre 2021-03-15 10:17:59 -07:00 committed by GitHub
parent 6aceb1e4c3
commit ec65e18bb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 129 deletions

View File

@ -4,4 +4,7 @@ bzl_library(
name = "bzl_srcs",
srcs = glob(["**/*.bzl"]),
visibility = ["//:__subpackages__"],
deps = [
"//foreign_cc/built_tools/private:bzl_srcs",
],
)

View File

@ -1,46 +1,33 @@
""" Rule for building CMake from sources. """
load("//foreign_cc/private:detect_root.bzl", "detect_root")
load("//foreign_cc/private:shell_script_helper.bzl", "convert_shell_script")
load(
"//foreign_cc/built_tools/private:built_tools_framework.bzl",
"FOREIGN_CC_BUILT_TOOLS_ATTRS",
"FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS",
"built_tool_rule_impl",
)
def _cmake_tool(ctx):
root = detect_root(ctx.attr.cmake_srcs)
cmake = ctx.actions.declare_directory("cmake")
def _cmake_tool_impl(ctx):
script = [
"export BUILD_DIR=##pwd##",
"export BUILD_TMPDIR=$${BUILD_DIR}$$.build_tmpdir",
"##copy_dir_contents_to_dir## ./{} $BUILD_TMPDIR".format(root),
"##mkdirs## " + cmake.path,
"cd $$BUILD_TMPDIR$$",
"./bootstrap --prefix=install",
"./bootstrap --prefix=$$INSTALLDIR$$",
# TODO: Use make from a toolchain
"make",
"make install",
"##copy_dir_contents_to_dir## ./install $BUILD_DIR/" + cmake.path,
"cd $$BUILD_DIR$$",
]
script_text = convert_shell_script(ctx, script)
ctx.actions.run_shell(
mnemonic = "BootstrapCMake",
inputs = ctx.attr.cmake_srcs.files,
outputs = [cmake],
tools = [],
use_default_shell_env = True,
command = script_text,
execution_requirements = {"block-network": ""},
return built_tool_rule_impl(
ctx,
script,
ctx.actions.declare_directory("cmake"),
"BootstrapCMake",
)
return [DefaultInfo(files = depset([cmake]))]
cmake_tool = rule(
doc = "Rule for building CMake. Invokes bootstrap script and make install.",
attrs = {
"cmake_srcs": attr.label(mandatory = True),
},
host_fragments = ["cpp"],
attrs = FOREIGN_CC_BUILT_TOOLS_ATTRS,
host_fragments = FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS,
output_to_genfiles = True,
implementation = _cmake_tool,
implementation = _cmake_tool_impl,
toolchains = [
str(Label("//foreign_cc/private/shell_toolchain/toolchains:shell_commands")),
"@bazel_tools//tools/cpp:toolchain_type",

View File

@ -1,59 +1,43 @@
""" Rule for building GNU Make from sources. """
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("//foreign_cc/private:detect_root.bzl", "detect_root")
load("//foreign_cc/private:run_shell_file_utils.bzl", "fictive_file_in_genroot")
load("//foreign_cc/private:shell_script_helper.bzl", "convert_shell_script")
load(
"//foreign_cc/built_tools/private:built_tools_framework.bzl",
"FOREIGN_CC_BUILT_TOOLS_ATTRS",
"FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS",
"built_tool_rule_impl",
)
load("//foreign_cc/private:shell_script_helper.bzl", "os_name")
def _make_tool(ctx):
root = detect_root(ctx.attr.make_srcs)
cc_toolchain = find_cpp_toolchain(ctx)
# we need this fictive file in the root to get the path of the root in the script
empty = fictive_file_in_genroot(ctx.actions, ctx.label.name)
make = ctx.actions.declare_directory("make")
def _make_tool_impl(ctx):
script = [
"export EXT_BUILD_ROOT=##pwd##",
"export INSTALLDIR=$$EXT_BUILD_ROOT$$/" + empty.file.dirname + "/" + ctx.attr.name,
"export BUILD_TMPDIR=$$INSTALLDIR$$.build_tmpdir",
"##mkdirs## $$BUILD_TMPDIR$$",
"##copy_dir_contents_to_dir## ./{} $BUILD_TMPDIR".format(root),
"cd $$BUILD_TMPDIR$$",
"./configure --disable-dependency-tracking --prefix=$$EXT_BUILD_ROOT$$/{}".format(make.path),
"./configure --disable-dependency-tracking --prefix=$$INSTALLDIR$$",
"./build.sh",
"./make install",
empty.script,
]
script_text = convert_shell_script(ctx, script)
ctx.actions.run_shell(
mnemonic = "BootstrapMake",
inputs = ctx.attr.make_srcs.files,
outputs = [make, empty.file],
tools = cc_toolchain.all_files,
use_default_shell_env = True,
command = script_text,
execution_requirements = {"block-network": ""},
if "win" in os_name(ctx):
script.extend([
"./make.exe install",
])
else:
script.extend([
"./make install",
])
return built_tool_rule_impl(
ctx,
script,
ctx.actions.declare_directory("make"),
"BootstrapGNUMake",
)
return [DefaultInfo(files = depset([make]))]
make_tool = rule(
doc = "Rule for building Make. Invokes configure script and make install.",
attrs = {
"make_srcs": attr.label(
doc = "target with the Make sources",
mandatory = True,
),
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
},
host_fragments = ["cpp"],
attrs = FOREIGN_CC_BUILT_TOOLS_ATTRS,
host_fragments = FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS,
output_to_genfiles = True,
implementation = _make_tool,
implementation = _make_tool_impl,
toolchains = [
"@rules_foreign_cc//foreign_cc/private/shell_toolchain/toolchains:shell_commands",
str(Label("//foreign_cc/private/shell_toolchain/toolchains:shell_commands")),
"@bazel_tools//tools/cpp:toolchain_type",
],
)

View File

@ -1,42 +1,34 @@
""" Rule for building Ninja from sources. """
load("//foreign_cc/private:detect_root.bzl", "detect_root")
load("//foreign_cc/private:shell_script_helper.bzl", "convert_shell_script")
load(
"//foreign_cc/built_tools/private:built_tools_framework.bzl",
"FOREIGN_CC_BUILT_TOOLS_ATTRS",
"FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS",
"built_tool_rule_impl",
)
def _ninja_tool(ctx):
root = detect_root(ctx.attr.ninja_srcs)
ninja = ctx.actions.declare_directory("ninja")
def _ninja_tool_impl(ctx):
script = [
"##mkdirs## " + ninja.path,
"##copy_dir_contents_to_dir## ./{} {}".format(root, ninja.path),
"cd " + ninja.path,
"./configure.py --bootstrap",
# TODO: Reduce unnecessary copys and only keep what's required
"##copy_dir_contents_to_dir## $$BUILD_TMPDIR$$ $$INSTALLDIR$$",
]
script_text = convert_shell_script(ctx, script)
ctx.actions.run_shell(
mnemonic = "BootstrapNinja",
inputs = ctx.attr.ninja_srcs.files,
outputs = [ninja],
tools = [],
use_default_shell_env = True,
command = script_text,
execution_requirements = {"block-network": ""},
return built_tool_rule_impl(
ctx,
script,
ctx.actions.declare_directory("ninja"),
"BootstrapNinjaBuild",
)
return [DefaultInfo(files = depset([ninja]))]
ninja_tool = rule(
doc = "Rule for building Ninja. Invokes configure script and make install.",
attrs = {
"ninja_srcs": attr.label(mandatory = True),
},
host_fragments = ["cpp"],
doc = "Rule for building Ninja. Invokes configure script.",
attrs = FOREIGN_CC_BUILT_TOOLS_ATTRS,
host_fragments = FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS,
output_to_genfiles = True,
implementation = _ninja_tool,
implementation = _ninja_tool_impl,
toolchains = [
"@rules_foreign_cc//foreign_cc/private/shell_toolchain/toolchains:shell_commands",
str(Label("//foreign_cc/private/shell_toolchain/toolchains:shell_commands")),
"@bazel_tools//tools/cpp:toolchain_type",
],
)

View File

@ -0,0 +1,7 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
bzl_library(
name = "bzl_srcs",
srcs = glob(["**/*.bzl"]),
visibility = ["//:__subpackages__"],
)

View File

@ -0,0 +1,93 @@
"""A module defining a common framework for "built_tools" rules"""
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("//foreign_cc/private:detect_root.bzl", "detect_root")
load("//foreign_cc/private:framework.bzl", "wrap_outputs")
load("//foreign_cc/private:shell_script_helper.bzl", "convert_shell_script")
# Common attributes for all built_tool rules
FOREIGN_CC_BUILT_TOOLS_ATTRS = {
"srcs": attr.label(
doc = "The target containing the build tool's sources",
mandatory = True,
),
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
}
# Common host fragments for all built_tool rules
FOREIGN_CC_BUILT_TOOLS_HOST_FRAGMENTS = [
"cpp",
]
def built_tool_rule_impl(ctx, script_lines, out_dir, mnemonic):
"""Framework function for bootstrapping C/C++ build tools.
This macro should be shared by all "built-tool" rules defined in rules_foreign_cc.
Any rule implementing this function should ensure that the appropriate artifacts
are placed in a directory represented by the `INSTALLDIR` environment variable.
Args:
ctx (ctx): The current rule's context object
script_lines (list): A list of lines of a bash script for building the build tool
out_dir (File): The output directory of the build tool
mnemonic (str): The mnemonic of the build action
Returns:
list: A list of providers
"""
root = detect_root(ctx.attr.srcs)
script = [
# TODO: The script prelude should be used but for some reason it fails
# on RBE builds.
# "##script_prelude##",
"export EXT_BUILD_ROOT=##pwd##",
"export INSTALLDIR=$$EXT_BUILD_ROOT$$/{}".format(out_dir.path),
"export BUILD_TMPDIR=$$INSTALLDIR$$.build_tmpdir",
"##mkdirs## $$BUILD_TMPDIR$$",
"##copy_dir_contents_to_dir## ./{} $$BUILD_TMPDIR$$".format(root),
"cd $$BUILD_TMPDIR$$",
]
script.append("set -x")
script.extend(script_lines)
script.append("set +x")
script_text = "\n".join([
"#!/usr/bin/env bash",
convert_shell_script(ctx, script),
"",
])
lib_name = ctx.attr.name
wrapped_outputs = wrap_outputs(ctx, lib_name, mnemonic, script_text)
cc_toolchain = find_cpp_toolchain(ctx)
tools = depset(
[wrapped_outputs.wrapper_script_file, wrapped_outputs.script_file],
transitive = [cc_toolchain.all_files],
)
# The use of `run_shell` here is intended to ensure bash is correctly setup on windows
# environments. This should not be replaced with `run` until a cross platform implementation
# is found that guarantees bash exists or appropriately errors out.
ctx.actions.run_shell(
mnemonic = mnemonic,
inputs = ctx.attr.srcs.files,
outputs = [out_dir, wrapped_outputs.log_file],
tools = tools,
use_default_shell_env = True,
command = wrapped_outputs.wrapper_script_file.path,
execution_requirements = {"block-network": ""},
)
return [
DefaultInfo(files = depset([out_dir])),
OutputGroupInfo(
log_file = depset([wrapped_outputs.log_file]),
script_file = depset([wrapped_outputs.script_file]),
wrapper_script_file = depset([wrapped_outputs.wrapper_script_file]),
),
]

View File

@ -382,25 +382,10 @@ def cc_external_rule_impl(ctx, attrs):
if "requires-network" in ctx.attr.tags:
execution_requirements = {"requires-network": ""}
# We need to create a native batch script on windows to ensure the wrapper
# script can correctly be envoked with bash.
wrapper = wrapped_outputs.wrapper_script_file
extra_tools = []
if "win" in execution_os_name:
batch_wrapper = ctx.actions.declare_file(wrapper.short_path + ".bat")
ctx.actions.write(
output = batch_wrapper,
content = "\n".join([
"@ECHO OFF",
"START /b /wait bash {}".format(wrapper.path),
"",
]),
is_executable = True,
)
extra_tools.append(wrapper)
wrapper = batch_wrapper
ctx.actions.run(
# The use of `run_shell` here is intended to ensure bash is correctly setup on windows
# environments. This should not be replaced with `run` until a cross platform implementation
# is found that guarantees bash exists or appropriately errors out.
ctx.actions.run_shell(
mnemonic = "Cc" + attrs.configure_name.capitalize() + "MakeRule",
inputs = depset(inputs.declared_inputs),
outputs = rule_outputs + [
@ -408,13 +393,13 @@ def cc_external_rule_impl(ctx, attrs):
wrapped_outputs.log_file,
],
tools = depset(
[wrapped_outputs.script_file] + extra_tools + ctx.files.data + ctx.files.tools_deps + ctx.files.additional_tools,
[wrapped_outputs.script_file, wrapped_outputs.wrapper_script_file] + ctx.files.data + ctx.files.tools_deps + ctx.files.additional_tools,
transitive = [cc_toolchain.all_files] + [data[DefaultInfo].default_runfiles.files for data in data_dependencies],
),
# TODO: Default to never using the default shell environment to make builds more hermetic. For now, every platform
# but MacOS will take the default PATH passed by Bazel, not that from cc_toolchain.
use_default_shell_env = execution_os_name != "osx",
executable = wrapper,
command = wrapped_outputs.wrapper_script_file.path,
execution_requirements = execution_requirements,
# this is ignored if use_default_shell_env = True
env = cc_env,
@ -467,10 +452,11 @@ WrappedOutputs = provider(
)
# buildifier: disable=function-docstring
def wrap_outputs(ctx, lib_name, configure_name, script_text):
build_log_file = ctx.actions.declare_file("{}_logs/{}.log".format(lib_name, configure_name))
wrapper_script_file = ctx.actions.declare_file("{}_scripts/{}_wrapper_script.sh".format(lib_name, configure_name))
build_script_file = ctx.actions.declare_file("{}_scripts/{}_script.sh".format(lib_name, configure_name))
def wrap_outputs(ctx, lib_name, configure_name, script_text, build_script_file = None):
build_log_file = ctx.actions.declare_file("{}_foreign_cc/{}.log".format(lib_name, configure_name))
build_script_file = ctx.actions.declare_file("{}_foreign_cc/build_script.sh".format(lib_name))
wrapper_script_file = ctx.actions.declare_file("{}_foreign_cc/wrapper_build_script.sh".format(lib_name))
ctx.actions.write(
output = build_script_file,
content = script_text,

View File

@ -67,7 +67,7 @@ native_tool_toolchain(
make_tool(
name = "make_tool",
make_srcs = "@gnumake_src//:all_srcs",
srcs = "@gnumake_src//:all_srcs",
tags = ["manual"],
)
@ -84,7 +84,7 @@ native_tool_toolchain(
cmake_tool(
name = "cmake_tool",
cmake_srcs = "@cmake_src//:all_srcs",
srcs = "@cmake_src//:all_srcs",
tags = ["manual"],
)
@ -101,7 +101,7 @@ native_tool_toolchain(
ninja_tool(
name = "ninja_tool",
ninja_srcs = "@ninja_build_src//:all_srcs",
srcs = "@ninja_build_src//:all_srcs",
tags = ["manual"],
)