Implement "friend" for GN header checking.

This allows a target to include private headers it wouldn't otherwise be
able to include. A target can provide a list of patterns of friend
targets that allow dependent targets to bypass public header checking.

A proper dependency path is still required, this applies only to the
concept of public/private headers on a target. Without this, it's
difficult to make a target be explicit about its public headers while
also allowing that target's own tests to use internal headers.

BUG=732993

Change-Id: I1cb94c7ac0db6f74cdcd49d08bcabedf70ea3eb0
Reviewed-on: https://chromium-review.googlesource.com/994453
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#548279}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 28f3c4987aacf447643a4f36e4553cc830508b0b
diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc
index 8659306..4ff9366 100644
--- a/tools/gn/binary_target_generator.cc
+++ b/tools/gn/binary_target_generator.cc
@@ -47,6 +47,9 @@
   if (!FillPublic())
     return;
 
+  if (!FillFriends())
+    return;
+
   if (!FillCheckIncludes())
     return;
 
@@ -79,6 +82,15 @@
   return true;
 }
 
+bool BinaryTargetGenerator::FillFriends() {
+  const Value* value = scope_->GetValue(variables::kFriend, true);
+  if (value) {
+    return ExtractListOfLabelPatterns(*value, scope_->GetSourceDir(),
+                                      &target_->friends(), err_);
+  }
+  return true;
+}
+
 bool BinaryTargetGenerator::FillOutputName() {
   const Value* value = scope_->GetValue(variables::kOutputName, true);
   if (!value)
diff --git a/tools/gn/binary_target_generator.h b/tools/gn/binary_target_generator.h
index 0788a20..40fc314 100644
--- a/tools/gn/binary_target_generator.h
+++ b/tools/gn/binary_target_generator.h
@@ -25,6 +25,7 @@
 
  private:
   bool FillCompleteStaticLib();
+  bool FillFriends();
   bool FillOutputName();
   bool FillOutputPrefixOverride();
   bool FillOutputDir();
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 0e20367..0ceaba0 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -17,9 +17,9 @@
     "  Dependent configs: all_dependent_configs, public_configs\n"
 #define DEPS_VARS \
     "  Deps: data_deps, deps, public_deps\n"
-#define GENERAL_TARGET_VARS \
-    "  General: check_includes, configs, data, inputs, output_name,\n" \
-    "           output_extension, public, sources, testonly, visibility\n"
+#define GENERAL_TARGET_VARS                                                  \
+  "  General: check_includes, configs, data, friend, inputs, output_name,\n" \
+  "           output_extension, public, sources, testonly, visibility\n"
 
 namespace functions {
 
diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc
index c47e6f1..57d2642 100644
--- a/tools/gn/header_checker.cc
+++ b/tools/gn/header_checker.cc
@@ -111,6 +111,14 @@
          a->label().name() == b->label().name();
 }
 
+// Returns true if the target |annotation_on| includes a friend annotation
+// that allows |is_marked_friend| as a friend.
+bool FriendMatches(const Target* annotation_on,
+                   const Target* is_marked_friend) {
+  return LabelPattern::VectorMatches(annotation_on->friends(),
+                                     is_marked_friend->label());
+}
+
 }  // namespace
 
 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
@@ -354,8 +362,9 @@
     return true;
 
   // For all targets containing this file, we require that at least one be
-  // a direct or public dependency of the current target, and that the header
-  // is public within the target.
+  // a direct or public dependency of the current target, and either (1) the
+  // header is public within the target, or (2) there is a friend definition
+  // whitelisting the includor.
   //
   // If there is more than one target containing this header, we may encounter
   // some error cases before finding a good one. This error stores the previous
@@ -378,14 +387,17 @@
 
       found_dependency = true;
 
-      if (target.is_public && is_permitted_chain) {
+      bool effectively_public =
+          target.is_public || FriendMatches(to_target, from_target);
+
+      if (effectively_public && is_permitted_chain) {
         // This one is OK, we're done.
         last_error = Err();
         break;
       }
 
       // Diagnose the error.
-      if (!target.is_public) {
+      if (!effectively_public) {
         // Danger: must call CreatePersistentRange to put in Err.
         last_error = Err(CreatePersistentRange(source_file, range),
                          "Including a private header.",
diff --git a/tools/gn/header_checker.h b/tools/gn/header_checker.h
index 3645d7a..b1d0f79 100644
--- a/tools/gn/header_checker.h
+++ b/tools/gn/header_checker.h
@@ -71,6 +71,8 @@
   FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, SourceFileForInclude);
   FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest,
                            SourceFileForInclude_FileNotFound);
+  FRIEND_TEST_ALL_PREFIXES(HeaderCheckerTest, Friend);
+
   ~HeaderChecker();
 
   struct TargetInfo {
diff --git a/tools/gn/header_checker_unittest.cc b/tools/gn/header_checker_unittest.cc
index 5bb5c70..dcfa7bb 100644
--- a/tools/gn/header_checker_unittest.cc
+++ b/tools/gn/header_checker_unittest.cc
@@ -348,3 +348,35 @@
   EXPECT_TRUE(source_file.is_null());
   EXPECT_FALSE(err.has_error());
 }
+
+TEST_F(HeaderCheckerTest, Friend) {
+  // Note: we have a public dependency chain A -> B -> C set up already.
+  InputFile input_file(SourceFile("//some_file.cc"));
+  input_file.SetContents(std::string());
+  LocationRange range;  // Dummy value.
+
+  // Add a private header on C.
+  SourceFile c_private("//c_private.h");
+  c_.sources().push_back(c_private);
+  c_.set_all_headers_public(false);
+
+  // List A as a friend of C.
+  Err err;
+  c_.friends().push_back(
+      LabelPattern::GetPattern(SourceDir("//"), Value(nullptr, "//a:*"), &err));
+  ASSERT_FALSE(err.has_error());
+
+  // Must be after setting everything up for it to find the files.
+  scoped_refptr<HeaderChecker> checker(
+      new HeaderChecker(setup_.build_settings(), targets_));
+
+  // B should not be allowed to include C's private header.
+  err = Err();
+  EXPECT_FALSE(checker->CheckInclude(&b_, input_file, c_private, range, &err));
+  EXPECT_TRUE(err.has_error());
+
+  // A should be able to because of the friend declaration.
+  err = Err();
+  EXPECT_TRUE(checker->CheckInclude(&a_, input_file, c_private, range, &err));
+  EXPECT_FALSE(err.has_error()) << err.message();
+}
diff --git a/tools/gn/label_pattern.cc b/tools/gn/label_pattern.cc
index c4e9bb6..b2568e7 100644
--- a/tools/gn/label_pattern.cc
+++ b/tools/gn/label_pattern.cc
@@ -242,6 +242,16 @@
   }
 }
 
+// static
+bool LabelPattern::VectorMatches(const std::vector<LabelPattern>& patterns,
+                                 const Label& label) {
+  for (const auto& pattern : patterns) {
+    if (pattern.Matches(label))
+      return true;
+  }
+  return false;
+}
+
 std::string LabelPattern::Describe() const {
   std::string result;
 
diff --git a/tools/gn/label_pattern.h b/tools/gn/label_pattern.h
index 7d0768c..774b81a 100644
--- a/tools/gn/label_pattern.h
+++ b/tools/gn/label_pattern.h
@@ -46,6 +46,10 @@
   // Returns true if this pattern matches the given label.
   bool Matches(const Label& label) const;
 
+  // Returns true if any of the patterns in the vector match the label.
+  static bool VectorMatches(const std::vector<LabelPattern>& patterns,
+                            const Label& label);
+
   // Returns a string representation of this pattern.
   std::string Describe() const;
 
diff --git a/tools/gn/target.h b/tools/gn/target.h
index b286518..568bea5 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -243,9 +243,10 @@
     return recursive_hard_deps_;
   }
 
-  std::vector<LabelPattern>& assert_no_deps() {
-    return assert_no_deps_;
-  }
+  std::vector<LabelPattern>& friends() { return friends_; }
+  const std::vector<LabelPattern>& friends() const { return friends_; }
+
+  std::vector<LabelPattern>& assert_no_deps() { return assert_no_deps_; }
   const std::vector<LabelPattern>& assert_no_deps() const {
     return assert_no_deps_;
   }
@@ -374,6 +375,7 @@
   // target is marked resolved. This will not include the current target.
   std::set<const Target*> recursive_hard_deps_;
 
+  std::vector<LabelPattern> friends_;
   std::vector<LabelPattern> assert_no_deps_;
 
   // Used for all binary targets, and for inputs in regular targets. The
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 80c6b36..591a945 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1100,17 +1100,67 @@
   See also "public_deps".
 )";
 
-const char kXcodeExtraAttributes[] = "xcode_extra_attributes";
-const char kXcodeExtraAttributes_HelpShort[] =
-    "xcode_extra_attributes: [scope] Extra attributes for Xcode projects.";
-const char kXcodeExtraAttributes_Help[] =
-    R"(xcode_extra_attributes: [scope] Extra attributes for Xcode projects.
+const char kFriend[] = "friend";
+const char kFriend_HelpShort[] =
+    "friend: [label pattern list] Allow targets to include private headers.";
+const char kFriend_Help[] =
+    R"(friend: Allow targets to include private headers.
 
-  The value defined in this scope will be copied to the EXTRA_ATTRIBUTES
-  property of the generated Xcode project. They are only meaningful when
-  generating with --ide=xcode.
+  A list of label patterns (see "gn help label_pattern") that allow dependent
+  targets to include private headers. Applies to all binary targets.
 
-  See "gn help create_bundle" for more information.
+  Normally if a target lists headers in the "public" list (see "gn help
+  public"), other headers are implicitly marked as private. Private headers
+  can not be included by other targets, even with a public dependency path.
+  The "gn check" function performs this validation.
+
+  A friend declaration allows one or more targets to include private headers.
+  This is useful for things like unit tests that are closely associated with a
+  target and require internal knowledge without opening up all headers to be
+  included by all dependents.
+
+  A friend target does not allow that target to include headers when no
+  dependency exists. A public dependency path must still exist between two
+  targets to include any headers from a destination target. The friend
+  annotation merely allows the use of headers that would otherwise be
+  prohibited because they are private.
+
+  The friend annotation is matched only against the target containing the file
+  with the include directive. Friend annotations are not propagated across
+  public or private dependencies. Friend annotations do not affect visibility.
+
+Example
+
+  static_library("lib") {
+    # This target can include our private headers.
+    friend = [ ":unit_tests" ]
+
+    public = [
+      "public_api.h",  # Normal public API for dependent targets.
+    ]
+
+    # Private API and sources.
+    sources = [
+      "a_source_file.cc",
+
+      # Normal targets that depend on this one won't be able to include this
+      # because this target defines a list of "public" headers. Without the
+      # "public" list, all headers are implicitly public.
+      "private_api.h",
+    ]
+  }
+
+  executable("unit_tests") {
+    sources = [
+      # This can include "private_api.h" from the :lib target because it
+      # depends on that target and because of the friend annotation.
+      "my_test.cc",
+    ]
+
+    deps = [
+      ":lib",  # Required for the include to be allowed.
+    ]
+  }
 )";
 
 const char kIncludeDirs[] = "include_dirs";
@@ -1593,7 +1643,8 @@
   If no public files are declared, other targets (assuming they have visibility
   to depend on this target) can include any file in the sources list. If this
   variable is defined on a target, dependent targets may only include files on
-  this whitelist.
+  this whitelist unless that target is marked as a friend (see "gn help
+  friend").
 
   Header file permissions are also subject to visibility. A target must be
   visible to another target to include any files from it at all and the public
@@ -1609,6 +1660,11 @@
   targets. If a file is included that is not known to the build, it will be
   allowed.
 
+  It is common for test targets to need to include private headers for their
+  associated code. In this case, list the test target in the "friend" list of
+  the target that owns the private header to allow the inclusion. See
+  "gn help friend" for more.
+
 Examples
 
   These exact files are public:
@@ -1901,6 +1957,19 @@
   help --runtime-deps-list-file").
 )";
 
+const char kXcodeExtraAttributes[] = "xcode_extra_attributes";
+const char kXcodeExtraAttributes_HelpShort[] =
+    "xcode_extra_attributes: [scope] Extra attributes for Xcode projects.";
+const char kXcodeExtraAttributes_Help[] =
+    R"(xcode_extra_attributes: [scope] Extra attributes for Xcode projects.
+
+  The value defined in this scope will be copied to the EXTRA_ATTRIBUTES
+  property of the generated Xcode project. They are only meaningful when
+  generating with --ide=xcode.
+
+  See "gn help create_bundle" for more information.
+)";
+
 // -----------------------------------------------------------------------------
 
 VariableInfo::VariableInfo()
@@ -1971,7 +2040,7 @@
     INSERT_VARIABLE(Defines)
     INSERT_VARIABLE(Depfile)
     INSERT_VARIABLE(Deps)
-    INSERT_VARIABLE(XcodeExtraAttributes)
+    INSERT_VARIABLE(Friend)
     INSERT_VARIABLE(IncludeDirs)
     INSERT_VARIABLE(Inputs)
     INSERT_VARIABLE(Ldflags)
@@ -1998,6 +2067,7 @@
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
     INSERT_VARIABLE(WriteRuntimeDeps)
+    INSERT_VARIABLE(XcodeExtraAttributes)
   }
   return info_map;
 }
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 0e3891d..80615f9 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -195,9 +195,9 @@
 extern const char kDeps_HelpShort[];
 extern const char kDeps_Help[];
 
-extern const char kXcodeExtraAttributes[];
-extern const char kXcodeExtraAttributes_HelpShort[];
-extern const char kXcodeExtraAttributes_Help[];
+extern const char kFriend[];
+extern const char kFriend_HelpShort[];
+extern const char kFriend_Help[];
 
 extern const char kIncludeDirs[];
 extern const char kIncludeDirs_HelpShort[];
@@ -303,6 +303,10 @@
 extern const char kWriteRuntimeDeps_HelpShort[];
 extern const char kWriteRuntimeDeps_Help[];
 
+extern const char kXcodeExtraAttributes[];
+extern const char kXcodeExtraAttributes_HelpShort[];
+extern const char kXcodeExtraAttributes_Help[];
+
 // -----------------------------------------------------------------------------
 
 struct VariableInfo {
diff --git a/tools/gn/visibility.cc b/tools/gn/visibility.cc
index 54d7280..ec96824 100644
--- a/tools/gn/visibility.cc
+++ b/tools/gn/visibility.cc
@@ -54,11 +54,7 @@
 }
 
 bool Visibility::CanSeeMe(const Label& label) const {
-  for (const auto& pattern : patterns_) {
-    if (pattern.Matches(label))
-      return true;
-  }
-  return false;
+  return LabelPattern::VectorMatches(patterns_, label);
 }
 
 std::string Visibility::Describe(int indent, bool include_brackets) const {