blob: 0c4e963cfa73fabcca54696a5f7df7ea8ed1ecdb [file] [log] [blame]
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: cros-bazel.eclass
# @MAINTAINER:
# Michael Martis <martis@chromium.org>
# @DESCRIPTION:
# A utility eclass for Chromium OS-specific additions to the Bazel eclass. In
# particular, functions supporting cross-compilation are provided.
if [[ ! ${_CROS_BAZEL_ECLASS} ]]; then
inherit bazel toolchain-funcs
# @ECLASS-VARIABLE: BAZEL_BAZELRC
# @DESCRIPTION:
# The location of the resource file used to provide Portage's build
# configuration details to Bazel. Must be kept in sync with the Bazel eclass.
BAZEL_BAZELRC="${T}/bazelrc"
# @ECLASS-VARIABLE: BAZEL_CC_BAZELRC
# @INTERNAL
# @DESCRIPTION:
# The location of the resource file specifying build configuration details for
# cross compilation (if setup). Is 'sourced' by BAZEL_BAZELRC.
BAZEL_CC_BAZELRC="${T}/cc_bazelrc"
# @ECLASS-VARIABLE: BAZEL_PORTAGE_PACKAGE_DIR
# @INTERNAL
# @DESCRIPTION:
# The directory used to store generated configuration targets (e.g. toolchain
# targets for cross compilation).
BAZEL_PORTAGE_PACKAGE_DIR="${T}/portage_packages/"
# @ECLASS-VARIABLE: BAZEL_CC_CONFIG_DIR
# @INTERNAL
# @DESCRIPTION:
# The directory (relative to BAZEL_PORTAGE_PACKAGE_DIR) in which "host" and
# "target" toolchain targets are generated for cross compilation.
BAZEL_CC_CONFIG_DIR="ebazel_cc_config"
# @ECLASS-VARIABLE: BAZEL_CC_BUILD
# @INTERNAL
# @DESCRIPTION:
# A template (with Bash-style variable placeholders) used to populate build
# files for both the "host" and "target" toolchain targets.
# shellcheck disable=SC2016
BAZEL_CC_BUILD='package(default_visibility = ["//visibility:public"])
filegroup(name = "empty")
# We should really be using @platforms//cpu:x86_64 and friends, but
# to keep this compatible with Bazel 0.24.1, we need to use the legacy
# definitions.
# TODO(crbug/1102798): Once Bazel is uprevved, change these to the @platforms defs.
amd64_constraints = [
"@bazel_tools//platforms:x86_64",
"@bazel_tools//platforms:linux",
]
k8_constraints = amd64_constraints
arm_constraints = [
"@bazel_tools//platforms:arm",
"@bazel_tools//platforms:linux",
]
aarch64_constraints = [
"@bazel_tools//platforms:aarch64",
"@bazel_tools//platforms:linux",
]
platform(
name = "amd64_platform",
constraint_values = amd64_constraints,
)
platform(
name = "k8_platform",
constraint_values = k8_constraints,
)
platform(
name = "arm_platform",
constraint_values = arm_constraints,
)
platform(
name = "aarch64_platform",
constraint_values = aarch64_constraints,
)
cc_toolchain_suite(
name = "toolchain",
toolchains = {
"amd64|local": "portage_toolchain",
"arm|local": "portage_toolchain",
"aarch64|local": "portage_toolchain",
"arm64|local": "portage_toolchain",
"k8|local": "portage_toolchain",
},
)
cc_toolchain(
name = "portage_toolchain",
toolchain_identifier = "portage-toolchain",
toolchain_config = ":portage_toolchain_config",
all_files = ":empty",
compiler_files = ":empty",
dwp_files = ":empty",
linker_files = ":empty",
objcopy_files = ":empty",
strip_files = ":empty",
supports_param_files = 0,
)
toolchain(
name = "cc-toolchain-${cpu_str}",
# compilation execution is always on the host, hence amd64
exec_compatible_with = amd64_constraints,
target_compatible_with = ${cpu_str}_constraints,
toolchain = ":portage_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
load(":cc_toolchain_config.bzl", "cc_toolchain_config")
cc_toolchain_config(name = "portage_toolchain_config")
'
# @ECLASS-VARIABLE: BAZEL_CC_TOOLCHAIN_CONFIG
# @INTERNAL
# @DESCRIPTION:
# Skylark implementation of the cc toolchain, using Bash-style variables
# to populate the build file.
BAZEL_CC_TOOLCHAIN_CONFIG='
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
load(
"@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
"feature",
"flag_group",
"flag_set",
"tool_path",
)
features = [
feature(name="supports_pic", enabled=True),
feature(
name="determinism",
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [
flag_group(
flags = [
# Make C++ compilation deterministic. Use linkstamping instead of these
# compiler symbols.
"-Wno-builtin-macro-redefined",
"-D__DATE__=\"redacted\"",
"-D__TIMESTAMP__=\"redacted\"",
"-D__TIME__=\"redacted\"",
]
)
]
),
]
),
feature(
name="hardening",
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [
flag_group(
flags = [
# Conservative choice; -D_FORTIFY_SOURCE=2 may be unsafe in some cases.
# We need to undef it before redefining it as some distributions now
# have it enabled by default.
"-U_FORTIFY_SOURCE",
"-D_FORTIFY_SOURCE=1",
"-fstack-protector",
]
)
]
),
flag_set(
actions = [
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
],
flag_groups = [flag_group(flags = ["-Wl,-z,relro,-z,now"])]
),
flag_set(
actions = [
ACTION_NAMES.cpp_link_executable,
],
flag_groups = [flag_group(flags = ["-pie", "-Wl,-z,relro,-z,now"])]
),
]
),
feature(
name="warnings",
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [
flag_group(
flags = [
# All warnings are enabled. Maybe enable -Werror as well?
"-Wall",
# Add another warning that is not part of -Wall.
"-Wunused-but-set-parameter",
# But disable some that are problematic.
"-Wno-free-nonheap-object" # has false positives
]
)
]
),
]
),
feature(
name="no-canonical-prefixes",
flag_sets = [
flag_set(
actions = [
ACTION_NAMES.c_compile,
ACTION_NAMES.cpp_compile,
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
ACTION_NAMES.cpp_link_executable,
],
flag_groups = [flag_group(flags = ["-no-canonical-prefixes"])]
),
]
),
feature(
name="linker-bin-path",
flag_sets = [
flag_set(
actions = [
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
ACTION_NAMES.cpp_link_executable,
],
flag_groups = [flag_group(flags = ["-B/usr/bin/"])]
),
]
),
feature(
name="disable-assertions",
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [flag_group(flags = ["-DNDEBUG"])]
),
]
),
feature(
name="common",
implies=[
"determinism",
"hardening",
"warnings",
"no-canonical-prefixes",
"linker-bin-path"
],
),
feature(
name="opt",
implies=["common", "disable-assertions"],
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [
flag_group(
flags = ["-g0", "-O2", "-ffunction-sections", "-fdata-sections"]
)
]
),
flag_set(
actions = [
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
ACTION_NAMES.cpp_link_executable,
],
flag_groups = [
flag_group(
flags = ["-Wl,--gc-sections"]
)
]
)
]
),
feature(
name="fastbuild",
implies=["common"],
),
feature(
name="dbg",
implies=["common"],
flag_sets = [
flag_set(
actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile],
flag_groups = [
flag_group(
flags = ["-g"]
)
]
)
]
),
]
def _impl(ctx):
tool_paths = [
tool_path(name = "gcc", path = "${env_cc}"),
tool_path(name = "ar", path = "${env_ar}"),
tool_path(name = "compat-ld", path = "${env_ld}"),
tool_path(name = "cpp", path = "${env_cpp}"),
tool_path(name = "dwp", path = "${env_dwp}"),
tool_path(name = "gcov", path = "${env_gcov}"),
tool_path(name = "ld", path = "${env_ld}"),
tool_path(name = "nm", path = "${env_nm}"),
tool_path(name = "objcopy", path = "${env_objcopy}"),
tool_path(name = "objdump", path = "${env_objdump}"),
tool_path(name = "strip", path = "${env_strip}"),
]
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
features = features,
cxx_builtin_include_directories = [
${builtin_include_dirs}
],
builtin_sysroot="${env_sysroot}",
toolchain_identifier = "portage-toolchain",
host_system_name = "local",
target_system_name = "local",
target_cpu = "${cpu_str}",
target_libc = "local",
compiler = "local",
abi_version = "local",
abi_libc_version = "local",
tool_paths = tool_paths,
)
cc_toolchain_config = rule(
implementation = _impl,
attrs = {},
provides = [CcToolchainConfigInfo],
)
'
# @FUNCTION: bazel_get_builtin_include_dirs
# @USAGE: <compiler binary>
# @RETURN:
# A list of the directories that are searched by default on invocation of the
# given compiler's preprocessor. These directories are normalized (e.g.
# parsing "..") and formatted as a python list of strings.
# @MAINTAINER:
# Michael Martis <martis@chromium.org>
# @INTERNAL
bazel_get_builtin_include_dirs() {
# Constants that demarcate default include dir information.
local match_head="#include <...> search starts here:"
local match_foot="End of search list."
local comp="${1}"
# Get preprocessor output (which contains searched include dirs).
local preproc_output
preproc_output="$("${comp}" -E -xc++ -Wp,-v - 2>&1 <<< "int main() { return 0; }" || die)"
# Keep only the include dirs (which are between two known markers).
local include_dirs
include_dirs="$(sed "1,/${match_head}/d;/${match_foot}/,\$d" <<< "${preproc_output}" || die)"
# For each include dir...
while read -r include_dir; do
# Normalize (e.g. process '..' sequences in) the path.
local norm_dir
# shellcheck disable=SC2015
norm_dir="$(cd "${include_dir}" && pwd || die)"
# Print the normalized path as a proto field.
echo "\"${norm_dir}\","
done <<< "${include_dirs}"
}
# @FUNCTION: bazel_populate_crosstool_target
# @USAGE: <sysroot> <prefix> <cpu string> <output directory>
# @MAINTAINER:
# Michael Martis <martis@chromium.org>
# @INTERNAL
# @DESCRIPTION:
# Accepts an environment sysroot, environment prefix (used to locate correct
# binaries for the environment) and environment CPU string (either '' or
# 'BUILD_'), and populates Bazel toolchain targets for the specified
# environment in the given output directory.
bazel_populate_crosstool_target() {
local env_sysroot="${1}"
local env_prefix="${2}"
local cpu_str="${3}"
local output_dir="${4}"
# Query compiler type (gcc / clang) from environment variables.
local comp_type
comp_type="$("tc-get-${env_prefix}compiler-type" || die)"
# Get actual compiler binary.
local comp
comp="$("tc-get${env_prefix}CC" || die)"
# Write out the BUILD file for this configuration.
cpu_str="${cpu_str}" \
envsubst <<< "${BAZEL_CC_BUILD}" > "${output_dir}/BUILD" || die
# Write out the toolchain_config file for this configuration.
#
# We call tc-getPROG directly for cpp, since we require a program that directly
# performs preprocessing (i.e. takes no flags), whereas tc-getCPP returns an
# invocation of the compiler for preprocessing (which uses flags).
cpu_str="${cpu_str}" \
builtin_include_dirs="$(bazel_get_builtin_include_dirs "${comp}" || die)" \
env_sysroot="${env_sysroot}" \
env_cc="$(command -v "${comp}" || die)" \
env_ar="$(command -v "$("tc-get${env_prefix}AR")" || die)" \
env_ld="$(command -v "$("tc-get${env_prefix}LD")" || die)" \
env_cpp="$(command -v "$("tc-get${env_prefix}PROG" CPP cpp)" || die)" \
env_dwp="$(command -v "$("tc-get${env_prefix}DWP")" || die)" \
env_gcov="$(command -v "$("tc-get${env_prefix}GCOV")" || die)" \
env_nm="$(command -v "$("tc-get${env_prefix}NM")" || die)" \
env_objcopy="$(command -v "$("tc-get${env_prefix}OBJCOPY")" || die)" \
env_objdump="$(command -v "$("tc-get${env_prefix}OBJDUMP")" || die)" \
env_strip="$(command -v "$("tc-get${env_prefix}STRIP")" || die)" \
envsubst <<< "${BAZEL_CC_TOOLCHAIN_CONFIG}" > \
"${output_dir}/cc_toolchain_config.bzl" || die
}
# @FUNCTION: bazel_get_stdlib_linkflag
# @USAGE: <compiler type>
# @RETURN: The correct stdlib linking flag for the given compiler type.
# @MAINTAINER:
# Michael Martis <martis@chromium.org>
# @INTERNAL
bazel_get_stdlib_linkflag() {
case "${1}" in
clang) echo "-lc++";;
gcc) echo "-lstdc++";;
*) die "Unsupported compiler type '${comp_type}'."
esac
}
# @FUNCTION: bazel_setup_crosstool
# @USAGE: <host cpu string> <target cpu string>
# @MAINTAINER:
# Michael Martis <martis@chromium.org>
# @DESCRIPTION:
# Accepts Bazel "host" and "target" CPU strings, and creates Bazel targets
# (under ${T}) that can be used to configure Bazel C++ compilation based on
# Portage environment variables.
#
# Also updates the bazelrc to specify the new crosstool targets by default.
#
# Should only be called once; subsequent calls will have no effect.
bazel_setup_crosstool() {
if [[ -f "${BAZEL_CC_BAZELRC}" ]]; then
return
fi
bazel_setup_bazelrc
local host_cpu_str="${1}"
if [[ -z "${host_cpu_str}" ]]; then
die "Must specify host CPU string when generating Bazel CROSSTOOL targets."
fi
local target_cpu_str="${2}"
if [[ -z "${target_cpu_str}" ]]; then
die "Must specify target CPU string when generating Bazel CROSSTOOL targets."
fi
# Populate host toolchain targets.
local host_crosstool_dir="${BAZEL_PORTAGE_PACKAGE_DIR}/${BAZEL_CC_CONFIG_DIR}/host"
mkdir -p "${host_crosstool_dir}" || die
bazel_populate_crosstool_target / BUILD_ "${host_cpu_str}" "${host_crosstool_dir}"
# Populate target toolchain targets.
local target_crosstool_dir="${BAZEL_PORTAGE_PACKAGE_DIR}/${BAZEL_CC_CONFIG_DIR}/target"
mkdir -p "${target_crosstool_dir}" || die
bazel_populate_crosstool_target "${PORTAGE_CONFIGROOT}" "" "${target_cpu_str}" "${target_crosstool_dir}"
# Create a bazelrc specifying the new toolchain targets by default.
cat > "${BAZEL_CC_BAZELRC}" <<-EOF || die
# Make Bazel respect Portage C/C++ configuration.
build --package_path="%workspace%:${BAZEL_PORTAGE_PACKAGE_DIR}"
build --host_crosstool_top="//${BAZEL_CC_CONFIG_DIR}/host:toolchain" --crosstool_top="//${BAZEL_CC_CONFIG_DIR}/target:toolchain"
build --host_cpu="${host_cpu_str}" --cpu="${target_cpu_str}" --compiler=local --host_compiler=local
build --host_platform="//${BAZEL_CC_CONFIG_DIR}/host:${host_cpu_str}_platform"
build --platforms="//${BAZEL_CC_CONFIG_DIR}/target:${target_cpu_str}_platform"
build --extra_toolchains="//${BAZEL_CC_CONFIG_DIR}/target:cc-toolchain-${target_cpu_str}"
# This is super helpful for figuring out how the toolchain is determined
# build --toolchain_resolution_debug
# Add correct standard library link flags.
build --linkopt="$(bazel_get_stdlib_linkflag "$(tc-get-compiler-type)" || die)"
build --host_linkopt="$(bazel_get_stdlib_linkflag "$(tc-get-BUILD_compiler-type)" || die)"
# Some compiler scripts require SYSROOT defined.
build --action_env SYSROOT="${PORTAGE_CONFIGROOT}"
# In case another config has disabled cross-compilation, re-enable it here.
build --distinct_host_configuration
EOF
echo "import ${BAZEL_CC_BAZELRC}" >> "${BAZEL_BAZELRC}" || die
}
_CROS_BAZEL_ECLASS=1
fi