403 lines
16 KiB
Python
403 lines
16 KiB
Python
""" Contains all logic for calling CMake for building external libraries/binaries """
|
|
|
|
load(":cc_toolchain_util.bzl", "absolutize_path_in_str")
|
|
|
|
def _escape_dquote_bash(text):
|
|
""" Escape double quotes in flag lists for use in bash strings that set environment variables """
|
|
|
|
# We use a starlark raw string to prevent the need to escape backslashes for starlark as well.
|
|
return text.replace('"', r'\\\"')
|
|
|
|
def _escape_dquote_bash_crosstool(text):
|
|
""" Escape double quotes in flag lists for use in bash strings to be passed to a HEREDOC.
|
|
|
|
This requires an additional level of indirection as CMake requires the variables to be escaped inside the crosstool file
|
|
"""
|
|
|
|
# We use a starlark raw string to prevent the need to escape backslashes for starlark as well.
|
|
return text.replace('"', r'\\\\\\\"')
|
|
|
|
_TARGET_OS_PARAMS = {
|
|
"android": {
|
|
"ANDROID": "YES",
|
|
"CMAKE_SYSTEM_NAME": "Linux",
|
|
},
|
|
"linux": {
|
|
"CMAKE_SYSTEM_NAME": "Linux",
|
|
},
|
|
}
|
|
|
|
_TARGET_ARCH_PARAMS = {
|
|
"aarch64": {
|
|
"CMAKE_SYSTEM_PROCESSOR": "aarch64",
|
|
},
|
|
"x86_64": {
|
|
"CMAKE_SYSTEM_PROCESSOR": "x86_64",
|
|
},
|
|
}
|
|
|
|
def create_cmake_script(
|
|
workspace_name,
|
|
target_os,
|
|
target_arch,
|
|
host_os,
|
|
generator,
|
|
cmake_path,
|
|
tools,
|
|
flags,
|
|
install_prefix,
|
|
root,
|
|
no_toolchain_file,
|
|
user_cache,
|
|
user_env,
|
|
options,
|
|
cmake_commands,
|
|
include_dirs = [],
|
|
cmake_prefix = None,
|
|
is_debug_mode = True):
|
|
"""Constructs CMake script to be passed to cc_external_rule_impl.
|
|
|
|
Args:
|
|
workspace_name: current workspace name
|
|
target_os: The target OS for the build
|
|
target_arch: The target arch for the build
|
|
host_os: The execution OS for the build
|
|
generator: The generator target for cmake to use
|
|
cmake_path: The path to the cmake executable
|
|
tools: cc_toolchain tools (CxxToolsInfo)
|
|
flags: cc_toolchain flags (CxxFlagsInfo)
|
|
install_prefix: value ot pass to CMAKE_INSTALL_PREFIX
|
|
root: sources root relative to the $EXT_BUILD_ROOT
|
|
no_toolchain_file: if False, CMake toolchain file will be generated, otherwise not
|
|
user_cache: dictionary with user's values of cache initializers
|
|
user_env: dictionary with user's values for CMake environment variables
|
|
options: other CMake options specified by user
|
|
cmake_commands: A list of cmake commands for building and installing targets
|
|
include_dirs: Optional additional include directories. Defaults to [].
|
|
cmake_prefix: Optional prefix before the cmake command (without the trailing space).
|
|
is_debug_mode: If the compilation mode is `debug`. Defaults to True.
|
|
|
|
Returns:
|
|
list: Lines of bash which make up the build script
|
|
"""
|
|
|
|
merged_prefix_path = _merge_prefix_path(user_cache, include_dirs)
|
|
|
|
toolchain_dict = _fill_crossfile_from_toolchain(workspace_name, tools, flags)
|
|
params = None
|
|
|
|
keys_with_empty_values_in_user_cache = [key for key in user_cache if user_cache.get(key) == ""]
|
|
|
|
if no_toolchain_file:
|
|
params = _create_cache_entries_env_vars(toolchain_dict, user_cache, user_env)
|
|
else:
|
|
params = _create_crosstool_file_text(toolchain_dict, user_cache, user_env)
|
|
|
|
build_type = params.cache.get(
|
|
"CMAKE_BUILD_TYPE",
|
|
"Debug" if is_debug_mode else "Release",
|
|
)
|
|
params.cache.update({
|
|
"CMAKE_BUILD_TYPE": build_type,
|
|
"CMAKE_INSTALL_PREFIX": install_prefix,
|
|
"CMAKE_PREFIX_PATH": merged_prefix_path,
|
|
})
|
|
|
|
# Give user the ability to suppress some value, taken from Bazel's toolchain,
|
|
# or to suppress calculated CMAKE_BUILD_TYPE
|
|
# If the user passes "CMAKE_BUILD_TYPE": "" (empty string),
|
|
# CMAKE_BUILD_TYPE will not be passed to CMake
|
|
_wipe_empty_values(params.cache, keys_with_empty_values_in_user_cache)
|
|
|
|
# However, if no CMAKE_RANLIB was passed, pass the empty value for it explicitly,
|
|
# as it is legacy and autodetection of ranlib made by CMake automatically
|
|
# breaks some cross compilation builds,
|
|
# see https://github.com/envoyproxy/envoy/pull/6991
|
|
if not params.cache.get("CMAKE_RANLIB"):
|
|
params.cache.update({"CMAKE_RANLIB": ""})
|
|
|
|
# Avoid CMake passing the wrong linker flags when cross compiling
|
|
# by setting CMAKE_SYSTEM_NAME and CMAKE_SYSTEM_PROCESSOR,
|
|
# see https://github.com/bazelbuild/rules_foreign_cc/issues/289,
|
|
# and https://github.com/bazelbuild/rules_foreign_cc/pull/1062
|
|
if target_os != host_os and target_os != "unknown":
|
|
params.cache.update(_TARGET_OS_PARAMS.get(target_os, {}))
|
|
params.cache.update(_TARGET_ARCH_PARAMS.get(target_arch, {}))
|
|
|
|
set_env_vars = [
|
|
"export {}=\"{}\"".format(key, _escape_dquote_bash(params.env[key]))
|
|
for key in params.env
|
|
]
|
|
str_cmake_cache_entries = " ".join([
|
|
"-D{}=\"{}\"".format(key, _escape_dquote_bash_crosstool(params.cache[key]))
|
|
for key in params.cache
|
|
])
|
|
|
|
# Add definitions for all environment variables
|
|
script = set_env_vars
|
|
|
|
directory = "$$EXT_BUILD_ROOT$$/" + root
|
|
|
|
script.append("##enable_tracing##")
|
|
|
|
# Configure the CMake generate command
|
|
cmake_prefixes = [cmake_prefix] if cmake_prefix else []
|
|
script.append(" ".join(cmake_prefixes + [
|
|
cmake_path,
|
|
str_cmake_cache_entries,
|
|
" ".join(options),
|
|
# Generator is always set last and will override anything specified by the user
|
|
"-G '{}'".format(generator),
|
|
directory,
|
|
]))
|
|
|
|
script.extend(cmake_commands)
|
|
|
|
script.append("##disable_tracing##")
|
|
|
|
return params.commands + script
|
|
|
|
def _wipe_empty_values(cache, keys_with_empty_values_in_user_cache):
|
|
for key in keys_with_empty_values_in_user_cache:
|
|
if cache.get(key) != None:
|
|
cache.pop(key)
|
|
|
|
# From CMake documentation: ;-list of directories specifying installation prefixes to be searched...
|
|
def _merge_prefix_path(user_cache, include_dirs):
|
|
user_prefix = user_cache.get("CMAKE_PREFIX_PATH")
|
|
values = ["$$EXT_BUILD_DEPS$$"] + include_dirs
|
|
if user_prefix != None:
|
|
# remove it, it is gonna be merged specifically
|
|
user_cache.pop("CMAKE_PREFIX_PATH")
|
|
values.append(user_prefix.strip("\"'"))
|
|
return ";".join(values)
|
|
|
|
_CMAKE_ENV_VARS_FOR_CROSSTOOL = {
|
|
"ASMFLAGS": struct(value = "CMAKE_ASM_FLAGS_INIT", replace = False),
|
|
"CC": struct(value = "CMAKE_C_COMPILER", replace = True),
|
|
"CFLAGS": struct(value = "CMAKE_C_FLAGS_INIT", replace = False),
|
|
"CXX": struct(value = "CMAKE_CXX_COMPILER", replace = True),
|
|
"CXXFLAGS": struct(value = "CMAKE_CXX_FLAGS_INIT", replace = False),
|
|
}
|
|
|
|
_CMAKE_CACHE_ENTRIES_CROSSTOOL = {
|
|
"CMAKE_AR": struct(value = "CMAKE_AR", replace = True),
|
|
"CMAKE_ASM_FLAGS": struct(value = "CMAKE_ASM_FLAGS_INIT", replace = False),
|
|
"CMAKE_CXX_ARCHIVE_CREATE": struct(value = "CMAKE_CXX_ARCHIVE_CREATE", replace = False),
|
|
"CMAKE_CXX_FLAGS": struct(value = "CMAKE_CXX_FLAGS_INIT", replace = False),
|
|
"CMAKE_CXX_LINK_EXECUTABLE": struct(value = "CMAKE_CXX_LINK_EXECUTABLE", replace = True),
|
|
"CMAKE_C_ARCHIVE_CREATE": struct(value = "CMAKE_C_ARCHIVE_CREATE", replace = False),
|
|
"CMAKE_C_FLAGS": struct(value = "CMAKE_C_FLAGS_INIT", replace = False),
|
|
"CMAKE_EXE_LINKER_FLAGS": struct(value = "CMAKE_EXE_LINKER_FLAGS_INIT", replace = False),
|
|
"CMAKE_RANLIB": struct(value = "CMAKE_RANLIB", replace = True),
|
|
"CMAKE_SHARED_LINKER_FLAGS": struct(value = "CMAKE_SHARED_LINKER_FLAGS_INIT", replace = False),
|
|
"CMAKE_STATIC_LINKER_FLAGS": struct(value = "CMAKE_STATIC_LINKER_FLAGS_INIT", replace = False),
|
|
}
|
|
|
|
def _create_crosstool_file_text(toolchain_dict, user_cache, user_env):
|
|
cache_entries = _dict_copy(user_cache)
|
|
env_vars = _dict_copy(user_env)
|
|
_move_dict_values(toolchain_dict, env_vars, _CMAKE_ENV_VARS_FOR_CROSSTOOL)
|
|
_move_dict_values(toolchain_dict, cache_entries, _CMAKE_CACHE_ENTRIES_CROSSTOOL)
|
|
|
|
lines = []
|
|
crosstool_vars = []
|
|
|
|
# The __var_* bash variables that are set here are a method to avoid
|
|
# having to quote the values when they are expanded in the HEREDOC.
|
|
# We could disable shell expansion by single quoting EOF in the HEREDOC
|
|
# but then we loose the ability to expand other variables such as
|
|
# $EXT_BUILD_DEPS and so we use this trick to leave expansion turned on in
|
|
# the HEREDOC for the crosstool
|
|
for key in toolchain_dict:
|
|
crosstool_vars.append("__var_{}=\"{}\"".format(key, _escape_dquote_bash_crosstool(toolchain_dict[key])))
|
|
if ("CMAKE_AR" == key):
|
|
lines.append('set({} "$$__var_{}$$" {})'.format(
|
|
key,
|
|
key,
|
|
'CACHE FILEPATH "Archiver"',
|
|
))
|
|
else:
|
|
lines.append('set({} "$$__var_{}$$")'.format(key, key))
|
|
|
|
cache_entries.update({
|
|
"CMAKE_TOOLCHAIN_FILE": "$$BUILD_TMPDIR$$/crosstool_bazel.cmake",
|
|
})
|
|
return struct(
|
|
commands = sorted(crosstool_vars) + ["cat > crosstool_bazel.cmake << EOF"] + sorted(lines) + ["EOF", ""],
|
|
env = env_vars,
|
|
cache = cache_entries,
|
|
)
|
|
|
|
def _dict_copy(d):
|
|
out = {}
|
|
if d:
|
|
out.update(d)
|
|
return out
|
|
|
|
def _create_cache_entries_env_vars(toolchain_dict, user_cache, user_env):
|
|
_move_dict_values(toolchain_dict, user_env, _CMAKE_ENV_VARS_FOR_CROSSTOOL)
|
|
_move_dict_values(toolchain_dict, user_cache, _CMAKE_CACHE_ENTRIES_CROSSTOOL)
|
|
|
|
merged_env = _translate_from_toolchain_file(toolchain_dict, _CMAKE_ENV_VARS_FOR_CROSSTOOL)
|
|
merged_cache = _translate_from_toolchain_file(toolchain_dict, _CMAKE_CACHE_ENTRIES_CROSSTOOL)
|
|
|
|
# anything left in user's env_entries does not correspond to anything defined by toolchain
|
|
# => simple merge
|
|
merged_env.update(user_env)
|
|
merged_cache.update(user_cache)
|
|
|
|
return struct(
|
|
commands = [],
|
|
env = merged_env,
|
|
cache = merged_cache,
|
|
)
|
|
|
|
def _translate_from_toolchain_file(toolchain_dict, descriptor_map):
|
|
reverse = _reverse_descriptor_dict(descriptor_map)
|
|
cl_keyed_toolchain = dict()
|
|
|
|
keys = toolchain_dict.keys()
|
|
for key in keys:
|
|
env_var_key = reverse.get(key)
|
|
if env_var_key:
|
|
cl_keyed_toolchain[env_var_key.value] = toolchain_dict.pop(key)
|
|
return cl_keyed_toolchain
|
|
|
|
def _merge_toolchain_and_user_values(toolchain_dict, user_dict, descriptor_map):
|
|
_move_dict_values(toolchain_dict, user_dict, descriptor_map)
|
|
cl_keyed_toolchain = _translate_from_toolchain_file(toolchain_dict, descriptor_map)
|
|
|
|
# anything left in user's env_entries does not correspond to anything defined by toolchain
|
|
# => simple merge
|
|
cl_keyed_toolchain.update(user_dict)
|
|
return cl_keyed_toolchain
|
|
|
|
def _reverse_descriptor_dict(dict):
|
|
out_dict = {}
|
|
|
|
for key in dict:
|
|
value = dict[key]
|
|
out_dict[value.value] = struct(value = key, replace = value.replace)
|
|
|
|
return out_dict
|
|
|
|
def _move_dict_values(target, source, descriptor_map):
|
|
keys = source.keys()
|
|
for key in keys:
|
|
existing = descriptor_map.get(key)
|
|
if existing:
|
|
value = source.pop(key)
|
|
if existing.replace or target.get(existing.value) == None:
|
|
target[existing.value] = value
|
|
else:
|
|
target[existing.value] = target[existing.value] + " " + value
|
|
|
|
def _fill_crossfile_from_toolchain(workspace_name, tools, flags):
|
|
dict = {}
|
|
|
|
_sysroot = _find_in_cc_or_cxx(flags, "sysroot")
|
|
if _sysroot:
|
|
dict["CMAKE_SYSROOT"] = _absolutize(workspace_name, _sysroot)
|
|
|
|
_ext_toolchain_cc = _find_flag_value(flags.cc, "gcc_toolchain")
|
|
if _ext_toolchain_cc:
|
|
dict["CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN"] = _absolutize(workspace_name, _ext_toolchain_cc)
|
|
|
|
_ext_toolchain_cxx = _find_flag_value(flags.cxx, "gcc_toolchain")
|
|
if _ext_toolchain_cxx:
|
|
dict["CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN"] = _absolutize(workspace_name, _ext_toolchain_cxx)
|
|
|
|
# Force convert tools paths to absolute using $EXT_BUILD_ROOT
|
|
if tools.cc:
|
|
dict["CMAKE_C_COMPILER"] = _absolutize(workspace_name, tools.cc, True)
|
|
if tools.cxx:
|
|
dict["CMAKE_CXX_COMPILER"] = _absolutize(workspace_name, tools.cxx, True)
|
|
|
|
if tools.cxx_linker_static:
|
|
dict["CMAKE_AR"] = _absolutize(workspace_name, tools.cxx_linker_static, True)
|
|
if tools.cxx_linker_static.endswith("/libtool"):
|
|
dict["CMAKE_C_ARCHIVE_CREATE"] = "<CMAKE_AR> %s <OBJECTS>" % \
|
|
" ".join(flags.cxx_linker_static)
|
|
dict["CMAKE_CXX_ARCHIVE_CREATE"] = "<CMAKE_AR> %s <OBJECTS>" % \
|
|
" ".join(flags.cxx_linker_static)
|
|
|
|
if tools.cxx_linker_executable and tools.cxx_linker_executable != tools.cxx:
|
|
normalized_path = _absolutize(workspace_name, tools.cxx_linker_executable)
|
|
dict["CMAKE_CXX_LINK_EXECUTABLE"] = " ".join([
|
|
normalized_path,
|
|
"<FLAGS>",
|
|
"<CMAKE_CXX_LINK_FLAGS>",
|
|
"<LINK_FLAGS>",
|
|
"<OBJECTS>",
|
|
"-o <TARGET>",
|
|
"<LINK_LIBRARIES>",
|
|
])
|
|
|
|
if flags.cc:
|
|
dict["CMAKE_C_FLAGS_INIT"] = _join_flags_list(workspace_name, flags.cc)
|
|
if flags.cxx:
|
|
dict["CMAKE_CXX_FLAGS_INIT"] = _join_flags_list(workspace_name, flags.cxx)
|
|
if flags.assemble:
|
|
dict["CMAKE_ASM_FLAGS_INIT"] = _join_flags_list(workspace_name, flags.assemble)
|
|
|
|
# todo this options are needed, but cause a bug because the flags are put in wrong order => keep this line
|
|
# if flags.cxx_linker_static:
|
|
# lines += [_set_list(ctx, "CMAKE_STATIC_LINKER_FLAGS_INIT", flags.cxx_linker_static)]
|
|
if flags.cxx_linker_shared:
|
|
dict["CMAKE_SHARED_LINKER_FLAGS_INIT"] = _join_flags_list(workspace_name, flags.cxx_linker_shared)
|
|
if flags.cxx_linker_executable:
|
|
dict["CMAKE_EXE_LINKER_FLAGS_INIT"] = _join_flags_list(workspace_name, flags.cxx_linker_executable)
|
|
|
|
return dict
|
|
|
|
def _find_in_cc_or_cxx(flags, flag_name_no_dashes):
|
|
_value = _find_flag_value(flags.cxx, flag_name_no_dashes)
|
|
if _value:
|
|
return _value
|
|
return _find_flag_value(flags.cc, flag_name_no_dashes)
|
|
|
|
def _find_flag_value(list, flag_name_no_dashes):
|
|
one_dash = "-" + flag_name_no_dashes.lstrip(" ")
|
|
two_dash = "--" + flag_name_no_dashes.lstrip(" ")
|
|
|
|
check_for_value = False
|
|
for value in list:
|
|
value = value.lstrip(" ")
|
|
if check_for_value:
|
|
return value.lstrip(" =")
|
|
_tail = _tail_if_starts_with(value, one_dash)
|
|
_tail = _tail_if_starts_with(value, two_dash) if _tail == None else _tail
|
|
if _tail != None and len(_tail) > 0:
|
|
return _tail.lstrip(" =")
|
|
if _tail != None:
|
|
check_for_value = True
|
|
return None
|
|
|
|
def _tail_if_starts_with(str, start):
|
|
if (str.startswith(start)):
|
|
return str[len(start):]
|
|
return None
|
|
|
|
def _absolutize(workspace_name, text, force = False):
|
|
if text.strip(" ").startswith("C:") or text.strip(" ").startswith("c:"):
|
|
return text
|
|
|
|
# Use bash parameter substitution to replace backslashes with forward slashes as CMake fails if provided paths containing backslashes
|
|
return absolutize_path_in_str(workspace_name, "$${EXT_BUILD_ROOT//\\\\//}$$/", text, force)
|
|
|
|
def _join_flags_list(workspace_name, flags):
|
|
return " ".join([_absolutize(workspace_name, flag) for flag in flags])
|
|
|
|
export_for_test = struct(
|
|
absolutize = _absolutize,
|
|
tail_if_starts_with = _tail_if_starts_with,
|
|
find_flag_value = _find_flag_value,
|
|
fill_crossfile_from_toolchain = _fill_crossfile_from_toolchain,
|
|
move_dict_values = _move_dict_values,
|
|
reverse_descriptor_dict = _reverse_descriptor_dict,
|
|
merge_toolchain_and_user_values = _merge_toolchain_and_user_values,
|
|
CMAKE_ENV_VARS_FOR_CROSSTOOL = _CMAKE_ENV_VARS_FOR_CROSSTOOL,
|
|
CMAKE_CACHE_ENTRIES_CROSSTOOL = _CMAKE_CACHE_ENTRIES_CROSSTOOL,
|
|
)
|