mirror of
https://github.com/bazel-contrib/rules_foreign_cc
synced 2024-11-30 16:42:07 +00:00
ea7ed42949
Fixes cross-compiling from macos to linux. see #997 for background. I had to make a couple of extra changes to support this: Setting CMAKE_SYSTEM_NAME manually causes CMAKE_SYSTEM_PROCESSOR to not be set. This breaks some builds that expect this variable to be set like libjpeg-turbo. I made it so that the above variables are only set when cross-compilation is detected so that rules_foreign_cc only takes on responsibility of setting them when necessary.
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,
|
|
)
|