Add Apple framework support to CcCompilationContext

RELNOTES: None
PiperOrigin-RevId: 253320981
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
index 5fda430..289690f 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
@@ -74,6 +74,7 @@
       SkylarkList<String> includes,
       SkylarkList<String> quoteIncludes,
       SkylarkList<String> systemIncludes,
+      SkylarkList<String> frameworkIncludes,
       SkylarkList<String> defines,
       SkylarkList<String> userCompileFlags,
       SkylarkList<CcCompilationContext> ccCompilationContexts,
@@ -93,6 +94,7 @@
         includes,
         quoteIncludes,
         systemIncludes,
+        frameworkIncludes,
         defines,
         userCompileFlags,
         ccCompilationContexts,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
index ade3695..0bfdeb4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
@@ -154,6 +154,17 @@
   }
 
   @Override
+  public SkylarkNestedSet getSkylarkFrameworkIncludeDirs() {
+    return SkylarkNestedSet.of(
+        String.class,
+        NestedSetBuilder.wrap(
+            Order.STABLE_ORDER,
+            getFrameworkIncludeDirs().stream()
+                .map(PathFragment::getSafePathString)
+                .collect(ImmutableList.toImmutableList())));
+  }
+
+  @Override
   public SkylarkNestedSet getSkylarkIncludeDirs() {
     return SkylarkNestedSet.of(
         String.class,
@@ -231,6 +242,16 @@
   }
 
   /**
+   * Returns the immutable list of include directories to be added with "-F" (possibly empty but
+   * never null). This includes the include dirs from the transitive deps closure of the target.
+   * This list does not contain duplicates. All fragments are either absolute or relative to the
+   * exec root (see {@link com.google.devtools.build.lib.analysis.BlazeDirectories#getExecRoot}).
+   */
+  public ImmutableList<PathFragment> getFrameworkIncludeDirs() {
+    return commandLineCcCompilationContext.frameworkIncludeDirs;
+  }
+
+  /**
    * Returns the immutable set of declared include directories, relative to a "-I" or "-iquote"
    * directory" (possibly empty but never null).
    */
@@ -458,16 +479,19 @@
     private final ImmutableList<PathFragment> includeDirs;
     private final ImmutableList<PathFragment> quoteIncludeDirs;
     private final ImmutableList<PathFragment> systemIncludeDirs;
+    private final ImmutableList<PathFragment> frameworkIncludeDirs;
     private final ImmutableList<String> defines;
 
     CommandLineCcCompilationContext(
         ImmutableList<PathFragment> includeDirs,
         ImmutableList<PathFragment> quoteIncludeDirs,
         ImmutableList<PathFragment> systemIncludeDirs,
+        ImmutableList<PathFragment> frameworkIncludeDirs,
         ImmutableList<String> defines) {
       this.includeDirs = includeDirs;
       this.quoteIncludeDirs = quoteIncludeDirs;
       this.systemIncludeDirs = systemIncludeDirs;
+      this.frameworkIncludeDirs = frameworkIncludeDirs;
       this.defines = defines;
     }
   }
@@ -487,6 +511,7 @@
     private final Set<PathFragment> includeDirs = new LinkedHashSet<>();
     private final Set<PathFragment> quoteIncludeDirs = new LinkedHashSet<>();
     private final Set<PathFragment> systemIncludeDirs = new LinkedHashSet<>();
+    private final Set<PathFragment> frameworkIncludeDirs = new LinkedHashSet<>();
     private final NestedSetBuilder<PathFragment> declaredIncludeDirs =
         NestedSetBuilder.stableOrder();
     private final NestedSetBuilder<Artifact> declaredIncludeSrcs =
@@ -554,6 +579,7 @@
       includeDirs.addAll(otherCcCompilationContext.getIncludeDirs());
       quoteIncludeDirs.addAll(otherCcCompilationContext.getQuoteIncludeDirs());
       systemIncludeDirs.addAll(otherCcCompilationContext.getSystemIncludeDirs());
+      frameworkIncludeDirs.addAll(otherCcCompilationContext.getFrameworkIncludeDirs());
       declaredIncludeDirs.addTransitive(otherCcCompilationContext.getDeclaredIncludeDirs());
       declaredIncludeSrcs.addTransitive(otherCcCompilationContext.getDeclaredIncludeSrcs());
       transitiveHeaderInfo.addTransitive(otherCcCompilationContext.transitiveHeaderInfos);
@@ -636,6 +662,12 @@
       return this;
     }
 
+    /** Add framewrok include directories to be added with "-F". */
+    public Builder addFrameworkIncludeDirs(Iterable<PathFragment> frameworkIncludeDirs) {
+      Iterables.addAll(this.frameworkIncludeDirs, frameworkIncludeDirs);
+      return this;
+    }
+
     /** Add a single declared include dir, relative to a "-I" or "-iquote" directory". */
     public Builder addDeclaredIncludeDir(PathFragment dir) {
       declaredIncludeDirs.add(dir);
@@ -773,6 +805,7 @@
               ImmutableList.copyOf(includeDirs),
               ImmutableList.copyOf(quoteIncludeDirs),
               ImmutableList.copyOf(systemIncludeDirs),
+              ImmutableList.copyOf(frameworkIncludeDirs),
               ImmutableList.copyOf(defines)),
           // TODO(b/110873917): We don't have the middle man compilation prerequisite, therefore, we
           // use the compilation prerequisites as they were passed to the builder, i.e. we use every
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
index dda017a..c8fc2b5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
@@ -248,6 +248,7 @@
   private final List<PathFragment> systemIncludeDirs = new ArrayList<>();
   private final List<PathFragment> quoteIncludeDirs = new ArrayList<>();
   private final List<PathFragment> includeDirs = new ArrayList<>();
+  private final List<PathFragment> frameworkIncludeDirs = new ArrayList<>();
 
   private HeadersCheckingMode headersCheckingMode = HeadersCheckingMode.LOOSE;
   private boolean fake;
@@ -586,6 +587,15 @@
     return this;
   }
 
+  /**
+   * Adds the given directories to the framework include directories (they are passed with {@code
+   * "-F"} to the compiler); these are also passed to dependent rules.
+   */
+  public CcCompilationHelper addFrameworkIncludeDirs(Iterable<PathFragment> frameworkIncludeDirs) {
+    Iterables.addAll(this.frameworkIncludeDirs, frameworkIncludeDirs);
+    return this;
+  }
+
   /** Adds a variableExtension to template the crosstool. */
   public CcCompilationHelper addVariableExtension(VariablesExtension variableExtension) {
     Preconditions.checkNotNull(variableExtension);
@@ -901,6 +911,7 @@
         configuration.getBinFragment().getRelative(repositoryPath));
 
     ccCompilationContextBuilder.addSystemIncludeDirs(systemIncludeDirs);
+    ccCompilationContextBuilder.addFrameworkIncludeDirs(frameworkIncludeDirs);
 
     for (PathFragment includeDir : includeDirs) {
       ccCompilationContextBuilder.addIncludeDir(includeDir);
@@ -1472,6 +1483,7 @@
         ccCompilationContext.getIncludeDirs(),
         ccCompilationContext.getQuoteIncludeDirs(),
         ccCompilationContext.getSystemIncludeDirs(),
+        ccCompilationContext.getFrameworkIncludeDirs(),
         ccCompilationContext.getDefines());
   }
 
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 ba16c83..321569f 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
@@ -218,6 +218,7 @@
       Object includeDirs,
       Object quoteIncludeDirs,
       Object systemIncludeDirs,
+      Object frameworkIncludeDirs,
       Object defines,
       boolean usePic,
       boolean addLegacyCxxOptions)
@@ -248,6 +249,7 @@
         asStringNestedSet(includeDirs),
         asStringNestedSet(quoteIncludeDirs),
         asStringNestedSet(systemIncludeDirs),
+        asStringNestedSet(frameworkIncludeDirs),
         asStringNestedSet(defines));
   }
 
@@ -501,7 +503,12 @@
 
   @Override
   public CcCompilationContext createCcCompilationContext(
-      Object headers, Object systemIncludes, Object includes, Object quoteIncludes, Object defines)
+      Object headers,
+      Object systemIncludes,
+      Object includes,
+      Object quoteIncludes,
+      Object frameworkIncludes,
+      Object defines)
       throws EvalException {
     CcCompilationContext.Builder ccCompilationContext =
         CcCompilationContext.builder(
@@ -521,6 +528,13 @@
         toNestedSetOfStrings(quoteIncludes, "quote_includes").getSet(String.class).toList().stream()
             .map(x -> PathFragment.create(x))
             .collect(ImmutableList.toImmutableList()));
+    ccCompilationContext.addFrameworkIncludeDirs(
+        toNestedSetOfStrings(frameworkIncludes, "framework_includes")
+            .getSet(String.class)
+            .toList()
+            .stream()
+            .map(x -> PathFragment.create(x))
+            .collect(ImmutableList.toImmutableList()));
     ccCompilationContext.addDefines(toNestedSetOfStrings(defines, "defines").getSet(String.class));
     return ccCompilationContext.build();
   }
@@ -1491,6 +1505,7 @@
       SkylarkList<String> includes,
       SkylarkList<String> quoteIncludes,
       SkylarkList<String> systemIncludes,
+      SkylarkList<String> frameworkIncludes,
       SkylarkList<String> defines,
       SkylarkList<String> userCompileFlags,
       SkylarkList<CcCompilationContext> ccCompilationContexts,
@@ -1559,6 +1574,10 @@
                 systemIncludes.stream()
                     .map(PathFragment::create)
                     .collect(ImmutableList.toImmutableList()))
+            .addFrameworkIncludeDirs(
+                frameworkIncludes.stream()
+                    .map(PathFragment::create)
+                    .collect(ImmutableList.toImmutableList()))
             .addDefines(defines)
             .setCopts(userCompileFlags)
             .addAdditionalCompilationInputs(headersForClifDoNotUseThisParam)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
index 41e246e..ad1ee48 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CompileBuildVariables.java
@@ -53,15 +53,21 @@
   /**
    * Variable for the collection of quote include paths.
    *
-   * @see CcCompilationContext#getIncludeDirs().
+   * @see CcCompilationContext#getQuoteIncludeDirs().
    */
   QUOTE_INCLUDE_PATHS("quote_include_paths"),
   /**
    * Variable for the collection of system include paths.
    *
-   * @see CcCompilationContext#getIncludeDirs().
+   * @see CcCompilationContext#getSystemIncludeDirs().
    */
   SYSTEM_INCLUDE_PATHS("system_include_paths"),
+  /**
+   * Variable for the collection of framework include paths.
+   *
+   * @see CcCompilationContext#getFrameworkIncludeDirs().
+   */
+  FRAMEWORK_PATHS("framework_include_paths"),
   /** Variable for the module map file name. */
   MODULE_MAP_FILE("module_map_file"),
   /** Variable for the dependent module map file name. */
@@ -126,6 +132,7 @@
       Iterable<PathFragment> includeDirs,
       Iterable<PathFragment> quoteIncludeDirs,
       Iterable<PathFragment> systemIncludeDirs,
+      Iterable<PathFragment> frameworkIncludeDirs,
       Iterable<String> defines) {
     try {
       return setupVariablesOrThrowEvalException(
@@ -152,6 +159,7 @@
           getSafePathStrings(includeDirs),
           getSafePathStrings(quoteIncludeDirs),
           getSafePathStrings(systemIncludeDirs),
+          getSafePathStrings(frameworkIncludeDirs),
           defines);
     } catch (EvalException e) {
       ruleErrorConsumer.ruleError(e.getMessage());
@@ -183,12 +191,14 @@
       Iterable<String> includeDirs,
       Iterable<String> quoteIncludeDirs,
       Iterable<String> systemIncludeDirs,
+      Iterable<String> frameworkIncludeDirs,
       Iterable<String> defines)
       throws EvalException {
     Preconditions.checkNotNull(directModuleMaps);
     Preconditions.checkNotNull(includeDirs);
     Preconditions.checkNotNull(quoteIncludeDirs);
     Preconditions.checkNotNull(systemIncludeDirs);
+    Preconditions.checkNotNull(frameworkIncludeDirs);
     Preconditions.checkNotNull(defines);
     CcToolchainVariables.Builder buildVariables =
         CcToolchainVariables.builder(
@@ -239,6 +249,9 @@
       buildVariables.addStringSequenceVariable(INCLUDES.getVariableName(), includes);
     }
 
+    buildVariables.addStringSequenceVariable(
+        FRAMEWORK_PATHS.getVariableName(), frameworkIncludeDirs);
+
     Iterable<String> allDefines;
     if (fdoStamp != null) {
       // Stamp FDO builds with FDO subtype string
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
index 79a98a6..cd8a3ff 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java
@@ -256,6 +256,10 @@
                         "      flag: '-isystem'",
                         "      flag: '%{system_include_paths}'",
                         "    }",
+                        "    flag_group {",
+                        "      iterate_over: 'framework_include_paths'",
+                        "      flag: '-F%{framework_include_paths}'",
+                        "    }",
                         "  }")));
       }
       if (!existingFeatureNames.contains(CppRuleClasses.FDO_INSTRUMENT)) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
index 1b9ec5f..86c6808 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkstampCompileHelper.java
@@ -178,6 +178,7 @@
         /* includeDirs= */ ImmutableList.of(PathFragment.create(".")),
         /* quoteIncludeDirs= */ ImmutableList.of(),
         /* systemIncludeDirs= */ ImmutableList.of(),
+        /* frameworkIncludeDirs= */ ImmutableList.of(),
         computeAllLinkstampDefines(
             labelReplacement,
             outputReplacement,
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
index a568fdc..6ccac29 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
@@ -144,6 +144,17 @@
             defaultValue = "[]",
             type = SkylarkList.class),
         @Param(
+            name = "framework_includes",
+            doc =
+                "Search paths for header files from Apple frameworks. They can be either relative "
+                    + "to the exec root or absolute. Usually passed with -F. Propagated to "
+                    + "dependents transitively.",
+            positional = false,
+            named = true,
+            noneable = true,
+            defaultValue = "[]",
+            type = SkylarkList.class),
+        @Param(
             name = "defines",
             doc = "Set of defines needed to compile this target. Each define is a string.",
             positional = false,
@@ -198,6 +209,7 @@
       SkylarkList<String> includes,
       SkylarkList<String> quoteIncludes,
       SkylarkList<String> systemIncludes,
+      SkylarkList<String> frameworkIncludes,
       SkylarkList<String> defines,
       SkylarkList<String> userCompileFlags,
       SkylarkList<CompilationContextT> ccCompilationContexts,
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcCompilationContextApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcCompilationContextApi.java
index eb05776..80115c8 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcCompilationContextApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/CcCompilationContextApi.java
@@ -44,26 +44,34 @@
   @SkylarkCallable(
       name = "system_includes",
       doc =
-          "Returns the set of search paths for header files referenced by angle brackets, e.g. "
-              + "#include <foo/bar/header.h>. They can be either relative to the exec root "
-              + "or absolute. Usually passed with -isystem.",
+          "Returns the set of search paths (as strings) for header files referenced by angle"
+              + " brackets, e.g. #include <foo/bar/header.h>. They can be either relative to the"
+              + " exec root or absolute. Usually passed with -isystem.",
       structField = true)
   SkylarkNestedSet getSkylarkSystemIncludeDirs();
 
   @SkylarkCallable(
+      name = "framework_includes",
+      doc =
+          "Returns the set of search paths (as strings) for framework header files. Usually passed"
+              + " with -F.",
+      structField = true)
+  SkylarkNestedSet getSkylarkFrameworkIncludeDirs();
+
+  @SkylarkCallable(
       name = "includes",
       doc =
-          "Returns the set of search paths for header files referenced both by angle bracket and "
-              + "quotes Usually passed with -I.",
+          "Returns the set of search paths (as strings) for header files referenced both by angle"
+              + " bracket and quotes. Usually passed with -I.",
       structField = true)
   SkylarkNestedSet getSkylarkIncludeDirs();
 
   @SkylarkCallable(
       name = "quote_includes",
       doc =
-          "Returns the set of search paths for header files referenced by quotes, e.g. "
-              + "#include \"foo/bar/header.h\". They can be either relative to the exec "
-              + "root or absolute. Usually passed with -iquote.",
+          "Returns the set of search paths (as strings) for header files referenced by quotes,"
+              + " e.g. #include \"foo/bar/header.h\". They can be either relative to the exec root"
+              + " or absolute. Usually passed with -iquote.",
       structField = true)
   SkylarkNestedSet getSkylarkQuoteIncludeDirs();
 }
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 4a15796..41bddc3 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
@@ -339,6 +339,17 @@
               @ParamType(type = SkylarkNestedSet.class)
             }),
         @Param(
+            name = "framework_include_directories",
+            doc = "Depset of framework include directories.",
+            positional = false,
+            named = true,
+            defaultValue = "None",
+            noneable = true,
+            allowedTypes = {
+              @ParamType(type = NoneType.class),
+              @ParamType(type = SkylarkNestedSet.class)
+            }),
+        @Param(
             name = "preprocessor_defines",
             doc = "Depset of preprocessor defines.",
             positional = false,
@@ -372,6 +383,7 @@
       Object includeDirs,
       Object quoteIncludeDirs,
       Object systemIncludeDirs,
+      Object frameworkIncludeDirs,
       Object defines,
       boolean usePic,
       boolean addLegacyCxxOptions)
@@ -681,6 +693,13 @@
             defaultValue = "unbound",
             type = Object.class),
         @Param(
+            name = "framework_includes",
+            doc = "Set of framework search paths for header files (Apple platform only)",
+            positional = false,
+            named = true,
+            defaultValue = "unbound",
+            type = Object.class),
+        @Param(
             name = "defines",
             doc = "Set of defines needed to compile this target. Each define is a string",
             positional = false,
@@ -689,7 +708,12 @@
             type = Object.class)
       })
   CompilationContextT createCcCompilationContext(
-      Object headers, Object systemIncludes, Object includes, Object quoteIncludes, Object defines)
+      Object headers,
+      Object systemIncludes,
+      Object includes,
+      Object quoteIncludes,
+      Object frameworkIncludes,
+      Object defines)
       throws EvalException;
 
   // TODO(b/65151735): Remove when cc_flags is entirely set from features.
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 24390b5..c37b315 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
@@ -107,10 +107,19 @@
 
   @Override
   public CcToolchainVariablesApi getCompileBuildVariables(
-      CcToolchainProviderApi ccToolchainProvider, FeatureConfigurationApi featureConfiguration,
-      Object sourceFile, Object outputFile, Object userCompileFlags, Object includeDirs,
-      Object quoteIncludeDirs, Object systemIncludeDirs, Object defines, boolean usePic,
-      boolean addLegacyCxxOptions) throws EvalException {
+      CcToolchainProviderApi ccToolchainProvider,
+      FeatureConfigurationApi featureConfiguration,
+      Object sourceFile,
+      Object outputFile,
+      Object userCompileFlags,
+      Object includeDirs,
+      Object quoteIncludeDirs,
+      Object systemIncludeDirs,
+      Object frameworkIncludeDirs,
+      Object defines,
+      boolean usePic,
+      boolean addLegacyCxxOptions)
+      throws EvalException {
     return null;
   }
 
@@ -161,7 +170,12 @@
 
   @Override
   public CcCompilationContextApi createCcCompilationContext(
-      Object headers, Object systemIncludes, Object includes, Object quoteIncludes, Object defines)
+      Object headers,
+      Object systemIncludes,
+      Object includes,
+      Object quoteIncludes,
+      Object frameworkIncludes,
+      Object defines)
       throws EvalException {
     return null;
   }
@@ -188,6 +202,7 @@
       SkylarkList<String> quoteIncludes,
       SkylarkList<String> defines,
       SkylarkList<String> systemIncludes,
+      SkylarkList<String> frameworkIncludes,
       SkylarkList<String> userCompileFlags,
       SkylarkList<CcCompilationContextApi> ccCompilationContexts,
       String name,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
index ae24710..0e68213 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
@@ -704,6 +704,19 @@
   }
 
   @Test
+  public void testCompileBuildVariablesForFrameworkIncludes() throws Exception {
+    assertThat(
+            commandLineForVariables(
+                CppActionNames.CPP_COMPILE,
+                "cc_common.create_compile_variables(",
+                "feature_configuration = feature_configuration,",
+                "cc_toolchain = toolchain,",
+                "framework_include_directories = depset(['foo/bar'])",
+                ")"))
+        .contains("-Ffoo/bar");
+  }
+
+  @Test
   public void testCompileBuildVariablesForDefines() throws Exception {
     assertThat(
             commandLineForVariables(
@@ -1068,6 +1081,7 @@
         "    system_includes=depset([ctx.attr._system_include]),",
         "    includes=depset([ctx.attr._include]),",
         "    quote_includes=depset([ctx.attr._quote_include]),",
+        "    framework_includes=depset([ctx.attr._framework_include]),",
         "    defines=depset([ctx.attr._define]))",
         "  cc_infos = [CcInfo(compilation_context=compilation_context)]",
         "  for dep in ctx.attr._deps:",
@@ -1080,6 +1094,8 @@
         "          merged_system_includes=merged_cc_info.compilation_context.system_includes,",
         "          merged_includes=merged_cc_info.compilation_context.includes,",
         "          merged_quote_includes=merged_cc_info.compilation_context.quote_includes,",
+        "         "
+            + " merged_framework_includes=merged_cc_info.compilation_context.framework_includes,",
         "          merged_defines=merged_cc_info.compilation_context.defines",
         "      )]",
         "crule = rule(",
@@ -1090,6 +1106,7 @@
         "    '_system_include': attr.string(default='foo/bar'),",
         "    '_include': attr.string(default='baz/qux'),",
         "    '_quote_include': attr.string(default='quux/abc'),",
+        "    '_framework_include': attr.string(default='fuux/fgh'),",
         "    '_define': attr.string(default='MYDEFINE'),",
         "    '_deps': attr.label_list(default=['//a:dep1', '//a:dep2'])",
         "  },",
@@ -1132,6 +1149,12 @@
     List<String> mergedQuoteIncludes =
         ((SkylarkNestedSet) myInfo.getValue("merged_quote_includes")).getSet(String.class).toList();
     assertThat(mergedQuoteIncludes).contains("quux/abc");
+
+    List<String> mergedFrameworkIncludes =
+        ((SkylarkNestedSet) myInfo.getValue("merged_framework_includes"))
+            .getSet(String.class)
+            .toList();
+    assertThat(mergedFrameworkIncludes).contains("fuux/fgh");
   }
 
   @Test
@@ -5165,6 +5188,17 @@
   }
 
   @Test
+  public void testFrameworkIncludeDirs() throws Exception {
+    createFilesForTestingCompilation(
+        scratch, "tools/build_defs/foo", "framework_includes=['foo/bar', 'baz/qux']");
+    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
+    assertThat(target).isNotNull();
+    CppCompileAction action =
+        (CppCompileAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), ".o"));
+    assertThat(action.getArguments()).containsAtLeast("-Ffoo/bar", "-Fbaz/qux").inOrder();
+  }
+
+  @Test
   public void testDefines() throws Exception {
     createFilesForTestingCompilation(
         scratch, "tools/build_defs/foo", "defines=['DEFINE1', 'DEFINE2']");