Add **Starlark** implementation of common.copts method.

Add implementation of several helper methods in cc_helper.

This change makes cc_internal's init_make_variables method obsolete, since native implementation of common.copts was implicitly setting certain make variables, it is now done in Starlark.

Also makes **native** common.copts method obsolete.
Delete common.copts and cc_internal.init_make_variables.

PiperOrigin-RevId: 440175031
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
index 00194b0..5d7adc3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCommon.java
@@ -204,6 +204,11 @@
     return mergedOutputGroups;
   }
 
+  @StarlarkMethod(name = "copts", structField = true, documented = false)
+  public Sequence<String> getCoptsForStarlark() {
+    return StarlarkList.immutableCopyOf(getCopts());
+  }
+
   @StarlarkMethod(name = "linkopts", structField = true, documented = false)
   public Sequence<String> getLinkoptsForStarlark() {
     return StarlarkList.immutableCopyOf(getLinkopts());
@@ -225,11 +230,6 @@
     return ImmutableList.copyOf(result);
   }
 
-  @StarlarkMethod(name = "copts", structField = true, documented = false)
-  public Sequence<String> getCoptsForStarlark() {
-    return StarlarkList.immutableCopyOf(getCopts());
-  }
-
   public ImmutableList<String> getCopts() {
     if (!getCoptsFilter(ruleContext).passesFilter("-Wno-future-warnings")) {
       ruleContext.attributeWarning(
@@ -248,6 +248,14 @@
         .build();
   }
 
+  // TODO(gnish): Delete this method once package default copts are gone.
+  // All of the package default copts will be included in rule attribute
+  // after the migration.
+  @StarlarkMethod(name = "unexpanded_package_copts", structField = true, documented = false)
+  public Sequence<String> getUnexpandedPackageCoptsForStarlark() {
+    return StarlarkList.immutableCopyOf(ruleContext.getRule().getPackage().getDefaultCopts());
+  }
+
   private boolean hasAttribute(String name, Type<?> type) {
     return ruleContext.attributes().has(name, type);
   }
@@ -1174,6 +1182,19 @@
     return sysrootFlag;
   }
 
+  @StarlarkMethod(
+      name = "compute_cc_flags_from_feature_config",
+      documented = false,
+      parameters = {
+        @Param(name = "ctx", positional = false, named = true),
+        @Param(name = "cc_toolchain", positional = false, named = true)
+      })
+  public List<String> computeCcFlagsFromFeatureConfigForStarlark(
+      StarlarkRuleContext starlarkRuleContext, CcToolchainProvider ccToolchain)
+      throws RuleErrorException {
+    return computeCcFlagsFromFeatureConfig(starlarkRuleContext.getRuleContext(), ccToolchain);
+  }
+
   private static List<String> computeCcFlagsFromFeatureConfig(
       RuleContext ruleContext, CcToolchainProvider toolchainProvider) throws RuleErrorException {
     FeatureConfiguration featureConfiguration = null;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
index cf27b88..0608db6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
@@ -15,12 +15,9 @@
 package com.google.devtools.build.lib.rules.cpp;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.docgen.annot.DocCategory;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
-import com.google.devtools.build.lib.analysis.MakeVariableSupplier.MapBackedMakeVariableSupplier;
-import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkActionFactory;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -29,7 +26,6 @@
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.rules.cpp.CcBinary.CcLauncherInfo;
-import com.google.devtools.build.lib.rules.cpp.CcCommon.CcFlagsSupplier;
 import com.google.devtools.build.lib.rules.cpp.CcLinkingContext.Linkstamp;
 import com.google.devtools.build.lib.starlarkbuildapi.FileApi;
 import com.google.devtools.build.lib.starlarkbuildapi.NativeComputedDefaultApi;
@@ -92,23 +88,6 @@
   }
 
   @StarlarkMethod(
-      name = "init_make_variables",
-      documented = false,
-      parameters = {
-        @Param(name = "ctx", positional = false, named = true),
-        @Param(name = "cc_toolchain", positional = false, named = true),
-      })
-  public void initMakeVariables(
-      StarlarkRuleContext starlarkRuleContext, CcToolchainProvider ccToolchain) {
-    ImmutableMap.Builder<String, String> toolchainMakeVariables = ImmutableMap.builder();
-    ccToolchain.addGlobalMakeVariables(toolchainMakeVariables);
-    RuleContext ruleContext = starlarkRuleContext.getRuleContext();
-    ruleContext.initConfigurationMakeVariableContext(
-        new MapBackedMakeVariableSupplier(toolchainMakeVariables.buildOrThrow()),
-        new CcFlagsSupplier(starlarkRuleContext.getRuleContext()));
-  }
-
-  @StarlarkMethod(
       name = "get_build_info",
       documented = false,
       parameters = {@Param(name = "ctx")})
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
index e60daa9..b2fc616 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProvider.java
@@ -843,6 +843,13 @@
     return legacyCcFlagsMakeVariable;
   }
 
+  @Override
+  public String getLegacyCcFlagsMakeVariableForStarlark(StarlarkThread thread)
+      throws EvalException {
+    CcModule.checkPrivateStarlarkificationAllowlist(thread);
+    return legacyCcFlagsMakeVariable;
+  }
+
   public FdoContext getFdoContext() {
     return fdoContext;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcToolchainProviderApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcToolchainProviderApi.java
index 5e7b374..1a15ba2 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcToolchainProviderApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcToolchainProviderApi.java
@@ -257,4 +257,10 @@
       })
   String getArtifactNameForCategory(String category, String outputName, StarlarkThread thread)
       throws EvalException;
+
+  @StarlarkMethod(
+      name = "legacy_cc_flags_make_variable",
+      documented = false,
+      useStarlarkThread = true)
+  String getLegacyCcFlagsMakeVariableForStarlark(StarlarkThread thread) throws EvalException;
 }
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
index 8087bb6..e74e74a 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
@@ -682,12 +682,16 @@
         compilation_context_deps.append(malloc_dep)
     if ctx.attr._stl != None:
         compilation_context_deps.append(ctx.attr._stl[CcInfo].compilation_context)
+
+    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, common, cc_toolchain))
+
     (compilation_context, compilation_outputs) = cc_common.compile(
         name = ctx.label.name,
         actions = ctx.actions,
         feature_configuration = feature_configuration,
         cc_toolchain = cc_toolchain,
-        user_compile_flags = common.copts,
+        user_compile_flags = cc_helper.get_copts(ctx, common, feature_configuration, additional_make_variable_substitutions),
         defines = common.defines,
         local_defines = common.local_defines,
         loose_includes = common.loose_include_dirs,
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
index cbd3afc..e492d8c 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
@@ -41,6 +41,8 @@
     CLIF_OUTPUT_PROTO = "CLIF_OUTPUT_PROTO",
 )
 
+SYSROOT_FLAG = "--sysroot="
+
 def _check_src_extension(file, allowed_src_files, allow_versioned_shared_libraries):
     extension = "." + file.extension
     if _matches_extension(extension, allowed_src_files) or (allow_versioned_shared_libraries and _is_versioned_shared_library_extension_valid(file.path)):
@@ -571,6 +573,244 @@
     transitive_libraries = [dep[CcInfo].transitive_native_libraries() for dep in deps if CcInfo in dep]
     return CcNativeLibraryInfo(libraries_to_link = depset(direct = libraries, transitive = transitive_libraries))
 
+def _get_toolchain_global_make_variables(cc_toolchain):
+    result = {
+        "CC": cc_toolchain.tool_path(tool = "GCC"),
+        "AR": cc_toolchain.tool_path(tool = "AR"),
+        "NM": cc_toolchain.tool_path(tool = "NM"),
+        "LD": cc_toolchain.tool_path(tool = "LD"),
+        "STRIP": cc_toolchain.tool_path(tool = "STRIP"),
+        "C_COMPILER": cc_toolchain.compiler,
+    }
+    obj_copy_tool = cc_toolchain.tool_path(tool = "OBJCOPY")
+    if obj_copy_tool != None:
+        # objcopy is optional in Crostool.
+        result["OBJCOPY"] = obj_copy_tool
+    gcov_tool = cc_toolchain.tool_path(tool = "GCOVTOOL")
+    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
+
+    return result
+
+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 _lookup_var(ctx, additional_vars, var):
+    expanded_make_var_ctx = ctx.var.get(var)
+    expanded_make_var_additional = additional_vars.get(var)
+    if expanded_make_var_additional != None:
+        return expanded_make_var_additional
+    if expanded_make_var_ctx != None:
+        return expanded_make_var_ctx
+    fail("{}: {} not defined".format(ctx.label, "$(" + var + ")"))
+
+def _get_cc_flags_make_variable(ctx, common, 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
+    feature_config_cc_flags = common.compute_cc_flags_from_feature_config(ctx = ctx, cc_toolchain = cc_toolchain)
+    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 _expand_nested_variable(ctx, additional_vars, exp):
+    # If make variable is predefined path variable(like $(location ...))
+    # we will expand it first.
+    if exp.find(" ") != -1:
+        return ctx.expand_location("$({})".format(exp))
+
+    # Recursively expand nested make variables, but since there is no recursion
+    # in Starlark we will do it via for loop.
+    unbounded_recursion = True
+
+    # The only way to check if the unbounded recursion is happening or not
+    # is to have a look at the depth of the recursion.
+    # 10 seems to be a reasonable number, since it is highly unexpected
+    # to have nested make variables which are expanding more than 10 times.
+    for _ in range(10):
+        exp = _lookup_var(ctx, additional_vars, exp)
+        if len(exp) >= 3 and exp[0] == "$" and exp[1] == "(" and exp[len(exp) - 1] == ")":
+            # Try to expand once more.
+            exp = exp[2:len(exp) - 1]
+            continue
+        unbounded_recursion = False
+        break
+
+    if unbounded_recursion:
+        fail("potentially unbounded recursion during expansion of {}".format(exp))
+    return exp
+
+def _expand(ctx, expression, additional_make_variable_substitutions):
+    idx = 0
+    last_make_var_end = 0
+    result = []
+    n = len(expression)
+    for _ in range(n):
+        if idx >= n:
+            break
+        if expression[idx] != "$":
+            idx += 1
+            continue
+
+        idx += 1
+
+        # We've met $$ pattern, so $ is escaped.
+        if idx < n and expression[idx] == "$":
+            idx += 1
+            result.append(expression[last_make_var_end:idx - 1])
+            last_make_var_end = idx
+            # We might have found a potential start for Make Variable.
+
+        elif idx < n and expression[idx] == "(":
+            # Try to find the closing parentheses.
+            make_var_start = idx
+            make_var_end = make_var_start
+            for j in range(idx + 1, n):
+                if expression[j] == ")":
+                    make_var_end = j
+                    break
+
+            # Note we cannot go out of string's bounds here,
+            # because of this check.
+            # If start of the variable is different from the end,
+            # we found a make variable.
+            if make_var_start != make_var_end:
+                # Some clarifications:
+                # *****$(MAKE_VAR_1)*******$(MAKE_VAR_2)*****
+                #                   ^       ^          ^
+                #                   |       |          |
+                #   last_make_var_end  make_var_start make_var_end
+                result.append(expression[last_make_var_end:make_var_start - 1])
+                make_var = expression[make_var_start + 1:make_var_end]
+                exp = _expand_nested_variable(ctx, additional_make_variable_substitutions, make_var)
+                result.append(exp)
+
+                # Update indexes.
+                idx = make_var_end + 1
+                last_make_var_end = idx
+
+    # Add the last substring which would be skipped by for loop.
+    if last_make_var_end < n:
+        result.append(expression[last_make_var_end:n])
+
+    return "".join(result)
+
+# Implementation of Bourne shell tokenization.
+# Tokenizes str and appends result to the options list.
+def _tokenize(options, options_string):
+    token = []
+    force_token = False
+    quotation = "\0"
+    length = len(options_string)
+
+    # Since it is impossible to modify loop variable inside loop
+    # in Starlark, and also there is no while loop, I have to
+    # use this ugly hack.
+    i = -1
+    for _ in range(length):
+        i += 1
+        if i >= length:
+            break
+        c = options_string[i]
+        if quotation != "\0":
+            # In quotation.
+            if c == quotation:
+                # End quotation.
+                quotation = "\0"
+            elif c == "\\" and quotation == "\"":
+                i += 1
+                if i == length:
+                    fail("backslash at the end of the string: {}".format(options_string))
+                c = options_string[i]
+                if c != "\\" and c != "\"":
+                    token.append("\\")
+                token.append(c)
+            else:
+                # Regular char, in quotation.
+                token.append(c)
+        else:
+            # Not in quotation.
+            if c == "'" or c == "\"":
+                # Begin single double quotation.
+                quotation = c
+                force_token = True
+            elif c == " " or c == "\t":
+                # Space not quoted.
+                if force_token or len(token) > 0:
+                    options.append("".join(token))
+                    token = []
+                    force_token = False
+            elif c == "\\":
+                # Backslash not quoted.
+                i += 1
+                if i == length:
+                    fail("backslash at the end of the string: {}".format(options_string))
+                token.append(options_string[i])
+            else:
+                # Regular char, not quoted.
+                token.append(c)
+    if quotation != "\0":
+        fail("unterminated quotation at the end of the string: {}".format(options_string))
+
+    if force_token or len(token) > 0:
+        options.append("".join(token))
+
+# Tries to expand a single make variable from token.
+# If token has additional characters other than ones
+# corresponding to make variable returns None.
+def _expand_single_make_variable(ctx, token, additional_make_variable_substitutions):
+    if len(token) < 3:
+        return None
+    if token[0] != "$" or token[1] != "(" or token[len(token) - 1] != ")":
+        return None
+    unexpanded_var = token[2:len(token) - 1]
+    expanded_var = _expand_nested_variable(ctx, additional_make_variable_substitutions, unexpanded_var)
+    return expanded_var
+
+def _expand_make_variables_for_copts(ctx, tokenization, unexpanded_tokens, additional_make_variable_substitutions):
+    tokens = []
+    for token in unexpanded_tokens:
+        if tokenization:
+            expanded_token = _expand(ctx, token, additional_make_variable_substitutions)
+            _tokenize(tokens, expanded_token)
+        else:
+            exp = _expand_single_make_variable(ctx, token, additional_make_variable_substitutions)
+            if exp != None:
+                _tokenize(tokens, exp)
+            else:
+                tokens.append(_expand(ctx, token, additional_make_variable_substitutions))
+    return tokens
+
+def _get_copts(ctx, common, feature_configuration, additional_make_variable_substitutions):
+    if not hasattr(ctx.attr, "copts"):
+        fail("could not find rule attribute named: 'copts'")
+    package_copts = common.unexpanded_package_copts
+    attribute_copts = ctx.attr.copts
+    tokenization = not (cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "no_copts_tokenization") or "no_copts_tokenization" in ctx.features)
+    expanded_package_copts = _expand_make_variables_for_copts(ctx, tokenization, package_copts, additional_make_variable_substitutions)
+    expanded_attribute_copts = _expand_make_variables_for_copts(ctx, tokenization, attribute_copts, additional_make_variable_substitutions)
+    return expanded_package_copts + expanded_attribute_copts
+
 cc_helper = struct(
     merge_cc_debug_contexts = _merge_cc_debug_contexts,
     is_code_coverage_enabled = _is_code_coverage_enabled,
@@ -603,4 +843,7 @@
     collect_compilation_prerequisites = _collect_compilation_prerequisites,
     collect_native_cc_libraries = _collect_native_cc_libraries,
     create_strip_action = _create_strip_action,
+    get_toolchain_global_make_variables = _get_toolchain_global_make_variables,
+    get_cc_flags_make_variable = _get_cc_flags_make_variable,
+    get_copts = _get_copts,
 )
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
index 06feb98..59dd8ab 100755
--- a/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
@@ -29,7 +29,6 @@
 
     cc_toolchain = common.toolchain
 
-    cc_internal.init_make_variables(ctx = ctx, cc_toolchain = cc_toolchain)
     feature_configuration = cc_common.configure_features(
         ctx = ctx,
         cc_toolchain = cc_toolchain,
@@ -54,12 +53,15 @@
     if not _is_stl(ctx.attr.tags) and ctx.attr._stl != None:
         interface_deps.append(ctx.attr._stl[CcInfo].compilation_context)
 
+    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, common, cc_toolchain))
+
     (compilation_context, srcs_compilation_outputs) = cc_common.compile(
         actions = ctx.actions,
         name = ctx.label.name,
         cc_toolchain = cc_toolchain,
         feature_configuration = feature_configuration,
-        user_compile_flags = common.copts,
+        user_compile_flags = cc_helper.get_copts(ctx, common, feature_configuration, additional_make_variable_substitutions),
         defines = common.defines,
         local_defines = common.local_defines,
         loose_includes = common.loose_include_dirs,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl b/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
index 649c2df..aa0e8e4 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
@@ -112,8 +112,11 @@
     disable_pbh = "disable_pbh",
     optional_cc_flags_feature = "optional_cc_flags_feature",
     cpp_compile_with_requirements = "cpp_compile_with_requirements",
+    no_copts_tokenization = "no_copts_tokenization",
 )
 
+_no_copts_tokenization_feature = feature(name = _FEATURE_NAMES.no_copts_tokenization)
+
 _disable_pbh_feature = feature(name = _FEATURE_NAMES.disable_pbh)
 
 _no_legacy_features_feature = feature(name = _FEATURE_NAMES.no_legacy_features)
@@ -1356,6 +1359,7 @@
     _FEATURE_NAMES.def_feature: _def_feature,
     _FEATURE_NAMES.strip_debug_symbols: _strip_debug_symbols_feature,
     _FEATURE_NAMES.disable_pbh: _disable_pbh_feature,
+    _FEATURE_NAMES.no_copts_tokenization: _no_copts_tokenization_feature,
     _FEATURE_NAMES.optional_cc_flags_feature: _optional_cc_flags_feature,
     _FEATURE_NAMES.cpp_compile_with_requirements: _cpp_compile_with_requirements,
     _FEATURE_NAMES.generate_pdb_file: _generate_pdb_file_feature,