Improve support for frameworks in gn

Add "frameworks" and "framework_dirs" variables to targets and configs
that are propagated using the following rules:

-   "frameworks" is propagated to all linkable targets (executables,
    shared libraries and complete static libraries)

-   "framework_dirs" is propagated to direct compile dependencies as
    "include_dirs" and also to all linkable targets (executables,
    shared libraries and complete static libraries)

The non-conventional propagation rules for "framework_dirs" is there
to support how framework are used on Apple's platforms (macOS & iOS).
A framework bundle is a way to ship a shared library with its public
headers, so the path to the framework needs to be propagated to the
compiler (to resolve import statement for the framework's header) and
to the linker (to find the path to the shared library contained in the
framework).

Add target of type CREATE_BUNDLE that generate a framework (identified
by the product type "com.apple.product-type.framework") as inherited
libraries since they contains shared library and add them to the list
of implicit dependencies to force the regeneration of linkable targets
if any of the framework API changes (this is bit pessimistic since it
can force an unnecessary rebuild if the linkable target does not link
with the framework, but this should be rare).

Bug: crbug.com/gn/119, crbug.com/1037607
Change-Id: Icef50c38ac02afab71f2348fe3f73b6124a29112
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6660
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 029f834..592e65f 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -433,9 +433,9 @@
         'src/gn/analyzer.cc',
         'src/gn/args.cc',
         'src/gn/binary_target_generator.cc',
+        'src/gn/build_settings.cc',
         'src/gn/builder.cc',
         'src/gn/builder_record.cc',
-        'src/gn/build_settings.cc',
         'src/gn/bundle_data.cc',
         'src/gn/bundle_data_target_generator.cc',
         'src/gn/bundle_file_rule.cc',
@@ -450,8 +450,8 @@
         'src/gn/command_format.cc',
         'src/gn/command_gen.cc',
         'src/gn/command_help.cc',
-        'src/gn/command_meta.cc',
         'src/gn/command_ls.cc',
+        'src/gn/command_meta.cc',
         'src/gn/command_path.cc',
         'src/gn/command_refs.cc',
         'src/gn/commands.cc',
@@ -469,6 +469,7 @@
         'src/gn/escape.cc',
         'src/gn/exec_process.cc',
         'src/gn/filesystem_utils.cc',
+        'src/gn/frameworks_utils.cc',
         'src/gn/function_exec_script.cc',
         'src/gn/function_foreach.cc',
         'src/gn/function_forward_variables_from.cc',
@@ -478,13 +479,13 @@
         'src/gn/function_process_file_template.cc',
         'src/gn/function_read_file.cc',
         'src/gn/function_rebase_path.cc',
-        'src/gn/functions.cc',
-        'src/gn/function_set_defaults.cc',
         'src/gn/function_set_default_toolchain.cc',
-        'src/gn/functions_target.cc',
+        'src/gn/function_set_defaults.cc',
         'src/gn/function_template.cc',
         'src/gn/function_toolchain.cc',
         'src/gn/function_write_file.cc',
+        'src/gn/functions.cc',
+        'src/gn/functions_target.cc',
         'src/gn/general_tool.cc',
         'src/gn/generated_file_target_generator.cc',
         'src/gn/group_target_generator.cc',
@@ -522,17 +523,17 @@
         'src/gn/output_conversion.cc',
         'src/gn/output_file.cc',
         'src/gn/parse_node_value_adapter.cc',
-        'src/gn/parser.cc',
         'src/gn/parse_tree.cc',
+        'src/gn/parser.cc',
         'src/gn/path_output.cc',
         'src/gn/pattern.cc',
         'src/gn/pool.cc',
         'src/gn/qt_creator_writer.cc',
         'src/gn/runtime_deps.cc',
         'src/gn/rust_substitution_type.cc',
-        'src/gn/rust_values_generator.cc',
         'src/gn/rust_tool.cc',
         'src/gn/rust_values.cc',
+        'src/gn/rust_values_generator.cc',
         'src/gn/rust_variables.cc',
         'src/gn/scheduler.cc',
         'src/gn/scope.cc',
@@ -590,6 +591,7 @@
         'src/gn/escape_unittest.cc',
         'src/gn/exec_process_unittest.cc',
         'src/gn/filesystem_utils_unittest.cc',
+        'src/gn/frameworks_utils_unittest.cc',
         'src/gn/function_foreach_unittest.cc',
         'src/gn/function_forward_variables_from_unittest.cc',
         'src/gn/function_get_label_info_unittest.cc',
@@ -600,8 +602,8 @@
         'src/gn/function_template_unittest.cc',
         'src/gn/function_toolchain_unittest.cc',
         'src/gn/function_write_file_unittest.cc',
-        'src/gn/functions_target_unittest.cc',
         'src/gn/functions_target_rust_unittest.cc',
+        'src/gn/functions_target_unittest.cc',
         'src/gn/functions_unittest.cc',
         'src/gn/header_checker_unittest.cc',
         'src/gn/inherited_libraries_unittest.cc',
@@ -614,14 +616,15 @@
         'src/gn/metadata_walk_unittest.cc',
         'src/gn/ninja_action_target_writer_unittest.cc',
         'src/gn/ninja_binary_target_writer_unittest.cc',
-        'src/gn/ninja_c_binary_target_writer_unittest.cc',
         'src/gn/ninja_build_writer_unittest.cc',
         'src/gn/ninja_bundle_data_target_writer_unittest.cc',
+        'src/gn/ninja_c_binary_target_writer_unittest.cc',
         'src/gn/ninja_copy_target_writer_unittest.cc',
         'src/gn/ninja_create_bundle_target_writer_unittest.cc',
-        'src/gn/ninja_rust_binary_target_writer_unittest.cc',
         'src/gn/ninja_generated_file_target_writer_unittest.cc',
         'src/gn/ninja_group_target_writer_unittest.cc',
+        'src/gn/ninja_rust_binary_target_writer_unittest.cc',
+        'src/gn/ninja_target_command_util_unittest.cc',
         'src/gn/ninja_target_writer_unittest.cc',
         'src/gn/ninja_toolchain_writer_unittest.cc',
         'src/gn/operators_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md
index f76c5b5..7abdd20 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -116,6 +116,8 @@
     *   [depfile: [string] File name for input dependencies for actions.](#var_depfile)
     *   [deps: [label list] Private linked dependencies.](#var_deps)
     *   [externs: [scope] Set of Rust crate-dependency pairs.](#var_externs)
+    *   [framework_dirs: [directory list] Additional framework search directories.](#var_framework_dirs)
+    *   [frameworks: [name list] Name of frameworks that must be linked.](#var_frameworks)
     *   [friend: [label pattern list] Allow targets to include private headers.](#var_friend)
     *   [include_dirs: [directory list] Additional include directories.](#var_include_dirs)
     *   [inputs: [file list] Additional compile-time dependencies.](#var_inputs)
@@ -496,6 +498,8 @@
   defines [--blame]
   depfile
   deps [--all] [--tree] (see below)
+  framework_dirs
+  frameworks
   include_dirs [--blame]
   inputs
   ldflags [--blame]
@@ -543,9 +547,9 @@
 ```
   --blame
       Used with any value specified on a config, this will name the config that
-      causes that target to get the flag. This doesn't currently work for libs
-      and lib_dirs because those are inherited and are more complicated to
-      figure out the blame (patches welcome).
+      causes that target to get the flag. This doesn't currently work for libs,
+      lib_dirs, frameworks and framework_dirs because those are inherited and
+      are more complicated to figure out the blame (patches welcome).
 ```
 
 #### **Configs**
@@ -3365,12 +3369,32 @@
         Valid for: Linker tools except "alink"
 
         These strings will be prepended to the libraries and library search
-        directories, respectively, because linkers differ on how specify them.
+        directories, respectively, because linkers differ on how to specify
+        them.
+
         If you specified:
           lib_switch = "-l"
           lib_dir_switch = "-L"
-        then the "{{libs}}" expansion for [ "freetype", "expat"] would be
-        "-lfreetype -lexpat".
+        then the "{{libs}}" expansion for
+          [ "freetype", "expat" ]
+        would be
+          "-lfreetype -lexpat".
+
+    framework_switch [string, optional, link tools only]
+    framework_dir_switch [string, optional, link tools only]
+        Valid for: Linker tools
+
+        These strings will be prepended to the frameworks and framework search
+        path directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          framework_switch = "-framework "
+          framework_dir_switch = "-F"
+        then the "{{libs}}" expansion for
+          [ "UIKit.framework", "$root_out_dir/Foo.framework" ]
+        would be
+          "-framework UIKit -F. -framework Foo"
 
     outputs  [list of strings with substitutions]
         Valid for: Linker and compiler tools (required)
@@ -3641,6 +3665,12 @@
 
         Example: "libfoo.so libbar.so"
 
+    {{frameworks}}
+        Shared libraries packaged as framework bundle. This is principally
+        used on Apple's platforms (macOS and iOS). All name must be ending
+        with ".framework" suffix; the suffix will be stripped when expanding
+        {{frameworks}} and each item will be preceded by "-framework".
+
   The static library ("alink") tool allows {{arflags}} plus the common tool
   substitutions.
 
@@ -5209,6 +5239,70 @@
   This target would compile the `foo` crate with the following `extern` flag:
   `--extern bar=path/to/bar.rlib`.
 ```
+### <a name="var_framework_dirs"></a>**framework_dirs**: [directory list] Additional framework search directories.
+
+```
+  A list of source directories.
+
+  The directories in this list will be added to the framework search path for
+  the files in the affected target.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  framework_dirs = [ "src/include", "//third_party/foo" ]
+```
+### <a name="var_frameworks"></a>**frameworks**: [name list] Name of frameworks that must be linked.
+
+```
+  A list of framework names.
+
+  The frameworks named in that list will be linked with any dynamic link
+  type target.
+```
+
+#### **Ordering of flags and values**
+
+```
+  1. Those set on the current target (not in a config).
+  2. Those set on the "configs" on the target in order that the
+     configs appear in the list.
+  3. Those set on the "all_dependent_configs" on the target in order
+     that the configs appear in the list.
+  4. Those set on the "public_configs" on the target in order that
+     those configs appear in the list.
+  5. all_dependent_configs pulled from dependencies, in the order of
+     the "deps" list. This is done recursively. If a config appears
+     more than once, only the first occurence will be used.
+  6. public_configs pulled from dependencies, in the order of the
+     "deps" list. If a dependency is public, they will be applied
+     recursively.
+```
+
+#### **Example**
+
+```
+  frameworks = [ "Foundation.framework", "Foo.framework" ]
+```
 ### <a name="var_friend"></a>**friend**: Allow targets to include private headers.
 
 ```
diff --git a/src/gn/bundle_data.h b/src/gn/bundle_data.h
index fbf82c0..10a42b1 100644
--- a/src/gn/bundle_data.h
+++ b/src/gn/bundle_data.h
@@ -154,6 +154,11 @@
   // Recursive collection of all bundle_data that the target depends on.
   const UniqueTargets& bundle_deps() const { return bundle_deps_; }
 
+  // Returns whether the bundle is a framework bundle.
+  bool is_framework() const {
+    return product_type_ == "com.apple.product-type.framework";
+  }
+
  private:
   SourceFiles assets_catalog_sources_;
   std::vector<const Target*> assets_catalog_deps_;
diff --git a/src/gn/c_substitution_type.cc b/src/gn/c_substitution_type.cc
index e142eb7..b21fea4 100644
--- a/src/gn/c_substitution_type.cc
+++ b/src/gn/c_substitution_type.cc
@@ -13,11 +13,12 @@
     &CSubstitutionAsmFlags,     &CSubstitutionCFlags,
     &CSubstitutionCFlagsC,      &CSubstitutionCFlagsCc,
     &CSubstitutionCFlagsObjC,   &CSubstitutionCFlagsObjCc,
-    &CSubstitutionDefines,      &CSubstitutionIncludeDirs,
+    &CSubstitutionDefines,      &CSubstitutionFrameworkDirs,
+    &CSubstitutionIncludeDirs,
 
     &CSubstitutionLinkerInputs, &CSubstitutionLinkerInputsNewline,
     &CSubstitutionLdFlags,      &CSubstitutionLibs,
-    &CSubstitutionSoLibs,
+    &CSubstitutionSoLibs,       &CSubstitutionFrameworks,
 
     &CSubstitutionArFlags,
 };
@@ -31,6 +32,8 @@
 const Substitution CSubstitutionCFlagsObjCc = {"{{cflags_objcc}}",
                                               "cflags_objcc"};
 const Substitution CSubstitutionDefines = {"{{defines}}", "defines"};
+const Substitution CSubstitutionFrameworkDirs = {"{{framework_dirs}}",
+                                                 "framework_dirs"};
 const Substitution CSubstitutionIncludeDirs = {"{{include_dirs}}",
                                               "include_dirs"};
 
@@ -41,6 +44,7 @@
 const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
 const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
 const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
+const Substitution CSubstitutionFrameworks = {"{{frameworks}}", "frameworks"};
 
 // Valid for alink only.
 const Substitution CSubstitutionArFlags = {"{{arflags}}", "arflags"};
@@ -51,6 +55,7 @@
          type == &CSubstitutionCFlags || type == &CSubstitutionCFlagsC ||
          type == &CSubstitutionCFlagsCc || type == &CSubstitutionCFlagsObjC ||
          type == &CSubstitutionCFlagsObjCc || type == &CSubstitutionDefines ||
+         type == &CSubstitutionFrameworkDirs ||
          type == &CSubstitutionIncludeDirs;
 }
 
@@ -61,14 +66,12 @@
 }
 
 bool IsValidLinkerSubstitution(const Substitution* type) {
-  return IsValidToolSubstitution(type) ||
-         type == &SubstitutionOutputDir ||
+  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
          type == &SubstitutionOutputExtension ||
          type == &CSubstitutionLinkerInputs ||
          type == &CSubstitutionLinkerInputsNewline ||
-         type == &CSubstitutionLdFlags ||
-         type == &CSubstitutionLibs ||
-         type == &CSubstitutionSoLibs;
+         type == &CSubstitutionLdFlags || type == &CSubstitutionLibs ||
+         type == &CSubstitutionSoLibs || type == &CSubstitutionFrameworks;
 }
 
 bool IsValidLinkerOutputsSubstitution(const Substitution* type) {
diff --git a/src/gn/c_substitution_type.h b/src/gn/c_substitution_type.h
index a2df7c2..e955312 100644
--- a/src/gn/c_substitution_type.h
+++ b/src/gn/c_substitution_type.h
@@ -21,6 +21,7 @@
 extern const Substitution CSubstitutionCFlagsObjC;
 extern const Substitution CSubstitutionCFlagsObjCc;
 extern const Substitution CSubstitutionDefines;
+extern const Substitution CSubstitutionFrameworkDirs;
 extern const Substitution CSubstitutionIncludeDirs;
 
 // Valid for linker tools.
@@ -29,6 +30,7 @@
 extern const Substitution CSubstitutionLdFlags;
 extern const Substitution CSubstitutionLibs;
 extern const Substitution CSubstitutionSoLibs;
+extern const Substitution CSubstitutionFrameworks;
 
 // Valid for alink only.
 extern const Substitution CSubstitutionArFlags;
diff --git a/src/gn/c_tool.cc b/src/gn/c_tool.cc
index b0f930b..4046e33 100644
--- a/src/gn/c_tool.cc
+++ b/src/gn/c_tool.cc
@@ -21,6 +21,7 @@
     : Tool(n), depsformat_(DEPS_GCC), precompiled_header_type_(PCH_NONE) {
   CHECK(ValidateName(n));
   set_framework_switch("-framework ");
+  set_framework_dir_switch("-F");
   set_lib_dir_switch("-L");
   set_lib_switch("-l");
   set_linker_arg("");
@@ -182,6 +183,8 @@
   }
 
   if (!ReadDepsFormat(scope, err) || !ReadPrecompiledHeaderType(scope, err) ||
+      !ReadString(scope, "framework_switch", &framework_switch_, err) ||
+      !ReadString(scope, "framework_dir_switch", &framework_dir_switch_, err) ||
       !ReadString(scope, "lib_switch", &lib_switch_, err) ||
       !ReadString(scope, "lib_dir_switch", &lib_dir_switch_, err) ||
       !ReadPattern(scope, "link_output", &link_output_, err) ||
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index 13e61c1..086d5f1 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -287,6 +287,8 @@
           {variables::kCflagsObjC, DefaultHandler},
           {variables::kCflagsObjCC, DefaultHandler},
           {variables::kDefines, DefaultHandler},
+          {variables::kFrameworkDirs, DefaultHandler},
+          {variables::kFrameworks, DefaultHandler},
           {variables::kIncludeDirs, DefaultHandler},
           {variables::kLdflags, DefaultHandler},
           {variables::kPrecompiledHeader, DefaultHandler},
@@ -372,6 +374,8 @@
   HandleProperty(variables::kCflagsObjC, handler_map, v, dict);
   HandleProperty(variables::kCflagsObjCC, handler_map, v, dict);
   HandleProperty(variables::kDefines, handler_map, v, dict);
+  HandleProperty(variables::kFrameworkDirs, handler_map, v, dict);
+  HandleProperty(variables::kFrameworks, handler_map, v, dict);
   HandleProperty(variables::kIncludeDirs, handler_map, v, dict);
   HandleProperty(variables::kLdflags, handler_map, v, dict);
   HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict);
@@ -434,6 +438,8 @@
   HandleProperty(variables::kCflagsObjC, handler_map, v, dict);
   HandleProperty(variables::kCflagsObjCC, handler_map, v, dict);
   HandleProperty(variables::kDefines, handler_map, v, dict);
+  HandleProperty(variables::kFrameworkDirs, handler_map, v, dict);
+  HandleProperty(variables::kFrameworks, handler_map, v, dict);
   HandleProperty(variables::kIncludeDirs, handler_map, v, dict);
   HandleProperty(variables::kInputs, handler_map, v, dict);
   HandleProperty(variables::kLdflags, handler_map, v, dict);
@@ -484,6 +490,8 @@
   defines [--blame]
   depfile
   deps [--all] [--tree] (see below)
+  framework_dirs
+  frameworks
   include_dirs [--blame]
   inputs
   ldflags [--blame]
@@ -523,9 +531,9 @@
 
   --blame
       Used with any value specified on a config, this will name the config that
-      causes that target to get the flag. This doesn't currently work for libs
-      and lib_dirs because those are inherited and are more complicated to
-      figure out the blame (patches welcome).
+      causes that target to get the flag. This doesn't currently work for libs,
+      lib_dirs, frameworks and framework_dirs because those are inherited and
+      are more complicated to figure out the blame (patches welcome).
 
 Configs
 
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 9ff67ca..5dfd2f6 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -47,6 +47,8 @@
   std::string cflags_cc;
   std::string cflags_objc;
   std::string cflags_objcc;
+  std::string framework_dirs;
+  std::string frameworks;
 };
 
 void SetupCompileFlags(const Target* target,
@@ -62,6 +64,19 @@
                                              defines_out);
   base::EscapeJSONString(defines_out.str(), false, &flags.defines);
 
+  std::ostringstream framework_dirs_out;
+  RecursiveTargetConfigToStream<SourceDir>(
+      target, &ConfigValues::framework_dirs,
+      FrameworkDirsWriter(path_output, "-F"), framework_dirs_out);
+  base::EscapeJSONString(framework_dirs_out.str(), false,
+                         &flags.framework_dirs);
+
+  std::ostringstream frameworks_out;
+  RecursiveTargetConfigToStream<std::string>(
+      target, &ConfigValues::frameworks,
+      FrameworksWriter(ESCAPE_SPACE, true, "-framework "), frameworks_out);
+  base::EscapeJSONString(frameworks_out.str(), false, &flags.frameworks);
+
   std::ostringstream includes_out;
   RecursiveTargetConfigToStream<SourceDir>(target, &ConfigValues::include_dirs,
                                            IncludeWriter(path_output),
@@ -139,6 +154,10 @@
       path_output.WriteFiles(command_out, tool_outputs);
     } else if (range.type == &CSubstitutionDefines) {
       command_out << flags.defines;
+    } else if (range.type == &CSubstitutionFrameworkDirs) {
+      command_out << flags.framework_dirs;
+    } else if (range.type == &CSubstitutionFrameworks) {
+      command_out << flags.frameworks;
     } else if (range.type == &CSubstitutionIncludeDirs) {
       command_out << flags.includes;
     } else if (range.type == &CSubstitutionCFlags) {
diff --git a/src/gn/config_values.cc b/src/gn/config_values.cc
index bcb8c33..d4f597e 100644
--- a/src/gn/config_values.cc
+++ b/src/gn/config_values.cc
@@ -29,6 +29,8 @@
   VectorAppend(&cflags_objc_, append.cflags_objc_);
   VectorAppend(&cflags_objcc_, append.cflags_objcc_);
   VectorAppend(&defines_, append.defines_);
+  VectorAppend(&frameworks_, append.frameworks_);
+  VectorAppend(&framework_dirs_, append.framework_dirs_);
   VectorAppend(&include_dirs_, append.include_dirs_);
   VectorAppend(&inputs_, append.inputs_);
   VectorAppend(&ldflags_, append.ldflags_);
diff --git a/src/gn/config_values.h b/src/gn/config_values.h
index 0b4ca18..efd70eb 100644
--- a/src/gn/config_values.h
+++ b/src/gn/config_values.h
@@ -42,6 +42,8 @@
   STRING_VALUES_ACCESSOR(cflags_objc)
   STRING_VALUES_ACCESSOR(cflags_objcc)
   STRING_VALUES_ACCESSOR(defines)
+  DIR_VALUES_ACCESSOR(framework_dirs)
+  STRING_VALUES_ACCESSOR(frameworks)
   DIR_VALUES_ACCESSOR(include_dirs)
   STRING_VALUES_ACCESSOR(ldflags)
   DIR_VALUES_ACCESSOR(lib_dirs)
@@ -84,6 +86,8 @@
   std::vector<std::string> cflags_objcc_;
   std::vector<std::string> defines_;
   std::vector<SourceDir> include_dirs_;
+  std::vector<SourceDir> framework_dirs_;
+  std::vector<std::string> frameworks_;
   std::vector<SourceFile> inputs_;
   std::vector<std::string> ldflags_;
   std::vector<SourceDir> lib_dirs_;
diff --git a/src/gn/config_values_generator.cc b/src/gn/config_values_generator.cc
index 6e0df36..52ef61f 100644
--- a/src/gn/config_values_generator.cc
+++ b/src/gn/config_values_generator.cc
@@ -7,6 +7,7 @@
 #include "base/strings/string_util.h"
 #include "gn/build_settings.h"
 #include "gn/config_values.h"
+#include "gn/frameworks_utils.h"
 #include "gn/scope.h"
 #include "gn/settings.h"
 #include "gn/value.h"
@@ -71,6 +72,7 @@
   FILL_STRING_CONFIG_VALUE(cflags_objc)
   FILL_STRING_CONFIG_VALUE(cflags_objcc)
   FILL_STRING_CONFIG_VALUE(defines)
+  FILL_DIR_CONFIG_VALUE(framework_dirs)
   FILL_DIR_CONFIG_VALUE(include_dirs)
   FILL_STRING_CONFIG_VALUE(ldflags)
   FILL_DIR_CONFIG_VALUE(lib_dirs)
@@ -102,6 +104,29 @@
                          input_dir_, &config_values_->externs(), err_);
   }
 
+  // Frameworks
+  const Value* frameworks_value =
+      scope_->GetValue(variables::kFrameworks, true);
+  if (frameworks_value) {
+    std::vector<std::string> frameworks;
+    if (!ExtractListOfStringValues(*frameworks_value, &frameworks, err_))
+      return;
+
+    // All strings must end with ".frameworks".
+    for (const std::string& framework : frameworks) {
+      std::string_view framework_name = GetFrameworkName(framework);
+      if (framework_name.empty()) {
+        *err_ = Err(*frameworks_value,
+                    "This frameworks value is wrong."
+                    "All listed frameworks names must not include any\n"
+                    "path component and have \".framework\" extension.");
+        return;
+      }
+    }
+
+    config_values_->frameworks().swap(frameworks);
+  }
+
   // Precompiled headers.
   const Value* precompiled_header_value =
       scope_->GetValue(variables::kPrecompiledHeader, true);
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 5cd9774..4e8f690 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -519,9 +519,8 @@
       res->SetWithoutPathExpansion("runtime_deps", RenderRuntimeDeps());
 
     // libs and lib_dirs are special in that they're inherited. We don't
-    // currently
-    // implement a blame feature for this since the bottom-up inheritance makes
-    // this difficult.
+    // currently implement a blame feature for this since the bottom-up
+    // inheritance makes this difficult.
 
     // Libs can be part of any target and get recursively pushed up the chain,
     // so display them regardless of target type.
@@ -545,6 +544,28 @@
       }
     }
 
+    if (what(variables::kFrameworks)) {
+      const auto& all_frameworks = target_->all_frameworks();
+      if (!all_frameworks.empty()) {
+        auto frameworks = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_frameworks.size(); i++)
+          frameworks->AppendString(all_frameworks[i]);
+        res->SetWithoutPathExpansion(variables::kFrameworks,
+                                     std::move(frameworks));
+      }
+    }
+
+    if (what(variables::kFrameworkDirs)) {
+      const auto& all_framework_dirs = target_->all_framework_dirs();
+      if (!all_framework_dirs.empty()) {
+        auto framework_dirs = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < all_framework_dirs.size(); i++)
+          framework_dirs->AppendString(all_framework_dirs[i].value());
+        res->SetWithoutPathExpansion(variables::kFrameworkDirs,
+                                     std::move(framework_dirs));
+      }
+    }
+
     return res;
   }
 
diff --git a/src/gn/frameworks_utils.cc b/src/gn/frameworks_utils.cc
new file mode 100644
index 0000000..4aba827
--- /dev/null
+++ b/src/gn/frameworks_utils.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/frameworks_utils.h"
+
+#include "gn/filesystem_utils.h"
+
+namespace {
+
+// Name of the extension of frameworks.
+const char kFrameworkExtension[] = "framework";
+
+}  // anonymous namespace
+
+std::string_view GetFrameworkName(const std::string& file) {
+  if (FindFilenameOffset(file) != 0)
+    return std::string_view();
+
+  std::string_view extension = FindExtension(&file);
+  if (extension != kFrameworkExtension)
+    return std::string_view();
+
+  return FindFilenameNoExtension(&file);
+}
diff --git a/src/gn/frameworks_utils.h b/src/gn/frameworks_utils.h
new file mode 100644
index 0000000..b9df3e7
--- /dev/null
+++ b/src/gn/frameworks_utils.h
@@ -0,0 +1,15 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_FRAMEWORKS_UTILS_H_
+#define TOOLS_GN_FRAMEWORKS_UTILS_H_
+
+#include <string>
+#include <string_view>
+
+// Returns the name of the framework from a file name. This returns an empty
+// string_view if the name is incorrect (does not ends in ".framework", ...).
+std::string_view GetFrameworkName(const std::string& file);
+
+#endif  // TOOLS_GN_FRAMEWORKS_UTILS_H_
diff --git a/src/gn/frameworks_utils_unittest.cc b/src/gn/frameworks_utils_unittest.cc
new file mode 100644
index 0000000..3e20823
--- /dev/null
+++ b/src/gn/frameworks_utils_unittest.cc
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/frameworks_utils.h"
+
+#include "util/test/test.h"
+
+TEST(FrameworksUtils, GetFrameworkName) {
+  const std::string kFramework = "Foundation.framework";
+  const std::string kFrameworkNoExtension = "Foundation";
+  const std::string kFrameworkPath = "Foo/Foo.framework";
+
+  EXPECT_EQ("Foundation", GetFrameworkName(kFramework));
+  EXPECT_EQ("", GetFrameworkName(kFrameworkNoExtension));
+  EXPECT_EQ("", GetFrameworkName(kFrameworkPath));
+}
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
index fc9b0c1..0efcd1e 100644
--- a/src/gn/function_toolchain.cc
+++ b/src/gn/function_toolchain.cc
@@ -395,12 +395,32 @@
         Valid for: Linker tools except "alink"
 
         These strings will be prepended to the libraries and library search
-        directories, respectively, because linkers differ on how specify them.
+        directories, respectively, because linkers differ on how to specify
+        them.
+
         If you specified:
           lib_switch = "-l"
           lib_dir_switch = "-L"
-        then the "{{libs}}" expansion for [ "freetype", "expat"] would be
-        "-lfreetype -lexpat".
+        then the "{{libs}}" expansion for
+          [ "freetype", "expat" ]
+        would be
+          "-lfreetype -lexpat".
+
+    framework_switch [string, optional, link tools only]
+    framework_dir_switch [string, optional, link tools only]
+        Valid for: Linker tools
+
+        These strings will be prepended to the frameworks and framework search
+        path directories, respectively, because linkers differ on how to specify
+        them.
+
+        If you specified:
+          framework_switch = "-framework "
+          framework_dir_switch = "-F"
+        then the "{{libs}}" expansion for
+          [ "UIKit.framework", "$root_out_dir/Foo.framework" ]
+        would be
+          "-framework UIKit -F. -framework Foo"
 
     outputs  [list of strings with substitutions]
         Valid for: Linker and compiler tools (required)
@@ -670,6 +690,12 @@
 
         Example: "libfoo.so libbar.so"
 
+    {{frameworks}}
+        Shared libraries packaged as framework bundle. This is principally
+        used on Apple's platforms (macOS and iOS). All name must be ending
+        with ".framework" suffix; the suffix will be stripped when expanding
+        {{frameworks}} and each item will be preceded by "-framework".
+
 )"  // String break to prevent overflowing the 16K max VC string length.
     R"(  The static library ("alink") tool allows {{arflags}} plus the common tool
   substitutions.
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index bf517a2..1a3e9b9 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -13,11 +13,13 @@
 #include "gn/general_tool.h"
 #include "gn/ninja_c_binary_target_writer.h"
 #include "gn/ninja_rust_binary_target_writer.h"
+#include "gn/ninja_target_command_util.h"
 #include "gn/ninja_utils.h"
 #include "gn/settings.h"
 #include "gn/string_utils.h"
 #include "gn/substitution_writer.h"
 #include "gn/target.h"
+#include "gn/variables.h"
 
 namespace {
 
@@ -97,7 +99,9 @@
   UniqueVector<OutputFile> extra_object_files;
   UniqueVector<const Target*> linkable_deps;
   UniqueVector<const Target*> non_linkable_deps;
-  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
+  UniqueVector<const Target*> framework_deps;
+  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps,
+          &framework_deps);
 
   // The classifier should never put extra object files in a source sets: any
   // source sets that we depend on should appear in our non-linkable deps
@@ -114,17 +118,18 @@
 void NinjaBinaryTargetWriter::GetDeps(
     UniqueVector<OutputFile>* extra_object_files,
     UniqueVector<const Target*>* linkable_deps,
-    UniqueVector<const Target*>* non_linkable_deps) const {
+    UniqueVector<const Target*>* non_linkable_deps,
+    UniqueVector<const Target*>* framework_deps) const {
   // Normal public/private deps.
   for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
     ClassifyDependency(pair.ptr, extra_object_files, linkable_deps,
-                       non_linkable_deps);
+                       non_linkable_deps, framework_deps);
   }
 
   // Inherited libraries.
   for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
     ClassifyDependency(inherited_target, extra_object_files, linkable_deps,
-                       non_linkable_deps);
+                       non_linkable_deps, framework_deps);
   }
 
   // Data deps.
@@ -136,7 +141,8 @@
     const Target* dep,
     UniqueVector<OutputFile>* extra_object_files,
     UniqueVector<const Target*>* linkable_deps,
-    UniqueVector<const Target*>* non_linkable_deps) const {
+    UniqueVector<const Target*>* non_linkable_deps,
+    UniqueVector<const Target*>* framework_deps) const {
   // Only the following types of outputs have libraries linked into them:
   //  EXECUTABLE
   //  SHARED_LIBRARY
@@ -179,6 +185,9 @@
     non_linkable_deps->push_back(dep);
   } else if (can_link_libs && dep->IsLinkable()) {
     linkable_deps->push_back(dep);
+  } else if (dep->output_type() == Target::CREATE_BUNDLE &&
+             dep->bundle_data().is_framework()) {
+    framework_deps->push_back(dep);
   } else {
     non_linkable_deps->push_back(dep);
   }
@@ -244,6 +253,20 @@
     }
   }
 
+  const auto& all_framework_dirs = target_->all_framework_dirs();
+  if (!all_framework_dirs.empty()) {
+    // Since we're passing these on the command line to the linker and not
+    // to Ninja, we need to do shell escaping.
+    PathOutput framework_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    for (size_t i = 0; i < all_framework_dirs.size(); i++) {
+      out << " " << tool->framework_dir_switch();
+      framework_path_output.WriteDir(out, all_framework_dirs[i],
+                                     PathOutput::DIR_NO_LAST_SLASH);
+    }
+  }
+
   if (optional_def_file) {
     out_ << " /DEF:";
     path_output_.WriteFile(out, *optional_def_file);
@@ -255,24 +278,34 @@
   EscapeOptions lib_escape_opts;
   lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
   const OrderedSet<LibFile> all_libs = target_->all_libs();
-  const std::string framework_ending(".framework");
   for (size_t i = 0; i < all_libs.size(); i++) {
     const LibFile& lib_file = all_libs[i];
     const std::string& lib_value = lib_file.value();
+    std::string_view framework_name = GetFrameworkName(lib_value);
     if (lib_file.is_source_file()) {
       out << " " << tool->linker_arg();
       path_output_.WriteFile(out, lib_file.source_file());
-    } else if (base::EndsWith(lib_value, framework_ending,
-                              base::CompareCase::INSENSITIVE_ASCII)) {
+    } else if (!framework_name.empty()) {
       // Special-case libraries ending in ".framework" to support Mac: Add the
       // -framework switch and don't add the extension to the output.
+      // TODO(crbug.com/gn/119): remove this once all code has been ported to
+      // use "frameworks" and "framework_dirs" instead.
       out << " " << tool->framework_switch();
-      EscapeStringToStream(
-          out, lib_value.substr(0, lib_value.size() - framework_ending.size()),
-          lib_escape_opts);
+      EscapeStringToStream(out, framework_name, lib_escape_opts);
     } else {
       out << " " << tool->lib_switch();
       EscapeStringToStream(out, lib_value, lib_escape_opts);
     }
   }
 }
+
+void NinjaBinaryTargetWriter::WriteFrameworks(std::ostream& out,
+                                              const Tool* tool) {
+  FrameworksWriter writer(tool->framework_switch());
+
+  // Frameworks that have been recursively pushed through the dependency tree.
+  const auto& all_frameworks = target_->all_frameworks();
+  for (size_t i = 0; i < all_frameworks.size(); i++) {
+    writer(all_frameworks[i], out);
+  }
+}
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h
index 0eceed7..7539ebb 100644
--- a/src/gn/ninja_binary_target_writer.h
+++ b/src/gn/ninja_binary_target_writer.h
@@ -37,7 +37,8 @@
   // object files from source sets we need to link.
   void GetDeps(UniqueVector<OutputFile>* extra_object_files,
                UniqueVector<const Target*>* linkable_deps,
-               UniqueVector<const Target*>* non_linkable_deps) const;
+               UniqueVector<const Target*>* non_linkable_deps,
+               UniqueVector<const Target*>* framework_deps) const;
 
   // Classifies the dependency as linkable or nonlinkable with the current
   // target, adding it to the appropriate vector. If the dependency is a source
@@ -46,7 +47,8 @@
   void ClassifyDependency(const Target* dep,
                           UniqueVector<OutputFile>* extra_object_files,
                           UniqueVector<const Target*>* linkable_deps,
-                          UniqueVector<const Target*>* non_linkable_deps) const;
+                          UniqueVector<const Target*>* non_linkable_deps,
+                          UniqueVector<const Target*>* framework_deps) const;
 
   OutputFile WriteStampAndGetDep(const UniqueVector<const SourceFile*>& files,
                                  const std::string& stamp_ext) const;
@@ -61,6 +63,7 @@
                         const Tool* tool,
                         const SourceFile* optional_def_file);
   void WriteLibs(std::ostream& out, const Tool* tool);
+  void WriteFrameworks(std::ostream& out, const Tool* tool);
 
   virtual void AddSourceSetFiles(const Target* source_set,
                                  UniqueVector<OutputFile>* obj_files) const;
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index f1d2fe5..ebc2d17 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -159,6 +159,22 @@
     out_ << std::endl;
   }
 
+  // Framework search path.
+  if (subst.used.count(&CSubstitutionFrameworkDirs)) {
+    const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);
+
+    out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
+    PathOutput framework_dirs_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        target_, &ConfigValues::framework_dirs,
+        FrameworkDirsWriter(framework_dirs_output,
+                            tool->framework_dir_switch()),
+        out_);
+    out_ << std::endl;
+  }
+
   // Include directories.
   if (subst.used.count(&CSubstitutionIncludeDirs)) {
     out_ << CSubstitutionIncludeDirs.ninja_name << " =";
@@ -443,7 +459,9 @@
   UniqueVector<OutputFile> extra_object_files;
   UniqueVector<const Target*> linkable_deps;
   UniqueVector<const Target*> non_linkable_deps;
-  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
+  UniqueVector<const Target*> framework_deps;
+  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps,
+          &framework_deps);
 
   // Object files.
   path_output_.WriteFiles(out_, object_files);
@@ -496,6 +514,17 @@
     }
   }
 
+  // If any target creates a framework bundle, then treat it as an implicit
+  // dependency via the .stamp file. This is a pessimisation as it is not
+  // always necessary to relink the current target if one of the framework
+  // is regenerated, but it ensure that if one of the framework API changes,
+  // any dependent target will relink it (see crbug.com/1037607).
+  if (!framework_deps.empty()) {
+    for (const Target* dep : framework_deps) {
+      implicit_deps.push_back(dep->dependency_output_file());
+    }
+  }
+
   // The input dependency is only needed if there are no object files, as the
   // dependency is normally provided transitively by the source files.
   if (!input_dep.value().empty() && object_files.empty())
@@ -535,6 +564,9 @@
     out_ << "  libs =";
     WriteLibs(out_, tool_);
     out_ << std::endl;
+    out_ << "  frameworks =";
+    WriteFrameworks(out_, tool_);
+    out_ << std::endl;
   } else if (target_->output_type() == Target::STATIC_LIBRARY) {
     out_ << "  arflags =";
     RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 48ff9e4..6b0fd64 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -89,6 +89,7 @@
         "|| obj/foo/bar.stamp\n"
         "  ldflags =\n"
         "  libs =\n"
+        "  frameworks =\n"
         "  output_extension = .so\n"
         "  output_dir = \n";
     std::string out_str = out.str();
@@ -338,6 +339,7 @@
       "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = .so.6\n"
       "  output_dir = foo\n";
 
@@ -429,6 +431,7 @@
       " || obj/foo/gen_obj.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = .so\n"
       "  output_dir = foo\n";
 
@@ -466,6 +469,7 @@
       " ./libgen_lib.so\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = \n"
       "  output_dir = foo\n";
 
@@ -502,6 +506,60 @@
       "build ./libshlib.so: solink | ../../foo/lib1.a\n"
       "  ldflags = -L../../foo/bar\n"
       "  libs = ../../foo/lib1.a -lfoo\n"
+      "  frameworks =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests frameworks are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, FrameworksAndFrameworkDirs) {
+  Err err;
+  TestWithScope setup;
+
+  // A config that force linking with the framework.
+  Config framework_config(setup.settings(),
+                          Label(SourceDir("//bar"), "framework_config"));
+  framework_config.own_values().frameworks().push_back("Bar.framework");
+  framework_config.own_values().framework_dirs().push_back(
+      SourceDir("//out/Debug/"));
+  ASSERT_TRUE(framework_config.OnResolved(&err));
+
+  // A target creating a framework bundle.
+  Target framework(setup.settings(), Label(SourceDir("//bar"), "framework"));
+  framework.set_output_type(Target::CREATE_BUNDLE);
+  framework.bundle_data().product_type() = "com.apple.product-type.framework";
+  framework.public_configs().push_back(LabelConfigPair(&framework_config));
+  framework.SetToolchain(setup.toolchain());
+  framework.visibility().SetPublic();
+  ASSERT_TRUE(framework.OnResolved(&err));
+
+  // A shared library w/ libs and lib_dirs.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.config_values().frameworks().push_back("System.framework");
+  target.private_deps().push_back(LabelTargetPair(&framework));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "\n"
+      "build ./libshlib.so: solink | obj/bar/framework.stamp\n"
+      "  ldflags = -F.\n"
+      "  libs =\n"
+      "  frameworks = -framework System -framework Bar\n"
       "  output_extension = .so\n"
       "  output_dir = \n";
 
@@ -547,6 +605,7 @@
       "obj/foo/shlib.input2.o\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = \n"
       "  output_dir = \n";
 
@@ -631,6 +690,7 @@
       "obj/foo/inter.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = \n"
       "  output_dir = \n";
   EXPECT_EQ(final_expected, final_out.str());
@@ -667,6 +727,7 @@
       "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
       "  ldflags = /DEF:../../foo/bar.def\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = .so\n"
       "  output_dir = \n";
   EXPECT_EQ(expected, out.str());
@@ -702,6 +763,7 @@
       "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = .so\n"
       "  output_dir = \n";
   EXPECT_EQ(loadable_expected, out.str());
@@ -735,6 +797,7 @@
       "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
       "  ldflags =\n"
       "  libs =\n"
+      "  frameworks =\n"
       "  output_extension = \n"
       "  output_dir = \n";
   EXPECT_EQ(final_expected, final_out.str());
@@ -1091,6 +1154,7 @@
         "build ./libbar.so: solink | ../../foo/input.data\n"
         "  ldflags =\n"
         "  libs =\n"
+        "  frameworks =\n"
         "  output_extension = .so\n"
         "  output_dir = \n";
 
@@ -1230,6 +1294,7 @@
         "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
+        "  frameworks =\n"
         "  output_extension = \n"
         "  output_dir = \n";
 
@@ -1288,6 +1353,7 @@
         "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
+        "  frameworks =\n"
         "  output_extension = \n"
         "  output_dir = \n";
 
@@ -1347,6 +1413,7 @@
         "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
+        "  frameworks =\n"
         "  output_extension = \n"
         "  output_dir = \n";
 
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 2ac1978..0f3895a 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -130,13 +130,17 @@
     WriteCompilerVars();
     UniqueVector<const Target*> linkable_deps;
     UniqueVector<const Target*> non_linkable_deps;
-    GetDeps(&deps, &linkable_deps, &non_linkable_deps);
+    UniqueVector<const Target*> framework_deps;
+    GetDeps(&deps, &linkable_deps, &non_linkable_deps, &framework_deps);
 
     if (!input_dep.value().empty())
       order_only_deps.push_back(input_dep);
 
     std::vector<OutputFile> rustdeps;
     std::vector<OutputFile> nonrustdeps;
+    for (const auto* framework_dep : framework_deps) {
+      order_only_deps.push_back(framework_dep->dependency_output_file());
+    }
     for (const auto* non_linkable_dep : non_linkable_deps) {
       if (non_linkable_dep->source_types_used().RustSourceUsed() &&
           non_linkable_dep->output_type() != Target::SOURCE_SET) {
diff --git a/src/gn/ninja_target_command_util.h b/src/gn/ninja_target_command_util.h
index 3b192ab..022d5e7 100644
--- a/src/gn/ninja_target_command_util.h
+++ b/src/gn/ninja_target_command_util.h
@@ -9,9 +9,11 @@
 #include "gn/config_values_extractors.h"
 #include "gn/escape.h"
 #include "gn/filesystem_utils.h"
+#include "gn/frameworks_utils.h"
 #include "gn/path_output.h"
 #include "gn/target.h"
 #include "gn/toolchain.h"
+#include "gn/variables.h"
 
 struct DefineWriter {
   DefineWriter() { options.mode = ESCAPE_NINJA_COMMAND; }
@@ -35,6 +37,54 @@
   bool escape_strings = false;
 };
 
+struct FrameworkDirsWriter {
+  FrameworkDirsWriter(PathOutput& path_output, const std::string& tool_switch)
+      : path_output_(path_output), tool_switch_(tool_switch) {}
+
+  ~FrameworkDirsWriter() = default;
+
+  void operator()(const SourceDir& d, std::ostream& out) const {
+    std::ostringstream path_out;
+    path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH);
+    const std::string& path = path_out.str();
+    if (path[0] == '"')
+      out << " \"" << tool_switch_ << path.substr(1);
+    else
+      out << " " << tool_switch_ << path;
+  }
+
+  PathOutput& path_output_;
+  std::string tool_switch_;
+};
+
+struct FrameworksWriter {
+  explicit FrameworksWriter(const std::string& tool_switch)
+      : FrameworksWriter(ESCAPE_NINJA_COMMAND, false, tool_switch) {}
+  FrameworksWriter(EscapingMode mode,
+                   bool escape_strings,
+                   const std::string& tool_switch)
+      : escape_strings_(escape_strings), tool_switch_(tool_switch) {
+    options_.mode = mode;
+  }
+
+  void operator()(const std::string& s, std::ostream& out) const {
+    out << " " << tool_switch_;
+    std::string_view framework_name = GetFrameworkName(s);
+
+    if (escape_strings_) {
+      std::string dest;
+      base::EscapeJSONString(framework_name, false, &dest);
+      EscapeStringToStream(out, dest, options_);
+      return;
+    }
+    EscapeStringToStream(out, framework_name, options_);
+  }
+
+  EscapeOptions options_;
+  bool escape_strings_;
+  std::string tool_switch_;
+};
+
 struct IncludeWriter {
   explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) {}
   ~IncludeWriter() = default;
diff --git a/src/gn/ninja_target_command_util_unittest.cc b/src/gn/ninja_target_command_util_unittest.cc
new file mode 100644
index 0000000..248db30
--- /dev/null
+++ b/src/gn/ninja_target_command_util_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_target_command_util.h"
+
+#include <algorithm>
+#include <sstream>
+
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+// Helper function that uses a "Writer" to format a list of strings and return
+// the generated output as a string.
+template <typename Writer, typename Item>
+std::string FormatWithWriter(Writer writer, std::vector<Item> items) {
+  std::ostringstream out;
+  for (const Item& item : items) {
+    writer(item, out);
+  }
+  return out.str();
+}
+
+// Helper function running to test "Writer" by formatting N "Items" and
+// comparing the resulting string to an expected output.
+template <typename Writer, typename Item>
+void TestWriter(Writer writer,
+                std::string expected,
+                std::initializer_list<Item> items) {
+  std::string formatted =
+      FormatWithWriter(writer, std::vector<Item>(std::move(items)));
+
+  // Manually implement the check and the formatting of the error message to
+  // see the difference in the error message (by default the error message
+  // would just be "formatted == expected").
+  if (formatted != expected) {
+    std::ostringstream stream;
+    stream << '"' << expected << "\" == \"" << formatted << '"';
+    std::string message = stream.str();
+
+    ::testing::TestResult result(false, message.c_str());
+    ::testing::AssertHelper(__FILE__, __LINE__, result) = ::testing::Message();
+  }
+}
+
+}  // anonymous namespace
+
+TEST(NinjaTargetCommandUtil, DefineWriter) {
+  TestWriter(DefineWriter(),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -DFOO -DBAR=1 \"-DBAZ=\\\"Baz\\\"\"",
+#else
+             " -DFOO -DBAR=1 -DBAZ=\\\"Baz\\\"",
+#endif
+             {"FOO", "BAR=1", "BAZ=\"Baz\""});
+
+  TestWriter(DefineWriter(ESCAPE_NINJA_COMMAND, true),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -DFOO -DBAR=1 \"-DBAZ=\\\\\\\"Baz\\\\\\\"\"",
+#else
+             " -DFOO -DBAR=1 -DBAZ=\\\\\\\"Baz\\\\\\\"",
+#endif
+             {"FOO", "BAR=1", "BAZ=\"Baz\""});
+}
+
+TEST(NinjaTargetCommandUtil, FrameworkDirsWriter) {
+  PathOutput ninja_path_output(SourceDir("//out"), "", ESCAPE_NINJA_COMMAND);
+  TestWriter(FrameworkDirsWriter(ninja_path_output, "-F"),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -F. \"-FPath$ With$ Spaces\"",
+#else
+             " -F. -FPath\\$ With\\$ Spaces",
+#endif
+             {SourceDir("//out"), SourceDir("//out/Path With Spaces")});
+
+  PathOutput space_path_output(SourceDir("//out"), "", ESCAPE_SPACE);
+  TestWriter(FrameworkDirsWriter(space_path_output, "-F"),
+             " -F. -FPath\\ With\\ Spaces",
+             {SourceDir("//out"), SourceDir("//out/Path With Spaces")});
+}
+
+TEST(NinjaTargetCommandUtil, FrameworksWriter) {
+  TestWriter(FrameworksWriter("-framework "),
+// Escaping is different between Windows and Posix.
+#if defined(OS_WIN)
+             " -framework Foundation -framework \"Name$ With$ Spaces\"",
+#else
+             " -framework Foundation -framework Name\\$ With\\$ Spaces",
+#endif
+             {"Foundation.framework", "Name With Spaces.framework"});
+
+  TestWriter(FrameworksWriter(ESCAPE_SPACE, true, "-framework "),
+             " -framework Foundation -framework Name\\ With\\ Spaces",
+             {"Foundation.framework", "Name With Spaces.framework"});
+}
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 0d76159..0b5402e 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -368,6 +368,10 @@
     const ConfigValues& cur = iter.cur();
     all_lib_dirs_.append(cur.lib_dirs().begin(), cur.lib_dirs().end());
     all_libs_.append(cur.libs().begin(), cur.libs().end());
+
+    all_framework_dirs_.append(cur.framework_dirs().begin(),
+                               cur.framework_dirs().end());
+    all_frameworks_.append(cur.frameworks().begin(), cur.frameworks().end());
   }
 
   PullRecursiveBundleData();
@@ -533,6 +537,11 @@
       dep->output_type() == RUST_PROC_MACRO)
     inherited_libraries_.Append(dep, is_public);
 
+  if (dep->output_type() == CREATE_BUNDLE &&
+      dep->bundle_data().is_framework()) {
+    inherited_libraries_.Append(dep, is_public);
+  }
+
   if (dep->output_type() == SHARED_LIBRARY) {
     // Shared library dependendencies are inherited across public shared
     // library boundaries.
@@ -579,6 +588,9 @@
   if (!dep->IsFinal() || dep->output_type() == STATIC_LIBRARY) {
     all_lib_dirs_.append(dep->all_lib_dirs());
     all_libs_.append(dep->all_libs());
+
+    all_framework_dirs_.append(dep->all_framework_dirs());
+    all_frameworks_.append(dep->all_frameworks());
   }
 }
 
diff --git a/src/gn/target.h b/src/gn/target.h
index b86a893..fbe0f71 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -277,6 +277,13 @@
   const OrderedSet<SourceDir>& all_lib_dirs() const { return all_lib_dirs_; }
   const OrderedSet<LibFile>& all_libs() const { return all_libs_; }
 
+  const OrderedSet<SourceDir>& all_framework_dirs() const {
+    return all_framework_dirs_;
+  }
+  const OrderedSet<std::string>& all_frameworks() const {
+    return all_frameworks_;
+  }
+
   const std::set<const Target*>& recursive_hard_deps() const {
     return recursive_hard_deps_;
   }
@@ -408,6 +415,11 @@
   OrderedSet<SourceDir> all_lib_dirs_;
   OrderedSet<LibFile> all_libs_;
 
+  // These frameworks and dirs are inherited from statically linked deps and
+  // all configs applying to this target.
+  OrderedSet<SourceDir> all_framework_dirs_;
+  OrderedSet<std::string> all_frameworks_;
+
   // All hard deps from this target and all dependencies. Filled in when this
   // target is marked resolved. This will not include the current target.
   std::set<const Target*> recursive_hard_deps_;
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index fbac9aa..3844d04 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -82,6 +82,52 @@
   EXPECT_EQ(0u, exec.all_lib_dirs().size());
 }
 
+// Tests that framework[_dir]s are inherited across deps boundaries for static
+// libraries but not executables.
+TEST_F(TargetTest, FrameworkInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  const std::string framework("Foo.framework");
+  const SourceDir frameworkdir("//out/foo/");
+
+  // Leaf target with ldflags set.
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
+  z.config_values().frameworks().push_back(framework);
+  z.config_values().framework_dirs().push_back(frameworkdir);
+  ASSERT_TRUE(z.OnResolved(&err));
+
+  // All framework[_dir]s should be set when target is resolved.
+  ASSERT_EQ(1u, z.all_frameworks().size());
+  EXPECT_EQ(framework, z.all_frameworks()[0]);
+  ASSERT_EQ(1u, z.all_framework_dirs().size());
+  EXPECT_EQ(frameworkdir, z.all_framework_dirs()[0]);
+
+  // Shared library target should inherit the libs from the static library
+  // and its own. Its own flag should be before the inherited one.
+  const std::string second_framework("Bar.framework");
+  const SourceDir second_frameworkdir("//out/bar/");
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
+  shared.config_values().frameworks().push_back(second_framework);
+  shared.config_values().framework_dirs().push_back(second_frameworkdir);
+  shared.private_deps().push_back(LabelTargetPair(&z));
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  ASSERT_EQ(2u, shared.all_frameworks().size());
+  EXPECT_EQ(second_framework, shared.all_frameworks()[0]);
+  EXPECT_EQ(framework, shared.all_frameworks()[1]);
+  ASSERT_EQ(2u, shared.all_framework_dirs().size());
+  EXPECT_EQ(second_frameworkdir, shared.all_framework_dirs()[0]);
+  EXPECT_EQ(frameworkdir, shared.all_framework_dirs()[1]);
+
+  // Executable target shouldn't get either by depending on shared.
+  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
+  exec.private_deps().push_back(LabelTargetPair(&shared));
+  ASSERT_TRUE(exec.OnResolved(&err));
+  EXPECT_EQ(0u, exec.all_frameworks().size());
+  EXPECT_EQ(0u, exec.all_framework_dirs().size());
+}
+
 // Test all_dependent_configs and public_config inheritance.
 TEST_F(TargetTest, DependentConfigs) {
   TestWithScope setup;
diff --git a/src/gn/tool.h b/src/gn/tool.h
index b83d416..655e2b7 100644
--- a/src/gn/tool.h
+++ b/src/gn/tool.h
@@ -129,6 +129,14 @@
     framework_switch_ = std::move(s);
   }
 
+  const std::string& framework_dir_switch() const {
+    return framework_dir_switch_;
+  }
+  void set_framework_dir_switch(std::string s) {
+    DCHECK(!complete_);
+    framework_dir_switch_ = std::move(s);
+  }
+
   const std::string& lib_switch() const { return lib_switch_; }
   void set_lib_switch(std::string s) {
     DCHECK(!complete_);
@@ -251,6 +259,7 @@
   SubstitutionPattern depfile_;
   SubstitutionPattern description_;
   std::string framework_switch_;
+  std::string framework_dir_switch_;
   std::string lib_switch_;
   std::string lib_dir_switch_;
   std::string linker_arg_;
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index f6ce4dc..08509ea 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -1184,6 +1184,40 @@
   }
 )";
 
+const char kFrameworkDirs[] = "framework_dirs";
+const char kFrameworkDirs_HelpShort[] =
+    "framework_dirs: [directory list] Additional framework search directories.";
+const char kFrameworkDirs_Help[] =
+    R"(framework_dirs: [directory list] Additional framework search directories.
+
+  A list of source directories.
+
+  The directories in this list will be added to the framework search path for
+  the files in the affected target.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  framework_dirs = [ "src/include", "//third_party/foo" ]
+)";
+
+const char kFrameworks[] = "frameworks";
+const char kFrameworks_HelpShort[] =
+    "frameworks: [name list] Name of frameworks that must be linked.";
+const char kFrameworks_Help[] =
+    R"(frameworks: [name list] Name of frameworks that must be linked.
+
+  A list of framework names.
+
+  The frameworks named in that list will be linked with any dynamic link
+  type target.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  frameworks = [ "Foundation.framework", "Foo.framework" ]
+)";
+
 const char kIncludeDirs[] = "include_dirs";
 const char kIncludeDirs_HelpShort[] =
     "include_dirs: [directory list] Additional include directories.";
@@ -2204,6 +2238,8 @@
     INSERT_VARIABLE(Deps)
     INSERT_VARIABLE(Externs)
     INSERT_VARIABLE(Friend)
+    INSERT_VARIABLE(FrameworkDirs)
+    INSERT_VARIABLE(Frameworks)
     INSERT_VARIABLE(IncludeDirs)
     INSERT_VARIABLE(Inputs)
     INSERT_VARIABLE(Ldflags)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index 93a97a6..c684caf 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -202,6 +202,14 @@
 extern const char kFriend_HelpShort[];
 extern const char kFriend_Help[];
 
+extern const char kFrameworkDirs[];
+extern const char kFrameworkDirs_HelpShort[];
+extern const char kFrameworkDirs_Help[];
+
+extern const char kFrameworks[];
+extern const char kFrameworks_HelpShort[];
+extern const char kFrameworks_Help[];
+
 extern const char kIncludeDirs[];
 extern const char kIncludeDirs_HelpShort[];
 extern const char kIncludeDirs_Help[];