Support weak_libraries
Sometimes we need to link to libraries that are only supported in newer versions of macOS,
or symbols that only exist in newer versions of macOS. The `-weak_library` linking parameter
can help us achieve this. When a library file or symbol is missing, dyld will bind the symbol
to NULL (instead of immediately exiting the program), allowing the application to determine
at runtime whether to use the new feature.
Related to https://gn-review.googlesource.com/c/gn/+/6660
Unlike -weak_framework, -weak_library requires a library file path, not
a name.
Reference: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
Change-Id: I6fa09f798108a805456b2def6d2edc56d36d100b
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/20320
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 489dfbb..61a227a 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -167,6 +167,7 @@
* [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)
+ * [weak_libraries: [file list] File of libraries that must be weak linked.](#var_weak_libraries)
* [write_runtime_deps: Writes the target's runtime_deps to the given path.](#var_write_runtime_deps)
* [xcasset_compiler_flags: [string list] Flags passed to xcassets compiler](#var_xcasset_compiler_flags)
* [xcode_extra_attributes: [scope] Extra attributes for Xcode projects.](#var_xcode_extra_attributes)
@@ -7037,6 +7038,42 @@
```
weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
```
+### <a name="var_weak_libraries"></a>**weak_libraries**: [file list] File of libraries that must be weak linked. [Back to Top](#gn-reference)
+
+```
+ A list of library files.
+
+ The library files in that list will be weak linked with any dynamic link
+ type target. Weak linking instructs the dynamic loader to attempt to load
+ the library, but if it is not able to do so, it leaves any imported symbols
+ unresolved. This is typically used when a library 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 occurrence 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_libraries = [ rebase_path("//path/to/libOnlyOnNewerOSes.dylib") ]
+```
### <a name="var_write_runtime_deps"></a>**write_runtime_deps**: Writes the target's runtime_deps to the given path. [Back to Top](#gn-reference)
```
diff --git a/src/gn/c_tool.cc b/src/gn/c_tool.cc
index e0bea41..3817f82 100644
--- a/src/gn/c_tool.cc
+++ b/src/gn/c_tool.cc
@@ -26,6 +26,7 @@
CHECK(ValidateName(n));
set_framework_switch("-framework ");
set_weak_framework_switch("-weak_framework ");
+ set_weak_library_switch("-weak_library ");
set_framework_dir_switch("-F");
set_lib_dir_switch("-L");
set_lib_switch("-l");
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index b50d4e9..491afa3 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -303,6 +303,7 @@
{variables::kRebase, DefaultHandler},
{variables::kWalkKeys, DefaultHandler},
{variables::kWeakFrameworks, DefaultHandler},
+ {variables::kWeakLibraries, DefaultHandler},
{variables::kWriteOutputConversion, DefaultHandler},
{variables::kRustCrateName, DefaultHandler},
{variables::kRustCrateRoot, DefaultHandler},
@@ -403,6 +404,7 @@
HandleProperty(variables::kRustflags, handler_map, v, dict);
HandleProperty(variables::kWalkKeys, handler_map, v, dict);
HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
+ HandleProperty(variables::kWeakLibraries, handler_map, v, dict);
HandleProperty(variables::kWriteOutputConversion, handler_map, v, dict);
#undef HandleProperty
@@ -467,6 +469,7 @@
HandleProperty(variables::kPrecompiledSource, handler_map, v, dict);
HandleProperty(variables::kRustflags, handler_map, v, dict);
HandleProperty(variables::kWeakFrameworks, handler_map, v, dict);
+ HandleProperty(variables::kWeakLibraries, handler_map, v, dict);
#undef HandleProperty
@@ -529,6 +532,7 @@
visibility
walk_keys
weak_frameworks
+ weak_libraries
runtime_deps
Compute all runtime deps for the given target. This is a computed list
diff --git a/src/gn/config_values.cc b/src/gn/config_values.cc
index 9bddf99..3bab686 100644
--- a/src/gn/config_values.cc
+++ b/src/gn/config_values.cc
@@ -31,6 +31,7 @@
VectorAppend(&defines_, append.defines_);
VectorAppend(&frameworks_, append.frameworks_);
VectorAppend(&weak_frameworks_, append.weak_frameworks_);
+ VectorAppend(&weak_libraries_, append.weak_libraries_);
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 4a86562..24a235a 100644
--- a/src/gn/config_values.h
+++ b/src/gn/config_values.h
@@ -44,6 +44,7 @@
DIR_VALUES_ACCESSOR(framework_dirs)
STRING_VALUES_ACCESSOR(frameworks)
STRING_VALUES_ACCESSOR(weak_frameworks)
+ STRING_VALUES_ACCESSOR(weak_libraries)
DIR_VALUES_ACCESSOR(include_dirs)
STRING_VALUES_ACCESSOR(ldflags)
DIR_VALUES_ACCESSOR(lib_dirs)
@@ -90,6 +91,7 @@
std::vector<SourceDir> framework_dirs_;
std::vector<std::string> frameworks_;
std::vector<std::string> weak_frameworks_;
+ std::vector<std::string> weak_libraries_;
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 a2ae225..cf91a8c 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/filesystem_utils.h"
#include "gn/frameworks_utils.h"
#include "gn/scope.h"
#include "gn/settings.h"
@@ -72,6 +73,34 @@
(config_values->*accessor)().swap(frameworks);
}
+void GetWeakLibrariesList(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> weak_libraries;
+ if (!ExtractListOfStringValues(*value, &weak_libraries, err))
+ return;
+
+ // All strings must end with ".dylib".
+ for (const std::string& weak_library : weak_libraries) {
+ std::string_view extension = FindExtension(&weak_library);
+ if (extension != "dylib") {
+ *err = Err(
+ *value,
+ "This weak_libraries value is wrong. "
+ "All listed weak_libraries files must have \".dylib\" extension.");
+ return;
+ }
+ }
+
+ (config_values->*accessor)().swap(weak_libraries);
+}
+
} // namespace
ConfigValuesGenerator::ConfigValuesGenerator(ConfigValues* dest_values,
@@ -138,6 +167,8 @@
&ConfigValues::frameworks, err_);
GetFrameworksList(scope_, variables::kWeakFrameworks, config_values_,
&ConfigValues::weak_frameworks, err_);
+ GetWeakLibrariesList(scope_, variables::kWeakLibraries, config_values_,
+ &ConfigValues::weak_libraries, err_);
// Precompiled headers.
const Value* precompiled_header_value =
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index def45a3..1b80302 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -628,6 +628,16 @@
std::move(frameworks));
}
}
+ if (what(variables::kWeakLibraries)) {
+ const auto& weak_libraries = resolved.GetLinkedWeakLibraries(target_);
+ if (!weak_libraries.empty()) {
+ auto libraries = std::make_unique<base::ListValue>();
+ for (const auto& weak_library : weak_libraries)
+ libraries->AppendString(weak_library);
+ res->SetWithoutPathExpansion(variables::kWeakLibraries,
+ std::move(libraries));
+ }
+ }
if (what(variables::kFrameworkDirs)) {
const auto& all_framework_dirs = resolved.GetLinkedFrameworkDirs(target_);
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index 585af9c..ac45e97 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -405,6 +405,14 @@
for (size_t i = 0; i < all_weak_frameworks.size(); i++) {
weak_writer(all_weak_frameworks[i], out);
}
+
+ if (!tool->weak_library_switch().empty()) {
+ WeakLibrariesWriter weak_library_writer(tool->weak_library_switch());
+ const auto& all_weak_libraries = resolved().GetLinkedWeakLibraries(target_);
+ for (const auto& weak_library : all_weak_libraries) {
+ weak_library_writer(weak_library, out);
+ }
+ }
}
void NinjaBinaryTargetWriter::WriteSwiftModules(
diff --git a/src/gn/ninja_target_command_util.h b/src/gn/ninja_target_command_util.h
index 93666fb..1ec2e33 100644
--- a/src/gn/ninja_target_command_util.h
+++ b/src/gn/ninja_target_command_util.h
@@ -67,6 +67,23 @@
std::string tool_switch_;
};
+struct WeakLibrariesWriter {
+ explicit WeakLibrariesWriter(std::string tool_switch)
+ : WeakLibrariesWriter(ESCAPE_NINJA_COMMAND, std::move(tool_switch)) {}
+ WeakLibrariesWriter(EscapingMode mode, std::string tool_switch)
+ : tool_switch_(std::move(tool_switch)) {
+ options_.mode = mode;
+ }
+
+ void operator()(std::string_view s, std::ostream& out) const {
+ out << " " << tool_switch_;
+ EscapeStringToStream(out, s, options_);
+ }
+
+ EscapeOptions options_;
+ std::string tool_switch_;
+};
+
struct IncludeWriter {
explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) {}
~IncludeWriter() = default;
diff --git a/src/gn/resolved_target_data.cc b/src/gn/resolved_target_data.cc
index 201f655..efe65ce 100644
--- a/src/gn/resolved_target_data.cc
+++ b/src/gn/resolved_target_data.cc
@@ -41,12 +41,14 @@
UniqueVector<SourceDir> all_framework_dirs;
UniqueVector<std::string> all_frameworks;
UniqueVector<std::string> all_weak_frameworks;
+ UniqueVector<std::string> all_weak_libraries;
for (ConfigValuesIterator iter(info->target); !iter.done(); iter.Next()) {
const ConfigValues& cur = iter.cur();
all_framework_dirs.Append(cur.framework_dirs());
all_frameworks.Append(cur.frameworks());
all_weak_frameworks.Append(cur.weak_frameworks());
+ all_weak_libraries.Append(cur.weak_libraries());
}
for (const Target* dep : info->deps.linked_deps()) {
if (!dep->IsFinal() || dep->output_type() == Target::STATIC_LIBRARY) {
@@ -54,12 +56,14 @@
all_framework_dirs.Append(dep_info->framework_dirs);
all_frameworks.Append(dep_info->frameworks);
all_weak_frameworks.Append(dep_info->weak_frameworks);
+ all_weak_libraries.Append(dep_info->weak_libraries);
}
}
info->framework_dirs = all_framework_dirs.release();
info->frameworks = all_frameworks.release();
info->weak_frameworks = all_weak_frameworks.release();
+ info->weak_libraries = all_weak_libraries.release();
info->has_framework_info = true;
}
diff --git a/src/gn/resolved_target_data.h b/src/gn/resolved_target_data.h
index a2743fc..febbf39 100644
--- a/src/gn/resolved_target_data.h
+++ b/src/gn/resolved_target_data.h
@@ -96,6 +96,13 @@
return GetTargetFrameworkInfo(target)->weak_frameworks;
}
+ // The list of weak library files to use at link time when generating macOS
+ // or iOS linkable binaries.
+ const std::vector<std::string>& GetLinkedWeakLibraries(
+ const Target* target) const {
+ return GetTargetFrameworkInfo(target)->weak_libraries;
+ }
+
// Retrieves a set of hard dependencies for this target.
// All hard deps from this target and all dependencies, but not the
// target itself.
@@ -165,6 +172,7 @@
std::vector<SourceDir> framework_dirs;
std::vector<std::string> frameworks;
std::vector<std::string> weak_frameworks;
+ std::vector<std::string> weak_libraries;
// Only valid if |has_hard_deps| is true.
TargetSet hard_deps;
diff --git a/src/gn/tool.h b/src/gn/tool.h
index b95c64b..68e919c 100644
--- a/src/gn/tool.h
+++ b/src/gn/tool.h
@@ -137,6 +137,14 @@
weak_framework_switch_ = std::move(s);
}
+ const std::string& weak_library_switch() const {
+ return weak_library_switch_;
+ }
+ void set_weak_library_switch(std::string s) {
+ DCHECK(!complete_);
+ weak_library_switch_ = std::move(s);
+ }
+
const std::string& framework_dir_switch() const {
return framework_dir_switch_;
}
@@ -281,6 +289,7 @@
SubstitutionPattern description_;
std::string framework_switch_;
std::string weak_framework_switch_;
+ std::string weak_library_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 1754172..2c6f8e4 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -2296,6 +2296,27 @@
weak_frameworks = [ "OnlyOnNewerOSes.framework" ]
)";
+const char kWeakLibraries[] = "weak_libraries";
+const char kWeakLibraries_HelpShort[] =
+ "weak_libraries: [file list] File of libraries that must be weak linked.";
+const char kWeakLibraries_Help[] =
+ R"(weak_libraries: [file list] File of libraries that must be weak linked.
+
+ A list of library files.
+
+ The library files in that list will be weak linked with any dynamic link
+ type target. Weak linking instructs the dynamic loader to attempt to load
+ the library, but if it is not able to do so, it leaves any imported symbols
+ unresolved. This is typically used when a library 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_libraries = [ rebase_path("//path/to/libOnlyOnNewerOSes.dylib") ]
+)";
+
const char kWriteValueContents[] = "contents";
const char kWriteValueContents_HelpShort[] =
"contents: Contents to write to file.";
@@ -2459,6 +2480,7 @@
INSERT_VARIABLE(Visibility)
INSERT_VARIABLE(WalkKeys)
INSERT_VARIABLE(WeakFrameworks)
+ INSERT_VARIABLE(WeakLibraries)
INSERT_VARIABLE(WriteOutputConversion)
INSERT_VARIABLE(WriteValueContents)
INSERT_VARIABLE(WriteRuntimeDeps)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index b4d2862..1f3c7fe 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -346,6 +346,10 @@
extern const char kWeakFrameworks_HelpShort[];
extern const char kWeakFrameworks_Help[];
+extern const char kWeakLibraries[];
+extern const char kWeakLibraries_HelpShort[];
+extern const char kWeakLibraries_Help[];
+
extern const char kWriteValueContents[];
extern const char kWriteValueContents_HelpShort[];
extern const char kWriteValueContents_Help[];