Extract the Bazel `cc_import` rule from `@_builtins` to `@rules_cc`
PiperOrigin-RevId: 786644184
Change-Id: I83991e4d83c8700aade3cfee454ef94b8c6ea155
diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl
index 35f6812..7eb24ef 100644
--- a/cc/common/cc_helper.bzl
+++ b/cc/common/cc_helper.bzl
@@ -15,6 +15,15 @@
load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE")
load(":cc_common.bzl", "cc_common")
+load(
+ ":cc_helper_internal.bzl",
+ "get_relative_path",
+ "is_versioned_shared_library_extension_valid",
+ "path_contains_up_level_references",
+ _package_source_root = "package_source_root",
+ _repository_exec_path = "repository_exec_path",
+)
+load(":cc_info.bzl", "CcInfo")
load(":visibility.bzl", "INTERNAL_VISIBILITY")
visibility(INTERNAL_VISIBILITY)
@@ -27,6 +36,62 @@
# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:linker_mode)
# LINT.IfChange(forked_exports)
+
+def _get_compilation_contexts_from_deps(deps):
+ compilation_contexts = []
+ for dep in deps:
+ if CcInfo in dep:
+ compilation_contexts.append(dep[CcInfo].compilation_context)
+ return compilation_contexts
+
+def _tool_path(cc_toolchain, tool):
+ return cc_toolchain._tool_paths.get(tool, None)
+
+def _get_toolchain_global_make_variables(cc_toolchain):
+ result = {
+ "CC": _tool_path(cc_toolchain, "gcc"),
+ "AR": _tool_path(cc_toolchain, "ar"),
+ "NM": _tool_path(cc_toolchain, "nm"),
+ "LD": _tool_path(cc_toolchain, "ld"),
+ "STRIP": _tool_path(cc_toolchain, "strip"),
+ "C_COMPILER": cc_toolchain.compiler,
+ } # buildifier: disable=unsorted-dict-items
+
+ obj_copy_tool = _tool_path(cc_toolchain, "objcopy")
+ if obj_copy_tool != None:
+ # objcopy is optional in Crostool.
+ result["OBJCOPY"] = obj_copy_tool
+ gcov_tool = _tool_path(cc_toolchain, "gcov-tool")
+ if gcov_tool != None:
+ # gcovtool is optional in Crostool.
+ result["GCOVTOOL"] = gcov_tool
+
+ libc = cc_toolchain.libc
+ if libc.startswith("glibc-"):
+ # Strip "glibc-" prefix.
+ result["GLIBC_VERSION"] = libc[6:]
+ else:
+ result["GLIBC_VERSION"] = libc
+
+ abi_glibc_version = cc_toolchain._abi_glibc_version
+ if abi_glibc_version != None:
+ result["ABI_GLIBC_VERSION"] = abi_glibc_version
+
+ abi = cc_toolchain._abi
+ if abi != None:
+ result["ABI"] = abi
+
+ result["CROSSTOOLTOP"] = cc_toolchain._crosstool_top_path
+ return result
+
+_SHARED_LIBRARY_EXTENSIONS = ["so", "dll", "dylib", "wasm"]
+
+def _is_valid_shared_library_artifact(shared_library):
+ if (shared_library.extension in _SHARED_LIBRARY_EXTENSIONS):
+ return True
+
+ return is_versioned_shared_library_extension_valid(shared_library.basename)
+
def _get_static_mode_params_for_dynamic_library_libraries(libs):
linker_inputs = []
for lib in libs.to_list():
@@ -285,11 +350,79 @@
)
)
+SYSROOT_FLAG = "--sysroot="
+
+def _contains_sysroot(original_cc_flags, feature_config_cc_flags):
+ if SYSROOT_FLAG in original_cc_flags:
+ return True
+ for flag in feature_config_cc_flags:
+ if SYSROOT_FLAG in flag:
+ return True
+
+ return False
+
+def _get_cc_flags_make_variable(_ctx, feature_configuration, cc_toolchain):
+ original_cc_flags = cc_toolchain._legacy_cc_flags_make_variable
+ sysroot_cc_flag = ""
+ if cc_toolchain.sysroot != None:
+ sysroot_cc_flag = SYSROOT_FLAG + cc_toolchain.sysroot
+
+ build_vars = cc_toolchain._build_variables
+ feature_config_cc_flags = cc_common.get_memory_inefficient_command_line(
+ feature_configuration = feature_configuration,
+ action_name = "cc-flags-make-variable",
+ variables = build_vars,
+ )
+ cc_flags = [original_cc_flags]
+
+ # Only add sysroots flag if nothing else adds sysroot, BUT it must appear
+ # before the feature config flags.
+ if not _contains_sysroot(original_cc_flags, feature_config_cc_flags):
+ cc_flags.append(sysroot_cc_flag)
+ cc_flags.extend(feature_config_cc_flags)
+ return {"CC_FLAGS": " ".join(cc_flags)}
+
+def _package_exec_path(ctx, package, sibling_repository_layout):
+ return get_relative_path(_repository_exec_path(ctx.label.workspace_name, sibling_repository_layout), package)
+
+def _system_include_dirs(ctx, additional_make_variable_substitutions):
+ result = []
+ sibling_repository_layout = ctx.configuration.is_sibling_repository_layout()
+ package = ctx.label.package
+ package_exec_path = _package_exec_path(ctx, package, sibling_repository_layout)
+ package_source_root = _package_source_root(ctx.label.workspace_name, package, sibling_repository_layout)
+ for include in ctx.attr.includes:
+ includes_attr = _expand(ctx, include, additional_make_variable_substitutions)
+ if includes_attr.startswith("/"):
+ continue
+ includes_path = get_relative_path(package_exec_path, includes_attr)
+ if not sibling_repository_layout and path_contains_up_level_references(includes_path):
+ fail("Path references a path above the execution root.", attr = "includes")
+
+ if includes_path == ".":
+ fail("'" + includes_attr + "' resolves to the workspace root, which would allow this rule and all of its " +
+ "transitive dependents to include any file in your workspace. Please include only" +
+ " what you need", attr = "includes")
+ result.append(includes_path)
+
+ # We don't need to perform the above checks against out_includes_path again since any errors
+ # must have manifested in includesPath already.
+ out_includes_path = get_relative_path(package_source_root, includes_attr)
+ if (ctx.configuration.has_separate_genfiles_directory()):
+ result.append(get_relative_path(ctx.genfiles_dir.path, out_includes_path))
+ result.append(get_relative_path(ctx.bin_dir.path, out_includes_path))
+ return result
+
cc_helper = struct(
create_strip_action = _create_strip_action,
get_expanded_env = _get_expanded_env,
get_static_mode_params_for_dynamic_library_libraries = _get_static_mode_params_for_dynamic_library_libraries,
should_use_pic = _should_use_pic,
tokenize = _tokenize,
+ is_valid_shared_library_artifact = _is_valid_shared_library_artifact,
+ get_toolchain_global_make_variables = _get_toolchain_global_make_variables,
+ get_cc_flags_make_variable = _get_cc_flags_make_variable,
+ get_compilation_contexts_from_deps = _get_compilation_contexts_from_deps,
+ system_include_dirs = _system_include_dirs,
)
# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl:forked_exports)
diff --git a/cc/common/cc_helper_internal.bzl b/cc/common/cc_helper_internal.bzl
new file mode 100644
index 0000000..ae25bb7
--- /dev/null
+++ b/cc/common/cc_helper_internal.bzl
@@ -0,0 +1,98 @@
+# Copyright 2024 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.
+
+"""
+Utility functions for C++ rules that don't depend on cc_common.
+
+Only use those within C++ implementation. The others need to go through cc_common.
+"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+# LINT.IfChange(forked_exports)
+
+def is_versioned_shared_library_extension_valid(shared_library_name):
+ """Validates the name against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
+
+ Args:
+ shared_library_name: (str) the name to validate
+
+ Returns:
+ (bool)
+ """
+
+ # must match VERSIONED_SHARED_LIBRARY.
+ for ext in (".so.", ".dylib."):
+ name, _, version = shared_library_name.rpartition(ext)
+ if name and version:
+ version_parts = version.split(".")
+ for part in version_parts:
+ if not part[0].isdigit():
+ return False
+ for c in part[1:].elems():
+ if not (c.isalnum() or c == "_"):
+ return False
+ return True
+ return False
+
+def _is_repository_main(repository):
+ return repository == ""
+
+def package_source_root(repository, package, sibling_repository_layout):
+ """
+ Determines the source root for a given repository and package.
+
+ Args:
+ repository: The repository to get the source root for.
+ package: The package to get the source root for.
+ sibling_repository_layout: Whether the repository layout is a sibling repository layout.
+
+ Returns:
+ The source root for the given repository and package.
+ """
+ if _is_repository_main(repository) or sibling_repository_layout:
+ return package
+ if repository.startswith("@"):
+ repository = repository[1:]
+ return paths.get_relative(paths.get_relative("external", repository), package)
+
+def repository_exec_path(repository, sibling_repository_layout):
+ """
+ Determines the exec path for a given repository.
+
+ Args:
+ repository: The repository to get the exec path for.
+ sibling_repository_layout: Whether the repository layout is a sibling repository layout.
+
+ Returns:
+ The exec path for the given repository.
+ """
+ if _is_repository_main(repository):
+ return ""
+ prefix = "external"
+ if sibling_repository_layout:
+ prefix = ".."
+ if repository.startswith("@"):
+ repository = repository[1:]
+ return paths.get_relative(prefix, repository)
+
+# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl:forked_exports)
+
+def get_relative_path(path_a, path_b):
+ if paths.is_absolute(path_b):
+ return path_b
+ return paths.normalize(paths.join(path_a, path_b))
+
+def path_contains_up_level_references(path):
+ return path.startswith("..") and (len(path) == 2 or path[2] == "/")
diff --git a/cc/common/semantics.bzl b/cc/common/semantics.bzl
new file mode 100644
index 0000000..121142f
--- /dev/null
+++ b/cc/common/semantics.bzl
@@ -0,0 +1,205 @@
+# Copyright 2020 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.
+
+"""Semantics for Bazel cc rules"""
+
+# Point virtual includes symlinks to the source root for better IDE integration.
+# See https://github.com/bazelbuild/bazel/pull/20540.
+# TODO: b/320980684 - Add a test that fails if this is flipped to True.
+USE_EXEC_ROOT_FOR_VIRTUAL_INCLUDES_SYMLINKS = False
+
+def _get_proto_aspects():
+ return []
+
+def _should_create_empty_archive():
+ return False
+
+def _validate_attributes(_ctx):
+ pass
+
+def _get_stl():
+ return attr.label()
+
+def _get_repo():
+ return "bazel_tools"
+
+def _get_platforms_root():
+ return "platforms//"
+
+def _additional_fragments():
+ return []
+
+def _get_licenses_attr():
+ # TODO(b/182226065): Change to applicable_licenses
+ return {}
+
+def _def_parser_computed_default(name, tags):
+ # This is needed to break the dependency cycle.
+ if "__DONT_DEPEND_ON_DEF_PARSER__" in tags or "def_parser" in name:
+ return None
+ else:
+ return Label("@bazel_tools//tools/def_parser:def_parser")
+
+def _get_def_parser():
+ return attr.label(
+ default = _def_parser_computed_default,
+ allow_single_file = True,
+ cfg = "exec",
+ )
+
+def _get_grep_includes():
+ return attr.label()
+
+def _get_runtimes_toolchain():
+ return []
+
+def _get_test_malloc_attr():
+ return {}
+
+def _get_coverage_attrs():
+ return {
+ "_lcov_merger": attr.label(
+ default = configuration_field(fragment = "coverage", name = "output_generator"),
+ executable = True,
+ cfg = config.exec(exec_group = "test"),
+ ),
+ "_collect_cc_coverage": attr.label(
+ default = "@bazel_tools//tools/test:collect_cc_coverage",
+ executable = True,
+ cfg = config.exec(exec_group = "test"),
+ ),
+ } # buildifier: disable=unsorted-dict-items
+
+def _get_coverage_env(ctx):
+ return ctx.runfiles(), {}
+
+def _get_cc_runtimes(ctx, is_library):
+ if is_library:
+ return []
+
+ runtimes = [ctx.attr.link_extra_lib]
+
+ if ctx.fragments.cpp.custom_malloc != None:
+ runtimes.append(ctx.attr._default_malloc)
+ else:
+ runtimes.append(ctx.attr.malloc)
+
+ return runtimes
+
+def _get_cc_runtimes_copts(_ctx):
+ return []
+
+def _get_implementation_deps_allowed_attr():
+ return {}
+
+def _check_can_use_implementation_deps(ctx):
+ experimental_cc_implementation_deps = ctx.fragments.cpp.experimental_cc_implementation_deps()
+ if (not experimental_cc_implementation_deps and ctx.attr.implementation_deps):
+ fail("requires --experimental_cc_implementation_deps", attr = "implementation_deps")
+
+_WINDOWS_PLATFORM = Label("@platforms//os:windows") # Resolve the label within builtins context
+
+def _get_linkstatic_default_for_test():
+ return select({
+ _WINDOWS_PLATFORM: True,
+ "//conditions:default": False,
+ })
+
+def _get_cc_link_memlimit(_compilation_mode, exec_info):
+ return exec_info
+
+def _get_nocopts_attr():
+ return {}
+
+def _get_experimental_link_static_libraries_once(ctx):
+ return ctx.fragments.cpp.experimental_link_static_libraries_once()
+
+def _check_cc_shared_library_tags(_ctx):
+ pass
+
+def _cpp_modules_tools():
+ return {
+ "_aggregate_ddi": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "@" + _get_repo() + "//tools/cpp:aggregate-ddi",
+ ),
+ "_generate_modmap": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "@" + _get_repo() + "//tools/cpp:generate-modmap",
+ ),
+ }
+
+def _validate(_ctx, _rule_name):
+ pass
+
+semantics = struct(
+ toolchain = "@bazel_tools//tools/cpp:toolchain_type",
+ validate = _validate,
+ allowlist_attrs = {},
+ ALLOWED_RULES_IN_DEPS = [
+ "cc_library",
+ "objc_library",
+ "cc_proto_library",
+ "cc_import",
+ ],
+ ALLOWED_FILES_IN_DEPS = [
+ ".ld",
+ ".lds",
+ ".ldscript",
+ ],
+ ALLOWED_RULES_WITH_WARNINGS_IN_DEPS = [],
+ validate_attributes = _validate_attributes,
+ get_repo = _get_repo,
+ get_platforms_root = _get_platforms_root,
+ additional_fragments = _additional_fragments,
+ get_licenses_attr = _get_licenses_attr,
+ get_def_parser = _get_def_parser,
+ get_stl = _get_stl,
+ should_create_empty_archive = _should_create_empty_archive,
+ get_grep_includes = _get_grep_includes,
+ get_implementation_deps_allowed_attr = _get_implementation_deps_allowed_attr,
+ check_can_use_implementation_deps = _check_can_use_implementation_deps,
+ get_linkstatic_default_for_test = _get_linkstatic_default_for_test,
+ get_cc_link_memlimit = _get_cc_link_memlimit,
+ get_runtimes_toolchain = _get_runtimes_toolchain,
+ get_test_malloc_attr = _get_test_malloc_attr,
+ get_cc_runtimes = _get_cc_runtimes,
+ get_cc_runtimes_copts = _get_cc_runtimes_copts,
+ get_coverage_attrs = _get_coverage_attrs,
+ get_coverage_env = _get_coverage_env,
+ get_proto_aspects = _get_proto_aspects,
+ get_nocopts_attr = _get_nocopts_attr,
+ get_experimental_link_static_libraries_once = _get_experimental_link_static_libraries_once,
+ cpp_modules_tools = _cpp_modules_tools,
+ check_cc_shared_library_tags = _check_cc_shared_library_tags,
+ BUILD_INFO_TRANLATOR_LABEL = "@bazel_tools//tools/build_defs/build_info:cc_build_info",
+ CC_PROTO_TOOLCHAIN = "@rules_cc//cc/proto:toolchain_type",
+ is_bazel = True,
+ extra_exec_groups = {},
+ stamp_extra_docs = "",
+ malloc_docs = """
+ Override the default dependency on malloc.
+ <p>
+ By default, C++ binaries are linked against <code>//tools/cpp:malloc</code>,
+ which is an empty library so the binary ends up using libc malloc.
+ This label must refer to a <code>cc_library</code>. If compilation is for a non-C++
+ rule, this option has no effect. The value of this attribute is ignored if
+ <code>linkshared=True</code> is specified.
+ </p>
+""",
+ cc_binary_extra_docs = "",
+ cc_test_extra_docs = "",
+)
diff --git a/cc/private/rules_impl/cc_import.bzl b/cc/private/rules_impl/cc_import.bzl
index 05c3f6b..418facd 100644
--- a/cc/private/rules_impl/cc_import.bzl
+++ b/cc/private/rules_impl/cc_import.bzl
@@ -11,7 +11,433 @@
# 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.
-"""cc_import rule without a macro"""
-# buildifier: disable=native-cc-import
-cc_import = native.cc_import
+"""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 or .dll)")
+
+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,
+ )
+
+ linker_input = cc_common.create_linker_input(
+ libraries = depset([library_to_link]),
+ user_link_flags = depset(ctx.attr.linkopts),
+ 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)
+ additional_make_variable_substitutions.update(cc_helper.get_cc_flags_make_variable(ctx, feature_configuration, cc_toolchain))
+
+ runtimes_deps = semantics.get_cc_runtimes(ctx, True)
+ runtimes_copts = semantics.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,
+ )
+
+ 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)
+
+ return [merged_cc_info]
+
+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>
+ or <code>.dylib</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 1, 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 1, 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 alwayslink 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>-isystem 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"],
+ ),
+ "_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"],
+)
diff --git a/tests/simple_import/BUILD b/tests/simple_import/BUILD
new file mode 100644
index 0000000..aa50f8c
--- /dev/null
+++ b/tests/simple_import/BUILD
@@ -0,0 +1,27 @@
+# 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.
+
+load("//cc:cc_binary.bzl", "cc_binary")
+load("//cc:cc_import.bzl", "cc_import")
+
+cc_import(
+ name = "foo_import",
+ hdrs = ["foo.h"],
+)
+
+cc_binary(
+ name = "foo_bin",
+ srcs = ["foo.cc"],
+ deps = [":foo_import"],
+)
diff --git a/tests/simple_import/foo.cc b/tests/simple_import/foo.cc
new file mode 100644
index 0000000..c3aeba4
--- /dev/null
+++ b/tests/simple_import/foo.cc
@@ -0,0 +1 @@
+int main() { return 0; }
\ No newline at end of file
diff --git a/tests/simple_import/foo.h b/tests/simple_import/foo.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/simple_import/foo.h