""" Contains all logic for calling CMake for building external libraries/binaries """ load(":cc_toolchain_util.bzl", "absolutize_path_in_str") def create_cmake_script( workspace_name, generator, cmake_path, tools, flags, install_prefix, root, no_toolchain_file, user_cache, user_env, options, cmake_commands, include_dirs = [], is_debug_mode = True): """Constructs CMake script to be passed to cc_external_rule_impl. Args: workspace_name: current workspace name 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 []. is_debug_mode: If the compilation mode is `debug`. Defaults to True. Returns: string: A formatted string of the generated build command """ 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": ""}) 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(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("set -x") # Configure the CMake generate command script.append(" ".join([ 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("set +x") return params.commands + script def _escape_dquote_bash(text): """ Escape double quotes in flag lists for use in bash strings. """ # Objective is to escape the quotes twice for bash. # 1. \\\" -> \" - when set_env_vars' strings get evaluated. # 2. \" -> " - when each flag containing a string is passed to the compiler. # We use a starlark raw string to prevent the need to escape backslashes for starlark as well. return text.replace('"', r'\\\"') 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 _escape_dquote_cmake(text): """ Escape double quotes for use in bash heredoc and CMake crosstool. """ # Context: # cat >> file.cmake < 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"] = " %s " % \ " ".join(flags.cxx_linker_static) dict["CMAKE_CXX_ARCHIVE_CREATE"] = " %s " % \ " ".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, "", "", "", "", "-o ", "", ]) 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 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, )