Add weak_frameworks linking option

The -weak_framework linker flag adds a dynamic library dependency to the
output, but it instructs the loader to skip loading the library if it is
not present and leave the external symbols unresolved. This is typically
used when a framework is present in a new version of an SDK that is not
present on an older OS. See
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
for details.

Currently users of gn implement this using a config, but due to the way
configs propagate, the -weak_link ldflags get lost at static_library
boundaries. Having first-class support for weakly linked frameworks
simplifies that.

Bug: chromium:1082896
Change-Id: Ia93bf1b67e5b9205073b8f2bf3ed9b96e3af79e4
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8464
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index afd928c..92d1f57 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -151,6 +151,7 @@
     *   [testonly: [boolean] Declares a target must only be used for testing.](#var_testonly)
     *   [visibility: [label list] A list of labels that can depend on a target.](#var_visibility)
     *   [walk_keys: [string list] Key(s) for managing the metadata collection walk.](#var_walk_keys)
+    *   [weak_frameworks: [name list] Name of frameworks that must be weak linked.](#var_weak_frameworks)
     *   [write_runtime_deps: Writes the target's runtime_deps to the given path.](#var_write_runtime_deps)
     *   [xcode_extra_attributes: [scope] Extra attributes for Xcode projects.](#var_xcode_extra_attributes)
     *   [xcode_test_application_name: [string] Name for Xcode test target.](#var_xcode_test_application_name)
@@ -355,10 +356,6 @@
 #### **Command-specific switches**
 
 ```
-  --force
-      Ignores specifications of "check_includes = false" and checks all
-      target's files that match the target label.
-
   --check-generated
       Generated files are normally not checked since they do not exist
       until after a build. With this flag, those generated files that
@@ -367,6 +364,18 @@
   --check-system
      Check system style includes (using <angle brackets>) in addition to
      "double quote" includes.
+
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
+  --force
+      Ignores specifications of "check_includes = false" and checks all
+      target's files that match the target label.
 ```
 
 #### **What gets checked**
@@ -465,7 +474,7 @@
   gn check out/Default "//foo/*
       Check only the files in targets in the //foo directory tree.
 ```
-### <a name="cmd_clean"></a>**gn clean &lt;out_dir&gt;**
+### <a name="cmd_clean"></a>**gn clean &lt;out_dir&gt;...**
 
 ```
   Deletes the contents of the output directory except for args.gn and
@@ -521,6 +530,7 @@
   testonly
   visibility
   walk_keys
+  weak_frameworks
 
   runtime_deps
       Compute all runtime deps for the given target. This is a computed list
@@ -533,15 +543,15 @@
 ```
 
 #### **Shared flags**
-```
-  --all-toolchains
-      Normally only inputs in the default toolchain will be included.
-      This switch will turn on matching all toolchains.
 
-      For example, a file is in a target might be compiled twice:
-      once in the default toolchain and once in a secondary one. Without
-      this flag, only the default toolchain one will be matched by
-      wildcards. With this flag, both will be matched.
+```
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
 
   --format=json
       Format the output as JSON instead of text.
@@ -553,8 +563,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,
-      lib_dirs, frameworks and framework_dirs because those are inherited and
-      are more complicated to figure out the blame (patches welcome).
+      lib_dirs, frameworks, weak_frameworks and framework_dirs because those are
+      inherited and are more complicated to figure out the blame (patches
+      welcome).
 ```
 
 #### **Configs**
@@ -584,6 +595,7 @@
   --all
       Collects all recursive dependencies and prints a sorted flat list. Also
       usable with --tree (see below).
+
   --as=(buildfile|label|output)
       How to print targets.
 
@@ -610,6 +622,7 @@
 
       Tree output can not be used with the filtering or output flags: --as,
       --type, --testonly.
+
   --type=(action|copy|executable|group|loadable_module|shared_library|
           source_set|static_library)
       Restrict outputs to targets matching the given type. If
@@ -810,6 +823,12 @@
 #### **Compilation Database**
 
 ```
+  --export-rust-project
+      Produces a rust-project.json file in the root of the build directory
+      This is used for various tools in the Rust ecosystem allowing for the
+      replay of individual compilations independent of the build system.
+      This is an unstable format and likely to change without warning.
+
   --export-compile-commands[=<target_name1,target_name2...>]
       Produces a compile_commands.json file in the root of the build directory
       containing an array of “command objects”, where each command object
@@ -842,7 +861,7 @@
   gn help --markdown all
       Dump all help to stdout in markdown format.
 ```
-### <a name="cmd_ls"></a>**gn ls &lt;out_dir&gt; [&lt;label_pattern&gt;] [\--all-toolchains] [\--as=...]**
+### <a name="cmd_ls"></a>**gn ls &lt;out_dir&gt; [&lt;label_pattern&gt;] [\--default-toolchain] [\--as=...]**
 ```
       [--type=...] [--testonly=...]
 
@@ -870,14 +889,13 @@
           Prints the first output file for the target relative to the
           root build directory.
 
-  --all-toolchains
-      Normally only inputs in the default toolchain will be included.
-      This switch will turn on matching all toolchains.
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
 
-      For example, a file is in a target might be compiled twice:
-      once in the default toolchain and once in a secondary one. Without
-      this flag, only the default toolchain one will be matched by
-      wildcards. With this flag, both will be matched.
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
 
   --testonly=(true|false)
       Restrict outputs to targets with the testonly flag set
@@ -910,10 +928,6 @@
 
   gn ls out/Debug "//base/*" --as=output | xargs ninja -C out/Debug
       Builds all targets in //base and all subdirectories.
-
-  gn ls out/Debug //base --all-toolchains
-      Lists all variants of the target //base:base (it may be referenced
-      in multiple toolchains).
 ```
 ### <a name="cmd_meta"></a>**gn meta**
 
@@ -1075,7 +1089,7 @@
 
 ```
   gn refs <out_dir> (<label_pattern>|<label>|<file>|@<response_file>)*
-          [--all] [--all-toolchains] [--as=...] [--testonly=...] [--type=...]
+          [--all] [--default-toolchain] [--as=...] [--testonly=...] [--type=...]
 
   Finds reverse dependencies (which targets reference something). The input is
   a list containing:
@@ -1111,14 +1125,6 @@
       directly or indirectly on that file.
 
       When used with --tree, turns off eliding to show a complete tree.
-  --all-toolchains
-      Normally only inputs in the default toolchain will be included.
-      This switch will turn on matching all toolchains.
-
-      For example, a file is in a target might be compiled twice:
-      once in the default toolchain and once in a secondary one. Without
-      this flag, only the default toolchain one will be matched by
-      wildcards. With this flag, both will be matched.
 
   --as=(buildfile|label|output)
       How to print targets.
@@ -1132,10 +1138,19 @@
           Prints the first output file for the target relative to the
           root build directory.
 
+  --default-toolchain
+      Normally wildcard targets are matched in all toolchains. This
+      switch makes wildcard labels with no explicit toolchain reference
+      only match targets in the default toolchain.
+
+      Non-wildcard inputs with no explicit toolchain specification will
+      always match only a target in the default toolchain if one exists.
+
   -q
      Quiet. If nothing matches, don't print any output. Without this option, if
      there are no matches there will be an informational message printed which
      might interfere with scripts processing the output.
+
   --testonly=(true|false)
       Restrict outputs to targets with the testonly flag set
       accordingly. When unspecified, the target's testonly flags are
@@ -1147,6 +1162,7 @@
 
       Tree output can not be used with the filtering or output flags: --as,
       --type, --testonly.
+
   --type=(action|copy|executable|group|loadable_module|shared_library|
           source_set|static_library)
       Restrict outputs to targets matching the given type. If
@@ -1956,8 +1972,7 @@
 ### <a name="func_source_set"></a>**source_set**: Declare a source set target.
 
 ```
-  The language of a source_set target is determined by the extensions present
-  in its sources.
+  Only C-language source sets are supported at the moment.
 ```
 
 #### **C-language source_sets**
@@ -1985,15 +2000,6 @@
   when linking multiple static libraries into a shared library.
 ```
 
-#### **Rust-language source_sets**
-
-```
-  A Rust source set is a collection of sources that get passed along to the
-  final target that depends on it. No compilation is performed, and the source
-  files are simply added as dependencies on the eventual rustc invocation that
-  would produce a binary.
-```
-
 #### **Variables**
 
 ```
@@ -2890,14 +2896,14 @@
   with a double slash like "//foo/bar"), you should use the get_path_info()
   function. This function won't work because it will always make relative
   paths, and it needs to support making paths relative to the source root, so
-  can't also generate source-absolute paths without more special-cases.
+  it can't also generate source-absolute paths without more special-cases.
 ```
 
 #### **Arguments**
 
 ```
   input
-      A string or list of strings representing file or directory names These
+      A string or list of strings representing file or directory names. These
       can be relative paths ("foo/bar.txt"), system absolute paths
       ("/foo/bar.txt"), or source absolute paths ("//foo/bar.txt").
 
@@ -3462,6 +3468,7 @@
           "-lfreetype -lexpat".
 
     framework_switch [string, optional, link tools only]
+    weak_framework_switch [string, optional, link tools only]
     framework_dir_switch [string, optional, link tools only]
         Valid for: Linker tools
 
@@ -3471,11 +3478,14 @@
 
         If you specified:
           framework_switch = "-framework "
+          weak_framework_switch = "-weak_framework "
           framework_dir_switch = "-F"
-        then the "{{libs}}" expansion for
-          [ "UIKit.framework", "$root_out_dir/Foo.framework" ]
-        would be
-          "-framework UIKit -F. -framework Foo"
+        and:
+          framework_dirs = [ "$root_out_dir" ]
+          frameworks = [ "UIKit.framework", "Foo.framework" ]
+          weak_frameworks = [ "MediaPlayer.framework" ]
+        would be:
+          "-F. -framework UIKit -framework Foo -weak_framework MediaPlayer"
 
     outputs  [list of strings with substitutions]
         Valid for: Linker and compiler tools (required)
@@ -3757,7 +3767,8 @@
         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".
+        {{frameworks}} and each item will be preceded by "-framework" or
+        "-weak_framework".
 
   The static library ("alink") tool allows {{arflags}} plus the common tool
   substitutions.
@@ -6382,6 +6393,42 @@
 
   See "gn help generated_file".
 ```
+### <a name="var_weak_frameworks"></a>**weak_frameworks**: [name list] Name of frameworks that must be weak linked.
+
+```
+  A list of framework names.
+
+  The frameworks named in that list will be weak linked with any dynamic link
+  type target. Weak linking instructs the dynamic loader to attempt to load
+  the framework, but if it is not able to do so, it leaves any imported symbols
+  unresolved. This is typically used when a framework is present in a new
+  version of an SDK but not on older versions of the OS that the software runs
+  on.
+```
+
+#### **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**
+
+```
+  weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
+```
 ### <a name="var_write_runtime_deps"></a>**write_runtime_deps**: Writes the target's runtime_deps to the given path.
 
 ```
diff --git a/src/gn/c_tool.cc b/src/gn/c_tool.cc
index 4046e33..7c06364 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_weak_framework_switch("-weak_framework ");
   set_framework_dir_switch("-F");
   set_lib_dir_switch("-L");
   set_lib_switch("-l");
@@ -184,6 +185,8 @@
 
   if (!ReadDepsFormat(scope, err) || !ReadPrecompiledHeaderType(scope, err) ||
       !ReadString(scope, "framework_switch", &framework_switch_, err) ||
+      !ReadString(scope, "weak_framework_switch", &weak_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) ||
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index 081ae83..278d45d 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -299,6 +299,7 @@
           {variables::kDataKeys, DefaultHandler},
           {variables::kRebase, DefaultHandler},
           {variables::kWalkKeys, DefaultHandler},
+          {variables::kWeakFrameworks, DefaultHandler},
           {variables::kWriteOutputConversion, DefaultHandler},
           {"runtime_deps", DefaultHandler}};
 }
@@ -386,6 +387,7 @@
   HandleProperty(variables::kDataKeys, handler_map, v, dict);
   HandleProperty(variables::kRebase, handler_map, v, dict);
   HandleProperty(variables::kWalkKeys, handler_map, v, dict);
+  HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
   HandleProperty(variables::kWriteOutputConversion, handler_map, v, dict);
 
 #undef HandleProperty
@@ -447,6 +449,7 @@
   HandleProperty(variables::kLibDirs, handler_map, v, dict);
   HandleProperty(variables::kPrecompiledHeader, handler_map, v, dict);
   HandleProperty(variables::kPrecompiledSource, handler_map, v, dict);
+  HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
 
 #undef HandleProperty
 
@@ -508,6 +511,7 @@
   testonly
   visibility
   walk_keys
+  weak_frameworks
 
   runtime_deps
       Compute all runtime deps for the given target. This is a computed list
@@ -533,8 +537,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,
-      lib_dirs, frameworks and framework_dirs because those are inherited and
-      are more complicated to figure out the blame (patches welcome).
+      lib_dirs, frameworks, weak_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 df5e73f..8d6c7fb 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -85,6 +85,9 @@
   flags.frameworks = FlagsGetter<std::string>(
       target, &ConfigValues::frameworks,
       FrameworksWriter(ESCAPE_SPACE, true, "-framework"));
+  flags.frameworks += FlagsGetter<std::string>(
+      target, &ConfigValues::weak_frameworks,
+      FrameworksWriter(ESCAPE_SPACE, true, "-weak_framework"));
 
   flags.includes = FlagsGetter<SourceDir>(target, &ConfigValues::include_dirs,
                                           IncludeWriter(path_output));
diff --git a/src/gn/config_values.cc b/src/gn/config_values.cc
index d4f597e..ba5ddf7 100644
--- a/src/gn/config_values.cc
+++ b/src/gn/config_values.cc
@@ -30,6 +30,7 @@
   VectorAppend(&cflags_objcc_, append.cflags_objcc_);
   VectorAppend(&defines_, append.defines_);
   VectorAppend(&frameworks_, append.frameworks_);
+  VectorAppend(&weak_frameworks_, append.weak_frameworks_);
   VectorAppend(&framework_dirs_, append.framework_dirs_);
   VectorAppend(&include_dirs_, append.include_dirs_);
   VectorAppend(&inputs_, append.inputs_);
diff --git a/src/gn/config_values.h b/src/gn/config_values.h
index efd70eb..ef57b26 100644
--- a/src/gn/config_values.h
+++ b/src/gn/config_values.h
@@ -44,6 +44,7 @@
   STRING_VALUES_ACCESSOR(defines)
   DIR_VALUES_ACCESSOR(framework_dirs)
   STRING_VALUES_ACCESSOR(frameworks)
+  STRING_VALUES_ACCESSOR(weak_frameworks)
   DIR_VALUES_ACCESSOR(include_dirs)
   STRING_VALUES_ACCESSOR(ldflags)
   DIR_VALUES_ACCESSOR(lib_dirs)
@@ -88,6 +89,7 @@
   std::vector<SourceDir> include_dirs_;
   std::vector<SourceDir> framework_dirs_;
   std::vector<std::string> frameworks_;
+  std::vector<std::string> weak_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 52ef61f..c8070d2 100644
--- a/src/gn/config_values_generator.cc
+++ b/src/gn/config_values_generator.cc
@@ -44,6 +44,34 @@
   (config_values->*accessor)().swap(result);
 }
 
+void GetFrameworksList(Scope* scope,
+                       const char* var_name,
+                       ConfigValues* config_values,
+                       std::vector<std::string>& (ConfigValues::*accessor)(),
+                       Err* err) {
+  const Value* value = scope->GetValue(var_name, true);
+  if (!value)
+    return;
+
+  std::vector<std::string> frameworks;
+  if (!ExtractListOfStringValues(*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(*value,
+                 "This frameworks value is wrong."
+                 "All listed frameworks names must not include any\n"
+                 "path component and have \".framework\" extension.");
+      return;
+    }
+  }
+
+  (config_values->*accessor)().swap(frameworks);
+}
+
 }  // namespace
 
 ConfigValuesGenerator::ConfigValuesGenerator(ConfigValues* dest_values,
@@ -105,27 +133,10 @@
   }
 
   // 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);
-  }
+  GetFrameworksList(scope_, variables::kFrameworks, config_values_,
+                    &ConfigValues::frameworks, err_);
+  GetFrameworksList(scope_, variables::kWeakFrameworks, config_values_,
+                    &ConfigValues::weak_frameworks, err_);
 
   // Precompiled headers.
   const Value* precompiled_header_value =
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 8ef77a6..9590975 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -569,6 +569,16 @@
                                      std::move(frameworks));
       }
     }
+    if (what(variables::kWeakFrameworks)) {
+      const auto& weak_frameworks = target_->all_weak_frameworks();
+      if (!weak_frameworks.empty()) {
+        auto frameworks = std::make_unique<base::ListValue>();
+        for (size_t i = 0; i < weak_frameworks.size(); i++)
+          frameworks->AppendString(weak_frameworks[i]);
+        res->SetWithoutPathExpansion(variables::kWeakFrameworks,
+                                     std::move(frameworks));
+      }
+    }
 
     if (what(variables::kFrameworkDirs)) {
       const auto& all_framework_dirs = target_->all_framework_dirs();
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
index 9efff8e..92721dc 100644
--- a/src/gn/function_toolchain.cc
+++ b/src/gn/function_toolchain.cc
@@ -408,6 +408,7 @@
           "-lfreetype -lexpat".
 
     framework_switch [string, optional, link tools only]
+    weak_framework_switch [string, optional, link tools only]
     framework_dir_switch [string, optional, link tools only]
         Valid for: Linker tools
 
@@ -417,11 +418,14 @@
 
         If you specified:
           framework_switch = "-framework "
+          weak_framework_switch = "-weak_framework "
           framework_dir_switch = "-F"
-        then the "{{libs}}" expansion for
-          [ "UIKit.framework", "$root_out_dir/Foo.framework" ]
-        would be
-          "-framework UIKit -F. -framework Foo"
+        and:
+          framework_dirs = [ "$root_out_dir" ]
+          frameworks = [ "UIKit.framework", "Foo.framework" ]
+          weak_frameworks = [ "MediaPlayer.framework" ]
+        would be:
+          "-F. -framework UIKit -framework Foo -weak_framework MediaPlayer"
 
     outputs  [list of strings with substitutions]
         Valid for: Linker and compiler tools (required)
@@ -702,7 +706,8 @@
         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".
+        {{frameworks}} and each item will be preceded by "-framework" or
+        "-weak_framework".
 
 )"  // String break to prevent overflowing the 16K max VC string length.
     R"(  The static library ("alink") tool allows {{arflags}} plus the common tool
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index cde6be6..2afb3db 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -341,11 +341,16 @@
 
 void NinjaBinaryTargetWriter::WriteFrameworks(std::ostream& out,
                                               const Tool* tool) {
-  FrameworksWriter writer(tool->framework_switch());
-
   // Frameworks that have been recursively pushed through the dependency tree.
+  FrameworksWriter writer(tool->framework_switch());
   const auto& all_frameworks = target_->all_frameworks();
   for (size_t i = 0; i < all_frameworks.size(); i++) {
     writer(all_frameworks[i], out);
   }
+
+  FrameworksWriter weak_writer(tool->weak_framework_switch());
+  const auto& all_weak_frameworks = target_->all_weak_frameworks();
+  for (size_t i = 0; i < all_weak_frameworks.size(); i++) {
+    weak_writer(all_weak_frameworks[i], out);
+  }
 }
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 315232b..bc9907a 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -540,6 +540,7 @@
   Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
   target.set_output_type(Target::SHARED_LIBRARY);
   target.config_values().frameworks().push_back("System.framework");
+  target.config_values().weak_frameworks().push_back("Whizbang.framework");
   target.private_deps().push_back(LabelTargetPair(&framework));
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
@@ -559,7 +560,8 @@
       "build ./libshlib.so: solink | obj/bar/framework.stamp\n"
       "  ldflags = -F.\n"
       "  libs =\n"
-      "  frameworks = -framework System -framework Bar\n"
+      "  frameworks = -framework System -framework Bar "
+      "-weak_framework Whizbang\n"
       "  output_extension = .so\n"
       "  output_dir = \n";
 
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 115ab8e..1b26c06 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -372,6 +372,8 @@
     all_framework_dirs_.append(cur.framework_dirs().begin(),
                                cur.framework_dirs().end());
     all_frameworks_.append(cur.frameworks().begin(), cur.frameworks().end());
+    all_weak_frameworks_.append(cur.weak_frameworks().begin(),
+                                cur.weak_frameworks().end());
   }
 
   PullRecursiveBundleData();
@@ -701,6 +703,7 @@
 
     all_framework_dirs_.append(dep->all_framework_dirs());
     all_frameworks_.append(dep->all_frameworks());
+    all_weak_frameworks_.append(dep->all_weak_frameworks());
   }
 }
 
diff --git a/src/gn/target.h b/src/gn/target.h
index 44f7e17..6332880 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -283,6 +283,9 @@
   const OrderedSet<std::string>& all_frameworks() const {
     return all_frameworks_;
   }
+  const OrderedSet<std::string>& all_weak_frameworks() const {
+    return all_weak_frameworks_;
+  }
 
   const std::set<const Target*>& recursive_hard_deps() const {
     return recursive_hard_deps_;
@@ -451,6 +454,7 @@
   // all configs applying to this target.
   OrderedSet<SourceDir> all_framework_dirs_;
   OrderedSet<std::string> all_frameworks_;
+  OrderedSet<std::string> all_weak_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.
diff --git a/src/gn/tool.h b/src/gn/tool.h
index 655e2b7..7fe6ed5 100644
--- a/src/gn/tool.h
+++ b/src/gn/tool.h
@@ -129,6 +129,14 @@
     framework_switch_ = std::move(s);
   }
 
+  const std::string& weak_framework_switch() const {
+    return weak_framework_switch_;
+  }
+  void set_weak_framework_switch(std::string s) {
+    DCHECK(!complete_);
+    weak_framework_switch_ = std::move(s);
+  }
+
   const std::string& framework_dir_switch() const {
     return framework_dir_switch_;
   }
@@ -259,6 +267,7 @@
   SubstitutionPattern depfile_;
   SubstitutionPattern description_;
   std::string framework_switch_;
+  std::string weak_framework_switch_;
   std::string framework_dir_switch_;
   std::string lib_switch_;
   std::string lib_dir_switch_;
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index 8a1ec2a..daabdb5 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -2140,6 +2140,27 @@
   See "gn help generated_file".
 )";
 
+const char kWeakFrameworks[] = "weak_frameworks";
+const char kWeakFrameworks_HelpShort[] =
+    "weak_frameworks: [name list] Name of frameworks that must be weak linked.";
+const char kWeakFrameworks_Help[] =
+    R"(weak_frameworks: [name list] Name of frameworks that must be weak linked.
+
+  A list of framework names.
+
+  The frameworks named in that list will be weak linked with any dynamic link
+  type target. Weak linking instructs the dynamic loader to attempt to load
+  the framework, but if it is not able to do so, it leaves any imported symbols
+  unresolved. This is typically used when a framework is present in a new
+  version of an SDK but not on older versions of the OS that the software runs
+  on.
+)" COMMON_ORDERING_HELP
+    R"(
+Example
+
+  weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
+)";
+
 const char kWriteValueContents[] = "contents";
 const char kWriteValueContents_HelpShort[] =
     "contents: Contents to write to file.";
@@ -2296,6 +2317,7 @@
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
     INSERT_VARIABLE(WalkKeys)
+    INSERT_VARIABLE(WeakFrameworks)
     INSERT_VARIABLE(WriteOutputConversion)
     INSERT_VARIABLE(WriteValueContents)
     INSERT_VARIABLE(WriteRuntimeDeps)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index dca4e34..acd78af 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -330,6 +330,10 @@
 extern const char kWalkKeys_HelpShort[];
 extern const char kWalkKeys_Help[];
 
+extern const char kWeakFrameworks[];
+extern const char kWeakFrameworks_HelpShort[];
+extern const char kWeakFrameworks_Help[];
+
 extern const char kWriteValueContents[];
 extern const char kWriteValueContents_HelpShort[];
 extern const char kWriteValueContents_Help[];