GN: Refine 'complete' static library handling
Allow static libraries to be dependencies of complete static libraries.
Propagate library settings (lib_dirs/libs) through complete static libraries.
BUG=413776
TEST=gn_unittests
R=brettw@chromium.org
Review URL: https://codereview.chromium.org/572893002
Cr-Original-Commit-Position: refs/heads/master@{#389041}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 33d9b3bf67d9e64cb476784a0f120a81e546eba5
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index cb48688..30658f2 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -938,7 +938,14 @@
// don't link at all.
bool can_link_libs = target_->IsFinal();
- if (dep->output_type() == Target::SOURCE_SET) {
+ if (dep->output_type() == Target::SOURCE_SET ||
+ // If a complete static library depends on an incomplete static library,
+ // manually link in the object files of the dependent library as if it
+ // were a source set. This avoids problems with braindead tools such as
+ // ar which don't properly link dependent static libraries.
+ (target_->complete_static_lib() &&
+ dep->output_type() == Target::STATIC_LIBRARY &&
+ !dep->complete_static_lib())) {
// Source sets have their object files linked into final targets
// (shared libraries, executables, loadable modules, and complete static
// libraries). Intermediate static libraries and other source sets
@@ -954,6 +961,8 @@
// can be complete. Otherwise, these will be skipped since this target
// will depend only on the source set's object files.
non_linkable_deps->push_back(dep);
+ } else if (target_->complete_static_lib() && dep->IsFinal()) {
+ non_linkable_deps->push_back(dep);
} else if (can_link_libs && dep->IsLinkable()) {
linkable_deps->push_back(dep);
} else {
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index ab8b641..f2297fb 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -179,6 +179,81 @@
EXPECT_EQ(expected, out_str);
}
+TEST(NinjaBinaryTargetWriter, CompleteStaticLibrary) {
+ TestWithScope setup;
+ Err err;
+
+ TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.config_values().arflags().push_back("--asdf");
+ target.set_complete_static_lib(true);
+
+ TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
+ baz.sources().push_back(SourceFile("//foo/input2.cc"));
+
+ target.public_deps().push_back(LabelTargetPair(&baz));
+
+ ASSERT_TRUE(target.OnResolved(&err));
+ ASSERT_TRUE(baz.OnResolved(&err));
+
+ // A complete static library that depends on an incomplete static library
+ // should link in the dependent object files as if the dependent target
+ // were a source set.
+ {
+ std::ostringstream out;
+ NinjaBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+ "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
+ " arflags = --asdf\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+
+ // Make the dependent static library complete.
+ baz.set_complete_static_lib(true);
+
+ // Dependent complete static libraries should not be linked directly.
+ {
+ std::ostringstream out;
+ NinjaBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+ "|| obj/foo/libbaz.a\n"
+ " arflags = --asdf\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+}
+
// This tests that output extension and output dir overrides apply, and input
// dependencies are applied.
TEST(NinjaBinaryTargetWriter, OutputExtensionAndInputDeps) {
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index e88c82f..ddc8ad5 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -55,19 +55,6 @@
"Either mark it test-only or don't do this dependency.");
}
-Err MakeStaticLibDepsError(const Target* from, const Target* to) {
- return Err(from->defined_from(),
- "Complete static libraries can't depend on static libraries.",
- from->label().GetUserVisibleName(false) +
- "\n"
- "which is a complete static library can't depend on\n" +
- to->label().GetUserVisibleName(false) +
- "\n"
- "which is a static library.\n"
- "\n"
- "Use source sets for intermediate targets instead.");
-}
-
// Set check_private_deps to true for the first invocation since a target
// can see all of its dependencies. For recursive invocations this will be set
// to false to follow only public dependency paths.
@@ -317,8 +304,6 @@
return false;
if (!CheckTestonly(err))
return false;
- if (!CheckNoNestedStaticLibs(err))
- return false;
if (!CheckAssertNoDeps(err))
return false;
CheckSourcesGenerated();
@@ -482,8 +467,24 @@
// The current target isn't linked, so propogate linked deps and
// libraries up the dependency tree.
inherited_libraries_.AppendInherited(dep->inherited_libraries(), is_public);
+ } else if (dep->complete_static_lib()) {
+ // Inherit only final targets through _complete_ static libraries.
+ //
+ // Inherited final libraries aren't linked into complete static libraries.
+ // They are forwarded here so that targets that depend on complete
+ // static libraries can link them in. Conversely, since complete static
+ // libraries link in non-final targets they shouldn't be inherited.
+ for (const auto& inherited :
+ dep->inherited_libraries().GetOrderedAndPublicFlag()) {
+ if (inherited.first->IsFinal()) {
+ inherited_libraries_.Append(inherited.first,
+ is_public && inherited.second);
+ }
+ }
+ }
- // Inherited library settings.
+ // Library settings are always inherited across static library boundaries.
+ if (!dep->IsFinal() || dep->output_type() == STATIC_LIBRARY) {
all_lib_dirs_.append(dep->all_lib_dirs());
all_libs_.append(dep->all_libs());
}
@@ -702,30 +703,6 @@
return true;
}
-bool Target::CheckNoNestedStaticLibs(Err* err) const {
- // If the current target is not a complete static library, it can depend on
- // static library targets with no problem.
- if (!(output_type() == Target::STATIC_LIBRARY && complete_static_lib()))
- return true;
-
- // Verify no deps are static libraries.
- for (const auto& pair : GetDeps(DEPS_ALL)) {
- if (pair.ptr->output_type() == Target::STATIC_LIBRARY) {
- *err = MakeStaticLibDepsError(this, pair.ptr);
- return false;
- }
- }
-
- // Verify no inherited libraries are static libraries.
- for (const auto& lib : inherited_libraries().GetOrdered()) {
- if (lib->output_type() == Target::STATIC_LIBRARY) {
- *err = MakeStaticLibDepsError(this, lib);
- return false;
- }
- }
- return true;
-}
-
bool Target::CheckAssertNoDeps(Err* err) const {
if (assert_no_deps_.empty())
return true;
diff --git a/tools/gn/target.h b/tools/gn/target.h
index ac159e1..495a9c1 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -330,7 +330,6 @@
// Validates the given thing when a target is resolved.
bool CheckVisibility(Err* err) const;
bool CheckTestonly(Err* err) const;
- bool CheckNoNestedStaticLibs(Err* err) const;
bool CheckAssertNoDeps(Err* err) const;
void CheckSourcesGenerated() const;
void CheckSourceGenerated(const SourceFile& source) const;
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index e2e41a8..622dd54 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -183,7 +183,13 @@
TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
b.set_complete_static_lib(true);
+
+ const LibFile lib("foo");
+ const SourceDir lib_dir("/foo_dir/");
TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+ c.config_values().libs().push_back(lib);
+ c.config_values().lib_dirs().push_back(lib_dir);
+
a.public_deps().push_back(LabelTargetPair(&b));
b.public_deps().push_back(LabelTargetPair(&c));
@@ -199,42 +205,74 @@
// A should have B in its inherited libs, but not any others (the complete
// static library will include the source set).
std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
- EXPECT_EQ(1u, a_inherited.size());
+ ASSERT_EQ(1u, a_inherited.size());
EXPECT_EQ(&b, a_inherited[0]);
+
+ // A should inherit the libs and lib_dirs from the C.
+ ASSERT_EQ(1u, a.all_libs().size());
+ EXPECT_EQ(lib, a.all_libs()[0]);
+ ASSERT_EQ(1u, a.all_lib_dirs().size());
+ EXPECT_EQ(lib_dir, a.all_lib_dirs()[0]);
}
-TEST(Target, InheritCompleteStaticLibNoDirectStaticLibDeps) {
+TEST(Target, InheritCompleteStaticLibStaticLibDeps) {
TestWithScope setup;
Err err;
// Create a dependency chain:
- // A (complete static lib) -> B (static lib)
- TestTarget a(setup, "//foo:a", Target::STATIC_LIBRARY);
- a.set_complete_static_lib(true);
+ // A (executable) -> B (complete static lib) -> C (static lib)
+ TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
-
- a.public_deps().push_back(LabelTargetPair(&b));
- ASSERT_TRUE(b.OnResolved(&err));
- ASSERT_FALSE(a.OnResolved(&err));
-}
-
-TEST(Target, InheritCompleteStaticLibNoIheritedStaticLibDeps) {
- TestWithScope setup;
- Err err;
-
- // Create a dependency chain:
- // A (complete static lib) -> B (source set) -> C (static lib)
- TestTarget a(setup, "//foo:a", Target::STATIC_LIBRARY);
- a.set_complete_static_lib(true);
- TestTarget b(setup, "//foo:b", Target::SOURCE_SET);
+ b.set_complete_static_lib(true);
TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
-
a.public_deps().push_back(LabelTargetPair(&b));
b.public_deps().push_back(LabelTargetPair(&c));
ASSERT_TRUE(c.OnResolved(&err));
ASSERT_TRUE(b.OnResolved(&err));
- ASSERT_FALSE(a.OnResolved(&err));
+ ASSERT_TRUE(a.OnResolved(&err));
+
+ // B should have C in its inherited libs.
+ std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+ ASSERT_EQ(1u, b_inherited.size());
+ EXPECT_EQ(&c, b_inherited[0]);
+
+ // A should have B in its inherited libs, but not any others (the complete
+ // static library will include the static library).
+ std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+ ASSERT_EQ(1u, a_inherited.size());
+ EXPECT_EQ(&b, a_inherited[0]);
+}
+
+TEST(Target, InheritCompleteStaticLibInheritedCompleteStaticLibDeps) {
+ TestWithScope setup;
+ Err err;
+
+ // Create a dependency chain:
+ // A (executable) -> B (complete static lib) -> C (complete static lib)
+ TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+ TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+ b.set_complete_static_lib(true);
+ TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+ c.set_complete_static_lib(true);
+
+ a.private_deps().push_back(LabelTargetPair(&b));
+ b.private_deps().push_back(LabelTargetPair(&c));
+
+ ASSERT_TRUE(c.OnResolved(&err));
+ ASSERT_TRUE(b.OnResolved(&err));
+ ASSERT_TRUE(a.OnResolved(&err));
+
+ // B should have C in its inherited libs.
+ std::vector<const Target*> b_inherited = b.inherited_libraries().GetOrdered();
+ ASSERT_EQ(1u, b_inherited.size());
+ EXPECT_EQ(&c, b_inherited[0]);
+
+ // A should have B and C in its inherited libs.
+ std::vector<const Target*> a_inherited = a.inherited_libraries().GetOrdered();
+ ASSERT_EQ(2u, a_inherited.size());
+ EXPECT_EQ(&b, a_inherited[0]);
+ EXPECT_EQ(&c, a_inherited[1]);
}
TEST(Target, NoActionDepPropgation) {
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index e9006a2..eea5bca 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -664,9 +664,16 @@
" In some cases the static library might be the final desired output.\n"
" For example, you may be producing a static library for distribution to\n"
" third parties. In this case, the static library should include code\n"
- " for all dependencies in one complete package. Since GN does not unpack\n"
- " static libraries to forward their contents up the dependency chain,\n"
- " it is an error for complete static libraries to depend on other static\n"
+ " for all dependencies in one complete package. However, complete static\n"
+ " libraries themselves are never linked into other complete static\n"
+ " libraries. All complete static libraries are for distribution and\n"
+ " linking them in would cause code duplication in this case. If the\n"
+ " static library is not for distribution, it should not be complete.\n"
+ "\n"
+ " GN treats non-complete static libraries as source sets when they are\n"
+ " linked into complete static libraries. This is done because some tools\n"
+ " like AR do not handle dependent static libraries properly. This makes\n"
+ " it easier to write \"alink\" rules.\n"
"\n"
" In rare cases it makes sense to list a header in more than one\n"
" target if it could be considered conceptually a member of both.\n"
@@ -756,7 +763,7 @@
const char kConsole[] = "console";
const char kConsole_HelpShort[] =
- "console [boolean]: Run this action in the console pool.";
+ "console: [boolean] Run this action in the console pool.";
const char kConsole_Help[] =
"console: Run this action in the console pool.\n"
"\n"