| # Copyright 2025 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Starlark implementation of cc_import. | |
| We may change the implementation at any moment or even delete this file. Do not | |
| rely on this. Pass the flag --experimental_starlark_cc_import | |
| """ | |
| load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE", "find_cc_toolchain", "use_cc_toolchain") | |
| load("//cc/common:cc_common.bzl", "cc_common") | |
| load("//cc/common:cc_helper.bzl", "cc_helper") | |
| load("//cc/common:cc_info.bzl", "CcInfo") | |
| load("//cc/common:semantics.bzl", "semantics") | |
| CPP_LINK_STATIC_LIBRARY_ACTION_NAME = "c++-link-static-library" | |
| def _perform_error_checks( | |
| system_provided, | |
| shared_library_artifact, | |
| interface_library_artifact): | |
| # 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_artifact != None: | |
| 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 shared_library_artifact == None and | |
| interface_library_artifact != None): | |
| fail("'shared_library' should be specified when 'system_provided' is false") | |
| if (shared_library_artifact != None and | |
| not cc_helper.is_valid_shared_library_artifact(shared_library_artifact)): | |
| fail("'shared_library' does not produce any cc_import shared_library files (expected .so, .dylib, .dll, .pyd or .xll)") | |
| def _create_archive_action( | |
| ctx, | |
| feature_configuration, | |
| cc_toolchain, | |
| output_file, | |
| object_files): | |
| archiver_path = cc_common.get_tool_for_action( | |
| feature_configuration = feature_configuration, | |
| action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, | |
| ) | |
| archiver_variables = cc_common.create_link_variables( | |
| feature_configuration = feature_configuration, | |
| cc_toolchain = cc_toolchain, | |
| output_file = output_file.path, | |
| is_using_linker = False, | |
| ) | |
| command_line = cc_common.get_memory_inefficient_command_line( | |
| feature_configuration = feature_configuration, | |
| action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, | |
| variables = archiver_variables, | |
| ) | |
| args = ctx.actions.args() | |
| args.add_all(command_line) | |
| args.add_all(object_files) | |
| args.use_param_file("@%s", use_always = True) | |
| env = cc_common.get_environment_variables( | |
| feature_configuration = feature_configuration, | |
| action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, | |
| variables = archiver_variables, | |
| ) | |
| # TODO(bazel-team): PWD=/proc/self/cwd env var is missing, but it is present when an analogous archiving | |
| # action is created by cc_library | |
| ctx.actions.run( | |
| executable = archiver_path, | |
| toolchain = CC_TOOLCHAIN_TYPE, | |
| arguments = [args], | |
| env = env, | |
| inputs = depset( | |
| direct = object_files, | |
| transitive = [ | |
| cc_toolchain.all_files, | |
| ], | |
| ), | |
| use_default_shell_env = True, | |
| outputs = [output_file], | |
| mnemonic = "CppArchive", | |
| ) | |
| def _cc_import_impl(ctx): | |
| cc_toolchain = find_cc_toolchain(ctx) | |
| feature_configuration = cc_common.configure_features( | |
| ctx = ctx, | |
| cc_toolchain = cc_toolchain, | |
| requested_features = ctx.features, | |
| unsupported_features = ctx.disabled_features, | |
| ) | |
| _perform_error_checks( | |
| ctx.attr.system_provided, | |
| ctx.file.shared_library, | |
| ctx.file.interface_library, | |
| ) | |
| pic_static_library = ctx.file.pic_static_library or None | |
| static_library = ctx.file.static_library or None | |
| if ctx.files.pic_objects and not pic_static_library: | |
| lib_name = "lib" + ctx.label.name + ".pic.a" | |
| pic_static_library = ctx.actions.declare_file(lib_name) | |
| _create_archive_action(ctx, feature_configuration, cc_toolchain, pic_static_library, ctx.files.pic_objects) | |
| if ctx.files.objects and not static_library: | |
| lib_name = "lib" + ctx.label.name + ".a" | |
| static_library = ctx.actions.declare_file(lib_name) | |
| _create_archive_action(ctx, feature_configuration, cc_toolchain, static_library, ctx.files.objects) | |
| not_none_artifact_to_link = False | |
| # Check if there is something to link, if not skip that part. | |
| if static_library != None or pic_static_library != None or ctx.file.interface_library != None or ctx.file.shared_library != None: | |
| not_none_artifact_to_link = True | |
| linking_context = None | |
| if not_none_artifact_to_link: | |
| library_to_link = cc_common.create_library_to_link( | |
| actions = ctx.actions, | |
| feature_configuration = feature_configuration, | |
| cc_toolchain = cc_toolchain, | |
| static_library = static_library, | |
| pic_static_library = pic_static_library, | |
| interface_library = ctx.file.interface_library, | |
| dynamic_library = ctx.file.shared_library, | |
| pic_objects = ctx.files.pic_objects, | |
| objects = ctx.files.objects, | |
| alwayslink = ctx.attr.alwayslink, | |
| ) | |
| link_flags = [] | |
| if static_library and cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "warn_backrefs_defined"): | |
| link_flags.append( | |
| "-Wl,--warn-backrefs-exclude=*{}*".format(static_library.short_path), | |
| ) | |
| link_flags.extend(ctx.attr.linkopts) | |
| linker_input = cc_common.create_linker_input( | |
| libraries = depset([library_to_link]), | |
| user_link_flags = depset(link_flags), | |
| owner = ctx.label, | |
| ) | |
| linking_context = cc_common.create_linking_context( | |
| linker_inputs = depset([linker_input]), | |
| ) | |
| additional_make_variable_substitutions = cc_helper.get_toolchain_global_make_variables(cc_toolchain, feature_configuration) | |
| additional_make_variable_substitutions.update(cc_helper.get_cc_flags_make_variable(ctx, feature_configuration, cc_toolchain)) | |
| runtimes_deps = cc_helper.get_cc_runtimes(ctx, True) | |
| runtimes_copts = cc_helper.get_cc_runtimes_copts(ctx) | |
| compilation_contexts = cc_helper.get_compilation_contexts_from_deps(runtimes_deps) | |
| (compilation_context, _) = cc_common.compile( | |
| actions = ctx.actions, | |
| feature_configuration = feature_configuration, | |
| user_compile_flags = runtimes_copts, | |
| cc_toolchain = cc_toolchain, | |
| compilation_contexts = compilation_contexts, | |
| public_hdrs = ctx.files.hdrs, | |
| includes = cc_helper.system_include_dirs(ctx, additional_make_variable_substitutions), | |
| name = ctx.label.name, | |
| strip_include_prefix = ctx.attr.strip_include_prefix, | |
| defines = cc_helper.defines(ctx, {}), | |
| ) | |
| this_cc_info = CcInfo(compilation_context = compilation_context, linking_context = linking_context) | |
| cc_infos = [this_cc_info] | |
| for dep in ctx.attr.deps: | |
| cc_infos.append(dep[CcInfo]) | |
| merged_cc_info = cc_common.merge_cc_infos(direct_cc_infos = [this_cc_info], cc_infos = cc_infos) | |
| runfiles_list = [] | |
| for data_dep in ctx.attr.data: | |
| if data_dep[DefaultInfo].data_runfiles.files: | |
| runfiles_list.append(data_dep[DefaultInfo].data_runfiles) | |
| else: | |
| # This branch ensures interop with custom Starlark rules following | |
| # https://bazel.build/extending/rules#runfiles_features_to_avoid | |
| runfiles_list.append(ctx.runfiles(transitive_files = data_dep[DefaultInfo].files)) | |
| runfiles_list.append(data_dep[DefaultInfo].default_runfiles) | |
| for dep in ctx.attr.deps: | |
| runfiles_list.append(dep[DefaultInfo].default_runfiles) | |
| runfiles = ctx.runfiles().merge_all(runfiles_list) | |
| if linking_context: | |
| default_runfiles = ctx.runfiles(files = cc_helper.get_dynamic_libraries_for_runtime(linking_context, True)) | |
| default_runfiles = runfiles.merge(default_runfiles) | |
| data_runfiles = ctx.runfiles(files = cc_helper.get_dynamic_libraries_for_runtime(linking_context, False)) | |
| data_runfiles = runfiles.merge(data_runfiles) | |
| else: | |
| default_runfiles = runfiles | |
| data_runfiles = runfiles | |
| return [ | |
| merged_cc_info, | |
| DefaultInfo( | |
| default_runfiles = default_runfiles, | |
| data_runfiles = data_runfiles, | |
| ), | |
| ] | |
| cc_import = rule( | |
| implementation = _cc_import_impl, | |
| doc = """ | |
| <p> | |
| <code>cc_import</code> rules allows users to import precompiled C/C++ libraries. | |
| </p> | |
| <p> | |
| The following are the typical use cases: <br/> | |
| 1. Linking a static library | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| static_library = "libmylib.a", | |
| # If alwayslink is turned on, | |
| # libmylib.a will be forcely linked into any binary that depends on it. | |
| # alwayslink = True, | |
| ) | |
| </code></pre> | |
| 2. Linking a shared library (Unix) | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| shared_library = "libmylib.so", | |
| ) | |
| </code></pre> | |
| 3. Linking a shared library with interface library | |
| <p>On Unix: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| # libmylib.ifso is an interface library for libmylib.so which will be passed to linker | |
| interface_library = "libmylib.ifso", | |
| # libmylib.so will be available for runtime | |
| shared_library = "libmylib.so", | |
| ) | |
| </code></pre> | |
| <p>On Windows: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| # mylib.lib is an import library for mylib.dll which will be passed to linker | |
| interface_library = "mylib.lib", | |
| # mylib.dll will be available for runtime | |
| shared_library = "mylib.dll", | |
| ) | |
| </code></pre> | |
| 4. Linking a shared library with <code>system_provided=True</code> | |
| <p>On Unix: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| interface_library = "libmylib.ifso", # Or we can also use libmylib.so as its own interface library | |
| # libmylib.so is provided by system environment, for example it can be found in LD_LIBRARY_PATH. | |
| # This indicates that Bazel is not responsible for making libmylib.so available. | |
| system_provided = True, | |
| ) | |
| </code></pre> | |
| <p>On Windows: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| # mylib.lib is an import library for mylib.dll which will be passed to linker | |
| interface_library = "mylib.lib", | |
| # mylib.dll is provided by system environment, for example it can be found in PATH. | |
| # This indicates that Bazel is not responsible for making mylib.dll available. | |
| system_provided = True, | |
| ) | |
| </code></pre> | |
| 5. Linking to static or shared library | |
| <p>On Unix: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| static_library = "libmylib.a", | |
| shared_library = "libmylib.so", | |
| ) | |
| </code></pre> | |
| <p>On Windows: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "mylib", | |
| hdrs = ["mylib.h"], | |
| static_library = "libmylib.lib", # A normal static library | |
| interface_library = "mylib.lib", # An import library for mylib.dll | |
| shared_library = "mylib.dll", | |
| ) | |
| </code></pre> | |
| <p>The remaining is the same on Unix and Windows: | |
| <pre><code class="lang-starlark"> | |
| # first will link to libmylib.a (or libmylib.lib) | |
| cc_binary( | |
| name = "first", | |
| srcs = ["first.cc"], | |
| deps = [":mylib"], | |
| linkstatic = True, # default value | |
| ) | |
| # second will link to libmylib.so (or libmylib.lib) | |
| cc_binary( | |
| name = "second", | |
| srcs = ["second.cc"], | |
| deps = [":mylib"], | |
| linkstatic = False, | |
| ) | |
| </code></pre> | |
| <p> | |
| <code>cc_import</code> supports an include attribute. For example: | |
| <pre><code class="lang-starlark"> | |
| cc_import( | |
| name = "curl_lib", | |
| hdrs = glob(["vendor/curl/include/curl/*.h"]), | |
| includes = ["vendor/curl/include"], | |
| shared_library = "vendor/curl/lib/.libs/libcurl.dylib", | |
| ) | |
| </code></pre> | |
| </p> | |
| """, | |
| attrs = { | |
| "hdrs": attr.label_list( | |
| allow_files = True, | |
| flags = ["ORDER_INDEPENDENT", "DIRECT_COMPILE_TIME_INPUT"], | |
| doc = """ | |
| The list of header files published by | |
| this precompiled library to be directly included by sources in dependent rules.""", | |
| ), | |
| "static_library": attr.label(allow_single_file = [".a", ".lib"], doc = """ | |
| A single precompiled static library. | |
| <p> Permitted file types: | |
| <code>.a</code>, | |
| <code>.pic.a</code> | |
| or <code>.lib</code> | |
| </p>"""), | |
| "pic_static_library": attr.label(allow_single_file = [".pic.a", ".pic.lib"]), # TODO: b/320462212 - document this attribute | |
| "shared_library": attr.label(allow_single_file = True, doc = """ | |
| A single precompiled shared library. Bazel ensures it is available to the | |
| binary that depends on it during runtime. | |
| <p> Permitted file types: | |
| <code>.so</code>, | |
| <code>.dll</code> | |
| <code>.dylib</code>, | |
| or <code>.pyd</code> | |
| </p>"""), | |
| "interface_library": attr.label( | |
| allow_single_file = [".ifso", ".tbd", ".lib", ".so", ".dylib"], | |
| doc = """ | |
| A single interface library for linking the shared library. | |
| <p> Permitted file types: | |
| <code>.ifso</code>, | |
| <code>.tbd</code>, | |
| <code>.lib</code>, | |
| <code>.so</code> | |
| or <code>.dylib</code> | |
| </p>""", | |
| ), | |
| "pic_objects": attr.label_list( | |
| # TODO: b/320462212 - document this attribute | |
| allow_files = [".o", ".pic.o"], | |
| ), | |
| "objects": attr.label_list( | |
| # TODO: b/320462212 - document this attribute | |
| allow_files = [".o", ".nopic.o"], | |
| ), | |
| "system_provided": attr.bool(default = False, doc = """ | |
| If enabled, it indicates the shared library required at runtime is provided by the system. In | |
| this case, <code>interface_library</code> should be specified and | |
| <code>shared_library</code> should be empty."""), | |
| "alwayslink": attr.bool(default = False, doc = """ | |
| If enabled, any binary that depends (directly or indirectly) on this C++ | |
| precompiled library will link in all the object files archived in the static library, | |
| even if some contain no symbols referenced by the binary. | |
| This is useful if your code isn't explicitly called by code in | |
| the binary, e.g., if your code registers to receive some callback | |
| provided by some service. | |
| <p>If <code>alwayslink</code> doesn't work with VS 2017 on Windows, that is due to a | |
| <a href="https://github.com/bazelbuild/bazel/issues/3949">known issue</a>, | |
| please upgrade your VS 2017 to the latest version.</p>"""), | |
| "linkopts": attr.string_list(doc = """ | |
| Add these flags to the C++ linker command. | |
| Subject to <a href="make-variables.html">"Make" variable</a> substitution, | |
| <a href="common-definitions.html#sh-tokenization"> | |
| Bourne shell tokenization</a> and | |
| <a href="common-definitions.html#label-expansion">label expansion</a>. | |
| Each string in this attribute is added to <code>LINKOPTS</code> before | |
| linking the binary target. | |
| <p> | |
| Each element of this list that does not start with <code>$</code> or <code>-</code> is | |
| assumed to be the label of a target in <code>deps</code>. The | |
| list of files generated by that target is appended to the linker | |
| options. An error is reported if the label is invalid, or is | |
| not declared in <code>deps</code>. | |
| </p>"""), | |
| "includes": attr.string_list(doc = """ | |
| List of include dirs to be added to the compile line. | |
| Subject to <a href="${link make-variables}">"Make variable"</a> substitution. | |
| Each string is prepended with the package path and passed to the C++ toolchain for | |
| expansion via the "include_paths" CROSSTOOL feature. A toolchain running on a POSIX system | |
| with typical feature definitions will produce | |
| <code>-I path_to_package/include_entry</code>. | |
| This should only be used for third-party libraries that | |
| do not conform to the Google style of writing #include statements. | |
| Unlike <a href="#cc_binary.copts">COPTS</a>, these flags are added for this rule | |
| and every rule that depends on it. (Note: not the rules it depends upon!) Be | |
| very careful, since this may have far-reaching effects. When in doubt, add | |
| "-I" flags to <a href="#cc_binary.copts">COPTS</a> instead. | |
| <p> | |
| The default <code>include</code> path doesn't include generated | |
| files. If you need to <code>#include</code> a generated header | |
| file, list it in the <code>srcs</code>. | |
| </p> | |
| """), | |
| "strip_include_prefix": attr.string(doc = """ | |
| The prefix to strip from the paths of the headers of this rule. | |
| <p>When set, the headers in the <code>hdrs</code> attribute of this rule are accessible | |
| at their path with this prefix cut off. | |
| <p>If it's a relative path, it's taken as a package-relative one. If it's an absolute one, | |
| it's understood as a repository-relative path. | |
| <p>The prefix in the <code>include_prefix</code> attribute is added after this prefix is | |
| stripped. | |
| <p>This attribute is only legal under <code>third_party</code>. | |
| """), | |
| "deps": attr.label_list(doc = """ | |
| The list of other libraries that the target depends upon. | |
| See general comments about <code>deps</code> | |
| at <a href="${link common-definitions#typical-attributes}">Typical attributes defined by | |
| most build rules</a>."""), | |
| "data": attr.label_list( | |
| allow_files = True, | |
| flags = ["SKIP_CONSTRAINTS_OVERRIDE"], | |
| doc = """ | |
| The list of files needed by this library at runtime. | |
| See general comments about <code>data</code> | |
| at <a href="${link common-definitions#typical-attributes}">Typical attributes defined by | |
| most build rules</a>.""", | |
| ), | |
| "defines": attr.string_list(doc = """ | |
| List of defines to add to the compile line of this and all dependent targets. | |
| Subject to <a href="${link make-variables}">"Make" variable</a> substitution and | |
| <a href="${link common-definitions#sh-tokenization}">Bourne shell tokenization</a>. | |
| Each string, which must consist of a single Bourne shell token, | |
| is prepended with <code>-D</code> and added to the compile command line to this target, | |
| as well as to every rule that depends on it. Be very careful, since this may have | |
| far-reaching effects -- the defines are added to every target that depends on | |
| this target. | |
| """), | |
| "_use_auto_exec_groups": attr.bool(default = True), | |
| }, # buildifier: disable=unsorted-dict-items | |
| provides = [CcInfo], | |
| toolchains = use_cc_toolchain() + semantics.get_runtimes_toolchain(), | |
| fragments = ["cpp"], | |
| ) |