Open source cc_import.bzl

This is guarded behind an experimental flag.

RELNOTES:none
PiperOrigin-RevId: 316442624
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index b73b009..a59ce1d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -693,6 +693,18 @@
   }
 
   @Override
+  public void checkExperimentalStarlarkCcImport(StarlarkActionFactory starlarkActionFactoryApi)
+      throws EvalException {
+    if (!starlarkActionFactoryApi
+        .getActionConstructionContext()
+        .getConfiguration()
+        .getFragment(CppConfiguration.class)
+        .experimentalStarlarkCcImport()) {
+      throw Starlark.errorf("Pass --experimental_starlark_cc_import to use cc_shared_library");
+    }
+  }
+
+  @Override
   public CcLinkingContext createCcLinkingInfo(
       Object linkerInputs,
       Object librariesToLinkObject,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index 2e37154..80e8c229 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -729,4 +729,8 @@
   public boolean appleGenerateDsym() {
     return appleGenerateDsym;
   }
+
+  public boolean experimentalStarlarkCcImport() {
+    return cppOptions.experimentalStarlarkCcImport;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
index 2cd08c8..56ff203 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -961,6 +961,17 @@
       help = "Whether to force enable generating debug symbol(.dSYM) file(s) for dbg builds.")
   public boolean appleEnableAutoDsymDbg;
 
+  @Option(
+      name = "experimental_starlark_cc_import",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
+      effectTags = {
+        OptionEffectTag.LOADING_AND_ANALYSIS,
+      },
+      metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+      help = "If enabled, the Starlark version of cc_import can be used.")
+  public boolean experimentalStarlarkCcImport;
+
   /** See {@link #targetLibcTopLabel} documentation. * */
   @Override
   public FragmentOptions getNormalized() {
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcModuleApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcModuleApi.java
index 612d92fa..b80f83d 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcModuleApi.java
@@ -651,6 +651,21 @@
   void checkExperimentalCcSharedLibrary(StarlarkThread thread) throws EvalException;
 
   @StarlarkMethod(
+      name = "check_experimental_starlark_cc_import",
+      doc = "DO NOT USE. This is to guard use of cc_import.bzl",
+      documented = false,
+      parameters = {
+        @Param(
+            name = "actions",
+            type = StarlarkActionFactoryApi.class,
+            positional = false,
+            named = true,
+            doc = "<code>actions</code> object."),
+      })
+  void checkExperimentalStarlarkCcImport(StarlarkActionFactoryT starlarkActionFactoryApi)
+      throws EvalException;
+
+  @StarlarkMethod(
       name = "create_linking_context",
       doc = "Creates a <code>LinkingContext</code>.",
       useStarlarkThread = true,
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
index 8ac2eff..b227c13 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
@@ -183,6 +183,10 @@
   public void checkExperimentalCcSharedLibrary(StarlarkThread thread) {}
 
   @Override
+  public void checkExperimentalStarlarkCcImport(
+      StarlarkActionFactoryApi starlarkActionFactoryApi) {}
+
+  @Override
   public CcLinkingContextApi<FileApi> createCcLinkingInfo(
       Object linkerInputs,
       Object librariesToLinkObject,
diff --git a/tools/build_defs/BUILD b/tools/build_defs/BUILD
index e726001..ca1a47f 100644
--- a/tools/build_defs/BUILD
+++ b/tools/build_defs/BUILD
@@ -35,6 +35,7 @@
     name = "bzl_srcs",
     srcs = [
         "//tools/build_defs/cc:action_names.bzl",
+        "//tools/build_defs/cc:cc_import.bzl",
         "//tools/build_defs/hash:hash.bzl",
         "//tools/build_defs/pkg:bzl_srcs",
         "//tools/build_defs/repo:bzl_srcs",
diff --git a/tools/build_defs/cc/cc_import.bzl b/tools/build_defs/cc/cc_import.bzl
new file mode 100644
index 0000000..4b2db01
--- /dev/null
+++ b/tools/build_defs/cc/cc_import.bzl
@@ -0,0 +1,144 @@
+# 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.
+
+"""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("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+
+def _to_list(element):
+    if element == None:
+        return []
+    else:
+        return [element]
+
+def _to_depset(element):
+    if element == None:
+        return depset()
+    else:
+        return depset([element])
+
+def _is_shared_library_extension_valid(shared_library_name):
+    if (shared_library_name.endswith(".so") or
+        shared_library_name.endswith(".dll") or
+        shared_library_name.endswith(".dylib")):
+        return True
+
+    # validate against the regex "^.+\.so(\.\d\w*)+$" for versioned .so files
+    parts = shared_library_name.split(".")
+    extension = parts[1]
+    if extension != "so":
+        return False
+    version_parts = parts[2:]
+    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
+
+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 _is_shared_library_extension_valid(shared_library_artifact.basename)):
+        fail("'shared_library' does not produce any cc_import shared_library files (expected .so, .dylib or .dll)")
+
+def _get_no_pic_and_pic_static_library(static_library):
+    if static_library == None:
+        return (None, None)
+
+    if static_library.extension == ".pic.a":
+        return (None, static_library)
+    else:
+        return (static_library, None)
+
+def _cc_import_impl(ctx):
+    cc_toolchain = find_cpp_toolchain(ctx)
+    cc_common.check_experimental_starlark_cc_import(ctx.actions)
+    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,
+    )
+
+    (no_pic_static_library, pic_static_library) = _get_no_pic_and_pic_static_library(
+        ctx.file.static_library,
+    )
+
+    library_to_link = cc_common.create_library_to_link(
+        actions = ctx.actions,
+        feature_configuration = feature_configuration,
+        cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo],
+        static_library = no_pic_static_library,
+        pic_static_library = pic_static_library,
+        dynamic_library = ctx.file.shared_library,
+        interface_library = ctx.file.interface_library,
+        alwayslink = ctx.attr.alwayslink,
+    )
+
+    linking_context = cc_common.create_linking_context(
+        libraries_to_link = [library_to_link],
+    )
+    (compilation_context, compilation_outputs) = cc_common.compile(
+        actions = ctx.actions,
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        public_hdrs = ctx.files.hdrs,
+        name = ctx.label.name,
+    )
+    return [CcInfo(
+        compilation_context = compilation_context,
+        linking_context = linking_context,
+    )]
+
+cc_import = rule(
+    implementation = _cc_import_impl,
+    attrs = {
+        "hdrs": attr.label_list(allow_files = [".h"]),
+        "static_library": attr.label(allow_single_file = [".a", ".lib", ".pic.a"]),
+        "shared_library": attr.label(allow_single_file = True),
+        "interface_library": attr.label(
+            allow_single_file = [".ifso", ".tbd", ".lib", ".so", ".dylib"],
+        ),
+        "system_provided": attr.bool(default = False),
+        "alwayslink": attr.bool(default = False),
+        "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
+    },
+    toolchains = ["@rules_cc//cc:toolchain_type"],  # copybara-use-repo-external-label
+    fragments = ["cpp"],
+)