Do not support Bazel before 0.20 (#195)

This commit is contained in:
irengrig 2019-01-08 18:21:22 +01:00 committed by GitHub
parent 99ea1b09fc
commit 4dc369a845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 7 additions and 1033 deletions

View File

@ -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.

View File

@ -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",
],
),
}
},
)

View File

@ -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 '<top-package-name>/',
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

View File

@ -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)