diff --git a/README.md b/README.md index d20b60f6..2928dd3a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Rules for building C/C++ projects using foreign build systems inside Bazel proje * This is not an officially supported Google product (meaning, support and/or new releases may be limited.) -**NOTE**: this requires Bazel version starting from 0.17.1. +**NOTE**: this requires Bazel version starting from 0.20.0 It also requires passing Bazel the following flag: (**please pay attention @foreign_cc_impl was added** due to adoption to Starlark API changes that are goning to happen in Bazel 0.21) @@ -19,6 +19,8 @@ Where ```rules_foreign_cc``` is the name of this repository in your WORKSPACE fi ## News **January 2019:** +- Should be used with Bazel 0.20.0 or newer. + - Examples package became the separate workspace. This also allows to illustrate how to initialize rules_foreign_cc. diff --git a/for_workspace/starlark_api_change_support.bzl b/for_workspace/starlark_api_change_support.bzl index 345ab71e..72e4dd69 100644 --- a/for_workspace/starlark_api_change_support.bzl +++ b/for_workspace/starlark_api_change_support.bzl @@ -27,13 +27,11 @@ generate_implementation_fragments = repository_rule( default = [ "@rules_foreign_cc//tools/build_defs/new_11_2018/cc_toolchain_util.bzl", "@rules_foreign_cc//tools/build_defs/new_11_2018/framework.bzl", - "@rules_foreign_cc//tools/build_defs/old_11_2018/cc_toolchain_util.bzl", "@rules_foreign_cc//tools/build_defs/old_11_2018/framework.bzl", - - "@rules_foreign_cc//tools/build_defs/old_10_2018/cc_toolchain_util.bzl", - "@rules_foreign_cc//tools/build_defs/old_10_2018/framework.bzl", - ] + "@rules_foreign_cc//tools/build_defs/old_12_2018/cc_toolchain_util.bzl", + "@rules_foreign_cc//tools/build_defs/old_12_2018/framework.bzl", + ], ), - } + }, ) diff --git a/tools/build_defs/old_10_2018/BUILD b/tools/build_defs/old_10_2018/BUILD deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/build_defs/old_10_2018/cc_toolchain_util.bzl b/tools/build_defs/old_10_2018/cc_toolchain_util.bzl deleted file mode 100644 index 55281c4d..00000000 --- a/tools/build_defs/old_10_2018/cc_toolchain_util.bzl +++ /dev/null @@ -1,425 +0,0 @@ -""" Defines create_linking_info, which wraps passed libraries into CcLinkingInfo -""" - -load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") -load( - "@bazel_tools//tools/build_defs/cc:action_names.bzl", - "ASSEMBLE_ACTION_NAME", - "CPP_COMPILE_ACTION_NAME", - "CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME", - "CPP_LINK_EXECUTABLE_ACTION_NAME", - "CPP_LINK_STATIC_LIBRARY_ACTION_NAME", - "C_COMPILE_ACTION_NAME", -) - -LibrariesToLinkInfo = provider( - doc = "Libraries to be wrapped into CcLinkingInfo", - fields = dict( - static_libraries = "Static library files, optional", - shared_libraries = "Shared library files, optional", - interface_libraries = "Interface library files, optional", - ), -) - -CxxToolsInfo = provider( - doc = "Paths to the C/C++ tools, taken from the toolchain", - fields = dict( - cc = "C compiler", - cxx = "C++ compiler", - cxx_linker_static = "C++ linker to link static library", - cxx_linker_executable = "C++ linker to link executable", - ), -) - -CxxFlagsInfo = provider( - doc = "Flags for the C/C++ tools, taken from the toolchain", - fields = dict( - cc = "C compiler flags", - cxx = "C++ compiler flags", - cxx_linker_shared = "C++ linker flags when linking shared library", - cxx_linker_static = "C++ linker flags when linking static library", - cxx_linker_executable = "C++ linker flags when linking executable", - assemble = "Assemble flags", - ), -) - -def _to_list(element): - if element == None: - return [] - else: - return [element] - -def _to_depset(element): - if element == None: - return depset() - return depset(element) - -def _perform_error_checks( - system_provided, - shared_library_artifacts, - interface_library_artifacts, - targets_windows): - # If the shared library will be provided by system during runtime, users are not supposed to - # specify shared_library. - if system_provided and shared_library_artifacts: - fail("'shared_library' shouldn't be specified when 'system_provided' is true") - - # If a shared library won't be provided by system during runtime and we are linking the shared - # library through interface library, the shared library must be specified. - if (not system_provided and not shared_library_artifacts and - interface_library_artifacts): - fail("'shared_library' should be specified when 'system_provided' is false") - - if targets_windows and shared_library_artifacts: - fail("'interface library' must be specified when using cc_import for " + - "shared library on Windows") - -def _build_static_library_to_link(ctx, library): - if library == None: - fail("Parameter 'static_library_artifact' cannot be None") - - static_library_category = None - if ctx.attr.alwayslink: - static_library_category = "alwayslink_static_library" - else: - static_library_category = "static_library" - - return cc_common.create_library_to_link( - ctx = ctx, - library = library, - artifact_category = static_library_category, - ) - -def _build_shared_library_to_link(ctx, library, cc_toolchain, targets_windows): - if library == None: - fail("Parameter 'shared_library_artifact' cannot be None") - - if not targets_windows and hasattr(cc_common, "create_symlink_library_to_link"): - return cc_common.create_symlink_library_to_link( - ctx = ctx, - cc_toolchain = cc_toolchain, - library = library, - ) - - return cc_common.create_library_to_link( - ctx = ctx, - library = library, - artifact_category = "dynamic_library", - ) - -def _build_interface_library_to_link(ctx, library, cc_toolchain, targets_windows): - if library == None: - fail("Parameter 'interface_library_artifact' cannot be None") - - if not targets_windows and hasattr(cc_common, "create_symlink_library_to_link"): - return cc_common.create_symlink_library_to_link( - ctx = ctx, - cc_toolchain = cc_toolchain, - library = library, - ) - return cc_common.create_library_to_link( - ctx = ctx, - library = library, - artifact_category = "interface_library", - ) - -# we could possibly take a decision about linking interface/shared library beased on each library name -# (usefull for the case when multiple output targets are provided) -def _build_libraries_to_link_and_runtime_artifact(ctx, files, cc_toolchain, targets_windows): - static_libraries = [_build_static_library_to_link(ctx, lib) for lib in (files.static_libraries or [])] - - shared_libraries = [] - runtime_artifacts = [] - if files.shared_libraries != None: - for lib in files.shared_libraries: - shared_library = _build_shared_library_to_link(ctx, lib, cc_toolchain, targets_windows) - shared_libraries += [shared_library] - runtime_artifacts += [shared_library.artifact()] - - interface_libraries = [] - if files.interface_libraries != None: - for lib in files.interface_libraries: - interface_libraries += [_build_interface_library_to_link(ctx, lib, cc_toolchain, targets_windows)] - - dynamic_libraries_for_linking = None - if len(interface_libraries) > 0: - dynamic_libraries_for_linking = interface_libraries - else: - dynamic_libraries_for_linking = shared_libraries - - return { - "static_libraries": static_libraries, - "dynamic_libraries": dynamic_libraries_for_linking, - "runtime_artifacts": runtime_artifacts, - } - -def _build_cc_link_params( - ctx, - user_link_flags, - static_libraries, - dynamic_libraries, - runtime_artifacts): - static_shared = None - static_no_shared = None - if static_libraries != None and len(static_libraries) > 0: - static_shared = cc_common.create_cc_link_params( - ctx = ctx, - user_link_flags = user_link_flags, - libraries_to_link = _to_depset(static_libraries), - ) - static_no_shared = cc_common.create_cc_link_params( - ctx = ctx, - libraries_to_link = _to_depset(static_libraries), - ) - else: - static_shared = cc_common.create_cc_link_params( - ctx = ctx, - user_link_flags = user_link_flags, - libraries_to_link = _to_depset(dynamic_libraries), - dynamic_libraries_for_runtime = _to_depset(runtime_artifacts), - ) - static_no_shared = cc_common.create_cc_link_params( - ctx = ctx, - libraries_to_link = _to_depset(dynamic_libraries), - dynamic_libraries_for_runtime = _to_depset(runtime_artifacts), - ) - - no_static_shared = None - no_static_no_shared = None - if dynamic_libraries != None and len(dynamic_libraries) > 0: - no_static_shared = cc_common.create_cc_link_params( - ctx = ctx, - user_link_flags = user_link_flags, - libraries_to_link = _to_depset(dynamic_libraries), - dynamic_libraries_for_runtime = _to_depset(runtime_artifacts), - ) - no_static_no_shared = cc_common.create_cc_link_params( - ctx = ctx, - libraries_to_link = _to_depset(dynamic_libraries), - dynamic_libraries_for_runtime = _to_depset(runtime_artifacts), - ) - else: - no_static_shared = cc_common.create_cc_link_params( - ctx = ctx, - user_link_flags = user_link_flags, - libraries_to_link = _to_depset(static_libraries), - ) - no_static_no_shared = cc_common.create_cc_link_params( - ctx = ctx, - libraries_to_link = _to_depset(static_libraries), - ) - - return { - "static_mode_params_for_dynamic_library": static_shared, - "static_mode_params_for_executable": static_no_shared, - "dynamic_mode_params_for_dynamic_library": no_static_shared, - "dynamic_mode_params_for_executable": no_static_no_shared, - } - -def targets_windows(ctx, cc_toolchain): - """ Returns true if build is targeting Windows - Args: - ctx - rule context - cc_toolchain - optional - Cc toolchain - """ - toolchain = cc_toolchain if cc_toolchain else find_cpp_toolchain(ctx) - feature_configuration = cc_common.configure_features( - cc_toolchain = toolchain, - requested_features = ctx.features, - unsupported_features = ctx.disabled_features, - ) - - return cc_common.is_enabled( - feature_configuration = feature_configuration, - feature_name = "targets_windows", - ) - -def create_linking_info(ctx, user_link_flags, files): - """ Creates CcLinkingInfo for the passed user link options and libraries. - Args: - ctx - rule context - user_link_flags - (list of strings) link optins, provided by user - files - (LibrariesToLink) provider with the library files - """ - cc_toolchain = find_cpp_toolchain(ctx) - for_windows = targets_windows(ctx, cc_toolchain) - - _perform_error_checks( - False, - files.shared_libraries, - files.interface_libraries, - for_windows, - ) - - artifacts = _build_libraries_to_link_and_runtime_artifact( - ctx, - files, - cc_toolchain, - for_windows, - ) - - link_params = _build_cc_link_params(ctx, user_link_flags, **artifacts) - - return CcLinkingInfo(**link_params) - -def get_env_vars(ctx): - cc_toolchain = find_cpp_toolchain(ctx) - feature_configuration = cc_common.configure_features( - cc_toolchain = cc_toolchain, - requested_features = ctx.features, - unsupported_features = ctx.disabled_features, - ) - copts = ctx.attr.copts if hasattr(ctx.attr, "copts") else depset() - - vars = dict() - - for action_name in [C_COMPILE_ACTION_NAME, CPP_LINK_STATIC_LIBRARY_ACTION_NAME, CPP_LINK_EXECUTABLE_ACTION_NAME]: - vars.update(cc_common.get_environment_variables( - feature_configuration = feature_configuration, - action_name = action_name, - variables = cc_common.create_compile_variables( - feature_configuration = feature_configuration, - cc_toolchain = cc_toolchain, - user_compile_flags = copts, - ), - )) - return vars - -def is_debug_mode(ctx): - # see workspace_definitions.bzl - return str(True) == ctx.attr._is_debug[config_common.FeatureFlagInfo].value - -def get_tools_info(ctx): - """ Takes information about tools paths from cc_toolchain, returns CxxToolsInfo - Args: - ctx - rule context - """ - cc_toolchain = find_cpp_toolchain(ctx) - feature_configuration = cc_common.configure_features( - cc_toolchain = cc_toolchain, - requested_features = ctx.features, - unsupported_features = ctx.disabled_features, - ) - - return CxxToolsInfo( - cc = cc_common.get_tool_for_action( - feature_configuration = feature_configuration, - action_name = C_COMPILE_ACTION_NAME, - ), - cxx = cc_common.get_tool_for_action( - feature_configuration = feature_configuration, - action_name = CPP_COMPILE_ACTION_NAME, - ), - cxx_linker_static = cc_common.get_tool_for_action( - feature_configuration = feature_configuration, - action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, - ), - cxx_linker_executable = cc_common.get_tool_for_action( - feature_configuration = feature_configuration, - action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, - ), - ) - -def get_flags_info(ctx): - """ Takes information about flags from cc_toolchain, returns CxxFlagsInfo - Args: - ctx - rule context - """ - cc_toolchain = find_cpp_toolchain(ctx) - feature_configuration = cc_common.configure_features( - cc_toolchain = cc_toolchain, - requested_features = ctx.features, - unsupported_features = ctx.disabled_features, - ) - copts = ctx.attr.copts if hasattr(ctx.attr, "copts") else depset() - - return CxxFlagsInfo( - cc = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = C_COMPILE_ACTION_NAME, - variables = cc_common.create_compile_variables( - feature_configuration = feature_configuration, - cc_toolchain = cc_toolchain, - user_compile_flags = copts, - ), - ), - cxx = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = CPP_COMPILE_ACTION_NAME, - variables = cc_common.create_compile_variables( - feature_configuration = feature_configuration, - cc_toolchain = cc_toolchain, - user_compile_flags = copts, - add_legacy_cxx_options = True, - ), - ), - cxx_linker_shared = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME, - variables = cc_common.create_link_variables( - cc_toolchain = cc_toolchain, - feature_configuration = feature_configuration, - is_using_linker = True, - is_linking_dynamic_library = True, - ), - ), - cxx_linker_static = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, - variables = cc_common.create_link_variables( - cc_toolchain = cc_toolchain, - feature_configuration = feature_configuration, - is_using_linker = False, - is_linking_dynamic_library = False, - ), - ), - cxx_linker_executable = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, - variables = cc_common.create_link_variables( - cc_toolchain = cc_toolchain, - feature_configuration = feature_configuration, - is_using_linker = True, - is_linking_dynamic_library = False, - ), - ), - assemble = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = ASSEMBLE_ACTION_NAME, - variables = cc_common.create_compile_variables( - feature_configuration = feature_configuration, - cc_toolchain = cc_toolchain, - user_compile_flags = copts, - ), - ), - ) - -def absolutize_path_in_str(workspace_name, root_str, text, force = False): - """ Replaces relative paths in [the middle of] 'text', prepending them with 'root_str'. - If there is nothing to replace, returns the 'text'. - - We only will replace relative paths starting with either 'external/' or '/', - because we only want to point with absolute paths to external repositories or inside our - current workspace. (And also to limit the possibility of error with such not exact replacing.) - - Args: - workspace_name - workspace name - text - the text to do replacement in - root_str - the text to prepend to the found relative path - """ - new_text = _prefix(text, "external/", root_str) - if new_text == text: - new_text = _prefix(text, workspace_name + "/", root_str) - # absolutize relative by adding our working directory - # this works because we ru on windows under msys now - if force and new_text == text and not text.startswith("/"): - new_text = root_str + "/" + text - - return new_text - -def _prefix(text, from_str, prefix): - text = text.replace('"', '\\"') - (before, middle, after) = text.partition(from_str) - if not middle or before.endswith("/"): - return text - return before + prefix + middle + after diff --git a/tools/build_defs/old_10_2018/framework.bzl b/tools/build_defs/old_10_2018/framework.bzl deleted file mode 100644 index f7633bee..00000000 --- a/tools/build_defs/old_10_2018/framework.bzl +++ /dev/null @@ -1,601 +0,0 @@ -""" Contains definitions for creation of external C/C++ build rules (for building external libraries - with CMake, configure/make, autotools) -""" - -load("@bazel_skylib//lib:collections.bzl", "collections") -load("@rules_foreign_cc//tools/build_defs:version.bzl", "VERSION") -load( - ":cc_toolchain_util.bzl", - "LibrariesToLinkInfo", - "create_linking_info", - "get_env_vars", - "targets_windows", -) -load("@rules_foreign_cc//tools/build_defs:detect_root.bzl", "detect_root") -load("@foreign_cc_platform_utils//:os_info.bzl", "OSInfo") -load("@rules_foreign_cc//tools/build_defs:run_shell_file_utils.bzl", "copy_directory", "fictive_file_in_genroot") - -""" Dict with definitions of the context attributes, that customize cc_external_rule_impl function. - Many of the attributes have default values. - - Typically, the concrete external library rule will use this structure to create the attributes - description dict. See cmake.bzl as an example. -""" -CC_EXTERNAL_RULE_ATTRIBUTES = { - # Library name. Defines the name of the install directory and the name of the static library, - # if no output files parameters are defined (any of static_libraries, shared_libraries, - # interface_libraries, binaries_names) - # Optional. If not defined, defaults to the target's name. - "lib_name": attr.string(mandatory = False), - # Label with source code to build. Typically a filegroup for the source of remote repository. - # Mandatory. - "lib_source": attr.label(mandatory = True, allow_files = True), - # Optional compilation definitions to be passed to the dependencies of this library. - # They are NOT passed to the compiler, you should duplicate them in the configuration options. - "defines": attr.string_list(mandatory = False, default = []), - # - # Optional additional inputs to be declared as needed for the shell script action. - # Not used by the shell script part in cc_external_rule_impl. - "additional_inputs": attr.label_list(mandatory = False, allow_files = True, default = []), - # Optional additional tools needed for the building. - # Not used by the shell script part in cc_external_rule_impl. - "additional_tools": attr.label_list(mandatory = False, allow_files = True, default = []), - # - # Optional part of the shell script to be added after the make commands - "postfix_script": attr.string(mandatory = False), - # Optinal make commands, defaults to ["make", "make install"] - "make_commands": attr.string_list(mandatory = False, default = ["make", "make install"]), - # - # Optional dependencies to be copied into the directory structure. - # Typically those directly required for the external building of the library/binaries. - # (i.e. those that the external buidl system will be looking for and paths to which are - # provided by the calling rule) - "deps": attr.label_list(mandatory = False, allow_files = True, default = []), - # Optional tools to be copied into the directory structure. - # Similar to deps, those directly required for the external building of the library/binaries. - "tools_deps": attr.label_list(mandatory = False, allow_files = True, default = []), - # - # Optional name of the output subdirectory with the header files, defaults to 'include'. - "out_include_dir": attr.string(mandatory = False, default = "include"), - # Optional name of the output subdirectory with the library files, defaults to 'lib'. - "out_lib_dir": attr.string(mandatory = False, default = "lib"), - # Optional name of the output subdirectory with the binary files, defaults to 'bin'. - "out_bin_dir": attr.string(mandatory = False, default = "bin"), - # - # Optional. if true, link all the object files from the static library, - # even if they are not used. - "alwayslink": attr.bool(mandatory = False, default = False), - # Optional link options to be passed up to the dependencies of this library - "linkopts": attr.string_list(mandatory = False, default = []), - # - # Output files names parameters. If any of them is defined, only these files are passed to - # Bazel providers. - # if no of them is defined, default lib_name.a/lib_name.lib static library is assumed. - # - # Optional names of the resulting static libraries. - "static_libraries": attr.string_list(mandatory = False), - # Optional names of the resulting shared libraries. - "shared_libraries": attr.string_list(mandatory = False), - # Optional names of the resulting interface libraries. - "interface_libraries": attr.string_list(mandatory = False), - # Optional names of the resulting binaries. - "binaries": attr.string_list(mandatory = False), - # Flag variable to indicate that the library produces only headers - "headers_only": attr.bool(mandatory = False, default = False), - # - # link to the shell utilities used by the shell script in cc_external_rule_impl. - "_utils": attr.label( - default = "@foreign_cc_platform_utils//:shell_utils", - allow_single_file = True, - ), - "_is_debug": attr.label( - default = "@foreign_cc_platform_utils//:compilation_mode", - ), - # link to the shell utilities used by the shell script in cc_external_rule_impl. - "_target_os": attr.label( - default = "@foreign_cc_platform_utils//:target_os", - ), - # we need to declare this attribute to access cc_toolchain - "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), -} - -def create_attrs(attr_struct, configure_name, create_configure_script, **kwargs): - """ Function for adding/modifying context attributes struct (originally from ctx.attr), - provided by user, to be passed to the cc_external_rule_impl function as a struct. - - Copies a struct 'attr_struct' values (with attributes from CC_EXTERNAL_RULE_ATTRIBUTES) - to the resulting struct, adding or replacing attributes passed in 'configure_name', - 'configure_script', and '**kwargs' parameters. - """ - attrs = {} - for key in CC_EXTERNAL_RULE_ATTRIBUTES: - if not key.startswith("_") and hasattr(attr_struct, key): - attrs[key] = getattr(attr_struct, key) - - attrs["configure_name"] = configure_name - attrs["create_configure_script"] = create_configure_script - - for arg in kwargs: - attrs[arg] = kwargs[arg] - return struct(**attrs) - -ForeignCcDeps = provider( - doc = """Provider to pass transitive information about external libraries.""", - fields = {"artifacts": "Depset of ForeignCcArtifact"}, -) - -ForeignCcArtifact = provider( - doc = """Groups information about the external library install directory, -and relative bin, include and lib directories. - -Serves to pass transitive information about externally built artifacts up the dependency chain. - -Can not be used as a top-level provider. -Instances of ForeignCcArtifact are incapsulated in a depset ForeignCcDeps#artifacts.""", - fields = { - "gen_dir": "Install directory", - "bin_dir_name": "Bin directory, relative to install directory", - "lib_dir_name": "Lib directory, relative to install directory", - "include_dir_name": "Include directory, relative to install directory", - }, -) - -ConfigureParameters = provider( - doc = """Parameters of create_configure_script callback function, called by -cc_external_rule_impl function. create_configure_script creates the configuration part -of the script, and allows to reuse the inputs structure, created by the framework.""", - fields = dict( - ctx = "Rule context", - attrs = """Attributes struct, created by create_attrs function above""", - inputs = """InputFiles provider: summarized information on rule inputs, created by framework -function, to be reused in script creator. Contains in particular merged compilation and linking -dependencies.""", - ), -) - -def cc_external_rule_impl(ctx, attrs): - """ Framework function for performing external C/C++ building. - - To be used to build external libraries or/and binaries with CMake, configure/make, autotools etc., - and use results in Bazel. - It is possible to use it to build a group of external libraries, that depend on each other or on - Bazel library, and pass nessesary tools. - - Accepts the actual commands for build configuration/execution in attrs. - - Creates and runs a shell script, which: - - 1) prepares directory structure with sources, dependencies, and tools symlinked into subdirectories - of the execroot directory. Adds tools into PATH. - 2) defines the correct absolute paths in tools with the script paths, see 7 - 3) defines the following environment variables: - EXT_BUILD_ROOT: execroot directory - EXT_BUILD_DEPS: subdirectory of execroot, which contains the following subdirectories: - - For cmake_external built dependencies: - symlinked install directories of the dependencies - - for Bazel built/imported dependencies: - - include - here the include directories are symlinked - lib - here the library files are symlinked - lib/pkgconfig - here the pkgconfig files are symlinked - bin - here the tools are copied - INSTALLDIR: subdirectory of the execroot (named by the lib_name), where the library/binary - will be installed - - These variables should be used by the calling rule to refer to the created directory structure. - 4) calls 'attrs.create_configure_script' - 5) calls 'attrs.make_commands' - 6) calls 'attrs.postfix_script' - 7) replaces absolute paths in possibly created scripts with a placeholder value - - Please see cmake.bzl for example usage. - - Args: - ctx: calling rule context - attrs: attributes struct, created by create_attrs function above. - Contains fields from CC_EXTERNAL_RULE_ATTRIBUTES (see descriptions there), - two mandatory fields: - - configure_name: name of the configuration tool, to be used in action mnemonic, - - create_configure_script(ConfigureParameters): function that creates configuration - script, accepts ConfigureParameters - and some other fields provided by the rule, which have been passed to create_attrs. - """ - lib_name = attrs.lib_name or ctx.attr.name - - inputs = _define_inputs(attrs) - outputs = _define_outputs(ctx, attrs, lib_name) - out_cc_info = _define_out_cc_info(ctx, attrs, inputs, outputs) - - shell_utils = ctx.attr._utils.files.to_list()[0].path - - env = _correct_path_variable(get_env_vars(ctx)) - set_envs = "" - if not ctx.attr._target_os[OSInfo].is_osx: - set_envs = "\n".join(["export {}=\"{}\"".format(key, env[key]) for key in env]) - - version_and_lib = "Bazel external C/C++ Rules #{}. Building library '{}'".format(VERSION, lib_name) - - # We can not declare outputs of the action, which are in parent-child relashion, - # so we need to have a (symlinked) copy of the output directory to provide - # both the C/C++ artifacts - libraries, headers, and binaries, - # and the install directory as a whole (which is mostly nessesary for chained external builds). - # - # We want the install directory output of this rule to have the same name as the library, - # so symlink it under the same name but in a subdirectory - installdir_copy = copy_directory(ctx.actions, "$INSTALLDIR", "copy_{}/{}".format(lib_name, lib_name)) - - # 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) - - define_variables = [ - set_envs, - "set_platform_env_vars", - "export EXT_BUILD_ROOT=$BUILD_PWD", - "export BUILD_TMPDIR=$(mktemp -d)", - "export EXT_BUILD_DEPS=$EXT_BUILD_ROOT/bazel_foreign_cc_deps_" + lib_name, - "export INSTALLDIR=$EXT_BUILD_ROOT/" + empty.file.dirname + "/" + lib_name, - ] - - trap_function = [ - "function cleanup() {", - "local ecode=$?", - "if [ $ecode -eq 0 ]; then", - "echo \"rules_foreign_cc: Cleaning temp directories\"", - "rm -rf $BUILD_TMPDIR $EXT_BUILD_ROOT/bazel_foreign_cc_deps_" + lib_name, - "else", - "echo \"\"", - "echo \"rules_foreign_cc: Keeping temp build directory $BUILD_TMPDIR\ - and dependencies directory $EXT_BUILD_ROOT/bazel_foreign_cc_deps_" + lib_name + " for debug.\"", - "echo \"rules_foreign_cc: Please note that the directories inside a sandbox are still\ - cleaned unless you specify '--sandbox_debug' Bazel command line flag.\"", - "echo \"\"", - "fi", - "}", - ] - - script_lines = [ - "echo \"\n{}\n\"".format(version_and_lib), - "set -e", - "source " + shell_utils, - "\n".join(define_variables), - "path $EXT_BUILD_ROOT", - "\n".join(trap_function), - "mkdir -p $EXT_BUILD_DEPS", - "mkdir -p $INSTALLDIR", - _print_env(), - "trap \"cleanup\" EXIT", - "\n".join(_copy_deps_and_tools(inputs)), - # replace placeholder with the dependencies root - "define_absolute_paths $EXT_BUILD_DEPS $EXT_BUILD_DEPS", - "pushd $BUILD_TMPDIR", - attrs.create_configure_script(ConfigureParameters(ctx = ctx, attrs = attrs, inputs = inputs)), - "\n".join(attrs.make_commands), - attrs.postfix_script or "", - # replace references to the root directory when building ($BUILD_TMPDIR) - # and the root where the dependencies were installed ($EXT_BUILD_DEPS) - # for the results which are in $INSTALLDIR (with placeholder) - "replace_absolute_paths $INSTALLDIR $BUILD_TMPDIR", - "replace_absolute_paths $INSTALLDIR $EXT_BUILD_DEPS", - installdir_copy.script, - empty.script, - "popd", - ] - - script_text = "\n".join(script_lines) - print("script text: " + script_text) - - execution_requirements = {"block-network": ""} - - rule_outputs = outputs.declared_outputs + [installdir_copy.file] - - ctx.actions.run_shell( - mnemonic = "Cc" + attrs.configure_name.capitalize() + "MakeRule", - inputs = depset(inputs.declared_inputs) + ctx.attr._cc_toolchain.files, - outputs = rule_outputs + [empty.file], - tools = ctx.attr._utils.files, - # We should take the default PATH passed by Bazel, not that from cc_toolchain - # for Windows, because the PATH under msys2 is different and that is which we need - # for shell commands - use_default_shell_env = not ctx.attr._target_os[OSInfo].is_osx, - command = script_text, - execution_requirements = execution_requirements, - # this is ignored if use_default_shell_env = True - env = env, - ) - - externally_built = ForeignCcArtifact( - gen_dir = installdir_copy.file, - bin_dir_name = attrs.out_bin_dir, - lib_dir_name = attrs.out_lib_dir, - include_dir_name = attrs.out_include_dir, - ) - return [ - DefaultInfo(files = depset(direct = rule_outputs)), - OutputGroupInfo(**_declare_output_groups(installdir_copy.file, outputs.out_binary_files)), - ForeignCcDeps(artifacts = depset( - [externally_built], - transitive = _get_transitive_artifacts(attrs.deps), - )), - cc_common.create_cc_skylark_info(ctx = ctx), - out_cc_info.compilation_info, - out_cc_info.linking_info, - ] - -def _declare_output_groups(installdir, outputs): - dict_ = {} - dict_["gen_dir"] = depset([installdir]) - for output in outputs: - dict_[output.basename] = [output] - return dict_ - -def _get_transitive_artifacts(deps): - artifacts = [] - for dep in deps: - foreign_dep = get_foreign_cc_dep(dep) - if foreign_dep: - artifacts += [foreign_dep.artifacts] - return artifacts - -def _print_env(): - return "\n".join([ - "echo \"Environment:______________\"", - "env", - "echo \"__________________________\"", - ]) - -def _correct_path_variable(env): - value = env.get("PATH", "") - if not value: - return env - value = env.get("PATH", "").replace("C:\\", "/c/") - value = value.replace("\\", "/") - value = value.replace(";", ":") - env["PATH"] = "$PATH:" + value - return env - -def _depset(item): - if item == None: - return depset() - return depset([item]) - -def _list(item): - if item: - return [item] - return [] - -def _copy_deps_and_tools(files): - lines = [] - lines += _symlink_to_dir("lib", files.libs, False) - lines += _symlink_to_dir("include", files.headers + files.include_dirs, True) - - lines += _symlink_to_dir("bin", files.tools_files, False) - - for ext_dir in files.ext_build_dirs: - lines += ["symlink_to_dir $EXT_BUILD_ROOT/{} $EXT_BUILD_DEPS".format(_file_path(ext_dir))] - - lines += ["if [ -d $EXT_BUILD_DEPS/bin ]; then"] - - lines += [" tools=$(find $EXT_BUILD_DEPS/bin -maxdepth 1 -mindepth 1)"] - lines += [" for tool in $tools;"] - lines += [" do"] - lines += [" if [[ -d \"$tool\" ]] || [[ -L \"$tool\" ]]; then"] - lines += [" export PATH=$PATH:$tool"] - lines += [" fi"] - lines += [" done"] - lines += ["fi"] - lines += ["path $EXT_BUILD_DEPS/bin"] - - return lines - -def _symlink_to_dir(dir_name, files_list, link_children): - if len(files_list) == 0: - return [] - lines = ["mkdir -p $EXT_BUILD_DEPS/" + dir_name] - - paths_list = [] - for file in files_list: - paths_list += [_file_path(file)] - - link_function = "symlink_contents_to_dir" if link_children else "symlink_to_dir" - for path in paths_list: - lines += ["{} $EXT_BUILD_ROOT/{} $EXT_BUILD_DEPS/{}".format(link_function, path, dir_name)] - - return lines - -def _file_path(file): - return file if type(file) == "string" else file.path - -def _check_file_name(var, name): - if (len(var) == 0): - fail("{} can not be empty string.".format(name.capitalize())) - - if (not var[0:1].isalpha()): - fail("{} should start with a letter.".format(name.capitalize())) - for index in range(1, len(var) - 1): - letter = var[index] - if not letter.isalnum() and letter != "_": - fail("{} should be alphanumeric or '_'.".format(name.capitalize())) - -_Outputs = provider( - doc = "Provider to keep different kinds of the external build output files and directories", - fields = dict( - out_include_dir = "Directory with header files (relative to install directory)", - out_binary_files = "Binary files, which will be created by the action", - libraries = "Library files, which will be created by the action", - declared_outputs = "All output files and directories of the action", - ), -) - -def _define_outputs(ctx, attrs, lib_name): - static_libraries = [] - if not hasattr(attrs, "headers_only") or not attrs.headers_only: - if (not (hasattr(attrs, "static_libraries") and len(attrs.static_libraries) > 0) and - not (hasattr(attrs, "shared_libraries") and len(attrs.shared_libraries) > 0) and - not (hasattr(attrs, "interface_libraries") and len(attrs.interface_libraries) > 0) and - not (hasattr(attrs, "binaries") and len(attrs.binaries) > 0)): - static_libraries = [lib_name + (".lib" if targets_windows(ctx, None) else ".a")] - else: - static_libraries = attrs.static_libraries - - _check_file_name(lib_name, "Library name") - - out_include_dir = ctx.actions.declare_directory(lib_name + "/" + attrs.out_include_dir) - - out_binary_files = _declare_out(ctx, lib_name, attrs.out_bin_dir, attrs.binaries) - - libraries = LibrariesToLinkInfo( - static_libraries = _declare_out(ctx, lib_name, attrs.out_lib_dir, static_libraries), - shared_libraries = _declare_out(ctx, lib_name, attrs.out_lib_dir, attrs.shared_libraries), - interface_libraries = _declare_out(ctx, lib_name, attrs.out_lib_dir, attrs.interface_libraries), - ) - declared_outputs = [out_include_dir] + out_binary_files - declared_outputs += libraries.static_libraries - declared_outputs += libraries.shared_libraries + libraries.interface_libraries - - return _Outputs( - out_include_dir = out_include_dir, - out_binary_files = out_binary_files, - libraries = libraries, - declared_outputs = declared_outputs, - ) - -def _declare_out(ctx, lib_name, dir_, files): - if files and len(files) > 0: - return [ctx.actions.declare_file("/".join([lib_name, dir_, file])) for file in files] - return [] - -InputFiles = provider( - doc = """Provider to keep different kinds of input files, directories, -and C/C++ compilation and linking info from dependencies""", - fields = dict( - headers = """Include files built by Bazel. Will be copied into $EXT_BUILD_DEPS/include.""", - include_dirs = """Include directories built by Bazel. -Will be copied into $EXT_BUILD_DEPS/include.""", - libs = """Library files built by Bazel. -Will be copied into $EXT_BUILD_DEPS/lib.""", - tools_files = """Files and directories with tools needed for configuration/building -to be copied into the bin folder, which is added to the PATH""", - ext_build_dirs = """Directories with libraries, built by framework function. -This directories should be copied into $EXT_BUILD_DEPS/lib-name as is, with all contents.""", - deps_compilation_info = "Merged CcCompilationInfo from deps attribute", - deps_linking_info = "Merged CcLinkingInfo from deps attribute", - declared_inputs = "All files and directories that must be declared as action inputs", - ), -) - -def _define_inputs(attrs): - compilation_infos_all = [] - linking_infos_all = [] - - bazel_headers = [] - bazel_system_includes = [] - bazel_libs = [] - - # This framework function-built libraries: copy result directories under - # $EXT_BUILD_DEPS/lib-name - ext_build_dirs = [] - ext_build_dirs_set = {} - - for dep in attrs.deps: - external_deps = get_foreign_cc_dep(dep) - - linking_infos_all += [dep[CcLinkingInfo]] - compilation_infos_all += [dep[CcCompilationInfo]] - - if external_deps: - for artifact in external_deps.artifacts: - if not ext_build_dirs_set.get(artifact.gen_dir): - ext_build_dirs_set[artifact.gen_dir] = 1 - ext_build_dirs += [artifact.gen_dir] - else: - headers_info = _get_headers(dep[CcCompilationInfo]) - bazel_headers += headers_info.headers - bazel_system_includes += headers_info.include_dirs - bazel_libs += _collect_libs(dep[CcLinkingInfo]) - - tools_roots = [] - tools_files = [] - for tool in attrs.tools_deps: - tool_root = detect_root(tool) - tools_roots += [tool_root] - for file_list in tool.files: - tools_files += _list(file_list) - - for tool in attrs.additional_tools: - for file_list in tool.files: - tools_files += _list(file_list) - - # These variables are needed for correct C/C++ providers constraction, - # they should contain all libraries and include directories. - deps_compilation = cc_common.merge_cc_compilation_infos(cc_compilation_infos = compilation_infos_all) - deps_linking = cc_common.merge_cc_linking_infos(cc_linking_infos = linking_infos_all) - - return InputFiles( - headers = bazel_headers, - include_dirs = bazel_system_includes, - libs = bazel_libs, - tools_files = tools_roots, - deps_compilation_info = deps_compilation, - deps_linking_info = deps_linking, - ext_build_dirs = ext_build_dirs, - declared_inputs = depset(attrs.lib_source.files) + bazel_libs + tools_files + - attrs.additional_inputs + deps_compilation.headers + ext_build_dirs, - ) - -def get_foreign_cc_dep(dep): - return dep[ForeignCcDeps] if ForeignCcDeps in dep else None - -# consider optimization here to do not iterate both collections -def _get_headers(compilation_info): - include_dirs = collections.uniq(compilation_info.system_includes.to_list()) - headers = [] - for header in compilation_info.headers: - path = header.path - included = False - for dir_ in include_dirs: - if path.startswith(dir_): - included = True - break - if not included: - headers += [header] - return struct( - headers = headers, - include_dirs = include_dirs, - ) - -def _define_out_cc_info(ctx, attrs, inputs, outputs): - compilation_info = CcCompilationInfo( - headers = depset([outputs.out_include_dir]), - system_includes = depset([outputs.out_include_dir.path]), - defines = depset(attrs.defines), - ) - out_compilation_info = cc_common.merge_cc_compilation_infos( - cc_compilation_infos = [inputs.deps_compilation_info, compilation_info], - ) - - linking_info = create_linking_info(ctx, depset(direct = attrs.linkopts), outputs.libraries) - out_linking_info = cc_common.merge_cc_linking_infos( - cc_linking_infos = [linking_info, inputs.deps_linking_info], - ) - - return struct( - compilation_info = out_compilation_info, - linking_info = out_linking_info, - ) - -def _extract_link_params(cc_linking): - return [ - cc_linking.static_mode_params_for_dynamic_library, - cc_linking.static_mode_params_for_executable, - cc_linking.dynamic_mode_params_for_dynamic_library, - cc_linking.dynamic_mode_params_for_executable, - ] - -def _collect_libs(cc_linking): - libs = [] - for params in _extract_link_params(cc_linking): - libs += [lib.artifact() for lib in params.libraries_to_link] - libs += params.dynamic_libraries_for_runtime.to_list() - return collections.uniq(libs)