Add GN output prefix override and allow empty output extensions.

Previously setting the output extension to an empty string would give the
default extension. This patch differentiates unset (default extension) from
set-to-empty-string (which can now mean "no extension").

A flag is added to out-out of the target output prefix application for targets
that don't want to have "lib" at the beginning on Posix systems. Even though
the flag might more naturally be called "override_output_prefix" I called it
"output_prefix_override" so that all of the output-name-related variables are
called "output_....".

Review URL: https://codereview.chromium.org/1868023002

Cr-Original-Commit-Position: refs/heads/master@{#385927}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 2a642a10300c49bcd33b6246e16650cf61a42d3b
diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc
index 923d0ed..7c81232 100644
--- a/tools/gn/binary_target_generator.cc
+++ b/tools/gn/binary_target_generator.cc
@@ -31,6 +31,9 @@
   if (!FillOutputName())
     return;
 
+  if (!FillOutputPrefixOverride())
+    return;
+
   if (!FillOutputExtension())
     return;
 
@@ -85,6 +88,16 @@
   return true;
 }
 
+bool BinaryTargetGenerator::FillOutputPrefixOverride() {
+  const Value* value = scope_->GetValue(variables::kOutputPrefixOverride, true);
+  if (!value)
+    return true;
+  if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+    return false;
+  target_->set_output_prefix_override(value->boolean_value());
+  return true;
+}
+
 bool BinaryTargetGenerator::FillOutputExtension() {
   const Value* value = scope_->GetValue(variables::kOutputExtension, true);
   if (!value)
diff --git a/tools/gn/binary_target_generator.h b/tools/gn/binary_target_generator.h
index 4674f96..40ed432 100644
--- a/tools/gn/binary_target_generator.h
+++ b/tools/gn/binary_target_generator.h
@@ -26,6 +26,7 @@
  private:
   bool FillCompleteStaticLib();
   bool FillOutputName();
+  bool FillOutputPrefixOverride();
   bool FillOutputExtension();
   bool FillAllowCircularIncludesFrom();
 
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 70cc6b3..22cbee0 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -2845,6 +2845,10 @@
         is not already there. The result will show up in the
         {{output_name}} substitution pattern.
 
+        Individual targets can opt-out of the output prefix by setting:
+          output_prefix_override = true
+        (see "gn help output_prefix_override").
+
         This is typically used to prepend "lib" to libraries on
         Posix systems:
           output_prefix = "lib"
@@ -4664,9 +4668,11 @@
   override the name (for example to use "libfreetype.so.6" instead
   of libfreetype.so on Linux).
 
-  This value should not include a leading dot. If undefined or empty,
-  the default_output_extension specified on the tool will be used.
-  The output_extension will be used in the "{{output_extension}}"
+  This value should not include a leading dot. If undefined, the default
+  specified on the tool will be used. If set to the empty string, no
+  output extension will be used.
+
+  The output_extension will be used to set the "{{output_extension}}"
   expansion which the linker tool will generally use to specify the
   output file name. See "gn help tool".
 
@@ -4724,6 +4730,35 @@
 
 
 ```
+## **output_prefix_override**: Don't use prefix for output name.
+
+```
+  A boolean that overrides the output prefix for a target. Defaults to
+  false.
+
+  Some systems use prefixes for the names of the final target output
+  file. The normal example is "libfoo.so" on Linux for a target
+  named "foo".
+
+  The output prefix for a given target type is specified on the linker
+  tool (see "gn help tool"). Sometimes this prefix is undesired.
+
+  See also "gn help output_extension".
+
+```
+
+### **Example**
+
+```
+  shared_library("doom_melon") {
+    # Normally this will produce "libdoom_melon.so" on Linux, setting
+    # Setting this flag will produce "doom_melon.so".
+    output_prefix_override = true
+    ...
+  }
+
+
+```
 ## **outputs**: Output files for actions and copy targets.
 
 ```
@@ -5131,6 +5166,29 @@
 
 
 ```
+## **write_runtime_deps**: Writes the target's runtime_deps to the given path.
+
+```
+  Does not synchronously write the file, but rather schedules it
+  to be written at the end of generation.
+
+  If the file exists and the contents are identical to that being
+  written, the file will not be updated. This will prevent unnecessary
+  rebuilds of targets that depend on this file.
+
+  Path must be within the output directory.
+
+  See "gn help runtime_deps" for how the runtime dependencies are
+  computed.
+
+  The format of this file will list one file per line with no escaping.
+  The files will be relative to the root_build_dir. The first line of
+  the file will be the main output file of the target itself. The file
+  contents will be the same as requesting the runtime deps be written on
+  the command line (see "gn help --runtime-deps-list-file").
+
+
+```
 ## **Build Arguments Overview**
 
 ```
@@ -5561,8 +5619,8 @@
 ```
   Runtime dependencies of a target are exposed via the "runtime_deps"
   category of "gn desc" (see "gn help desc") or they can be written
-  at build generation time via "--runtime-deps-list-file"
-  (see "gn help --runtime-deps-list-file").
+  at build generation time via write_runtime_deps(), or
+  --runtime-deps-list-file (see "gn help --runtime-deps-list-file").
 
   To a first approximation, the runtime dependencies of a target are
   the set of "data" files, data directories, and the shared libraries
diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc
index 727be54..72097f2 100644
--- a/tools/gn/function_toolchain.cc
+++ b/tools/gn/function_toolchain.cc
@@ -534,6 +534,10 @@
     "        is not already there. The result will show up in the\n"
     "        {{output_name}} substitution pattern.\n"
     "\n"
+    "        Individual targets can opt-out of the output prefix by setting:\n"
+    "          output_prefix_override = true\n"
+    "        (see \"gn help output_prefix_override\").\n"
+    "\n"
     "        This is typically used to prepend \"lib\" to libraries on\n"
     "        Posix systems:\n"
     "          output_prefix = \"lib\"\n"
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index b1bf803..eaae098 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -854,15 +854,9 @@
 }
 
 void NinjaBinaryTargetWriter::WriteOutputExtension() {
-  out_ << "  output_extension = ";
-  if (target_->output_extension().empty()) {
-    // Use the default from the tool.
-    out_ << tool_->default_output_extension();
-  } else {
-    // Use the one specified in the target. Note that the one in the target
-    // does not include the leading dot, so add that.
-    out_ << "." << target_->output_extension();
-  }
+  out_ << "  output_extension = "
+       << SubstitutionWriter::GetLinkerSubstitution(
+              target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
   out_ << std::endl;
 }
 
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index c9b3ebe..0b87883 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -241,10 +241,12 @@
 
   setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
 
-  // This test is the same as ProductExtension, except that
-  // we call set_output_extension("") and ensure that we still get the default.
+  // This test is the same as ProductExtension, except that we call
+  // set_output_extension("") and ensure that we get an empty one and override
+  // the output prefix so that the name matches the target exactly.
   Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
   target.set_output_type(Target::SHARED_LIBRARY);
+  target.set_output_prefix_override(true);
   target.set_output_extension(std::string());
   target.sources().push_back(SourceFile("//foo/input1.cc"));
   target.sources().push_back(SourceFile("//foo/input2.cc"));
@@ -263,16 +265,16 @@
       "cflags_cc =\n"
       "root_out_dir = .\n"
       "target_out_dir = obj/foo\n"
-      "target_output_name = libshlib\n"
+      "target_output_name = shlib\n"
       "\n"
-      "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc\n"
-      "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc\n"
+      "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
       "\n"
-      "build ./libshlib.so: solink obj/foo/libshlib.input1.o "
-          "obj/foo/libshlib.input2.o\n"
+      "build ./shlib: solink obj/foo/shlib.input1.o "
+          "obj/foo/shlib.input2.o\n"
       "  ldflags =\n"
       "  libs =\n"
-      "  output_extension = .so\n";
+      "  output_extension = \n";
 
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 99abced..23d6914 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -448,7 +448,7 @@
           result);
       break;
     case SUBSTITUTION_TARGET_OUTPUT_NAME:
-      *result = target->GetComputedOutputName(true);
+      *result = target->GetComputedOutputName();
       break;
     default:
       return false;
@@ -547,11 +547,13 @@
   // Fall-through to the linker-specific ones.
   switch (type) {
     case SUBSTITUTION_OUTPUT_EXTENSION:
-      // Use the extension provided on the target if nonempty, otherwise
+      // Use the extension provided on the target if specified, otherwise
       // fall back on the default. Note that the target's output extension
       // does not include the dot but the tool's does.
-      if (target->output_extension().empty())
+      if (!target->output_extension_set())
         return tool->default_output_extension();
+      if (target->output_extension().empty())
+        return std::string();  // Explicitly set to no extension.
       return std::string(".") + target->output_extension();
 
     default:
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index 36d027b..77374f1 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -268,4 +268,14 @@
   OutputFile output = SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
       &target, tool, pattern);
   EXPECT_EQ("./libbaz.so", output.value());
+
+  // Output extensions can be overridden.
+  target.set_output_extension("extension");
+  EXPECT_EQ(".extension",
+            SubstitutionWriter::GetLinkerSubstitution(
+                &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+  target.set_output_extension("");
+  EXPECT_EQ("",
+            SubstitutionWriter::GetLinkerSubstitution(
+                &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
 }
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 005dd97..18c353e 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -212,6 +212,8 @@
 Target::Target(const Settings* settings, const Label& label)
     : Item(settings, label),
       output_type_(UNKNOWN),
+      output_prefix_override_(false),
+      output_extension_set_(false),
       all_headers_public_(true),
       check_includes_(true),
       complete_static_lib_(false),
@@ -358,7 +360,7 @@
       &public_deps_, &private_deps_, &data_deps_));
 }
 
-std::string Target::GetComputedOutputName(bool include_prefix) const {
+std::string Target::GetComputedOutputName() const {
   DCHECK(toolchain_)
       << "Toolchain must be specified before getting the computed output name.";
 
@@ -366,14 +368,14 @@
                                                  : output_name_;
 
   std::string result;
-  if (include_prefix) {
-    const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
-    if (tool) {
-      // Only add the prefix if the name doesn't already have it.
-      if (!base::StartsWith(name, tool->output_prefix(),
-                            base::CompareCase::SENSITIVE))
-        result = tool->output_prefix();
-    }
+  const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+  if (tool) {
+    // Only add the prefix if the name doesn't already have it and it's not
+    // being overridden.
+    if (!output_prefix_override_ &&
+        !base::StartsWith(name, tool->output_prefix(),
+                          base::CompareCase::SENSITIVE))
+      result = tool->output_prefix();
   }
   result.append(name);
   return result;
@@ -536,7 +538,7 @@
       // entry in the outputs. These stamps are named
       // "<target_out_dir>/<targetname>.stamp".
       dependency_output_file_ = GetTargetOutputDirAsOutputFile(this);
-      dependency_output_file_.value().append(GetComputedOutputName(true));
+      dependency_output_file_.value().append(GetComputedOutputName());
       dependency_output_file_.value().append(".stamp");
       break;
     }
diff --git a/tools/gn/target.h b/tools/gn/target.h
index c3fed01..fdd6f1a 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -89,16 +89,29 @@
   void set_output_name(const std::string& name) { output_name_ = name; }
 
   // Returns the output name for this target, which is the output_name if
-  // specified, or the target label if not. If the flag is set, it will also
-  // include any output prefix specified on the tool (often "lib" on Linux).
+  // specified, or the target label if not.
   //
   // Because this depends on the tool for this target, the toolchain must
   // have been set before calling.
-  std::string GetComputedOutputName(bool include_prefix) const;
+  std::string GetComputedOutputName() const;
 
+  bool output_prefix_override() const { return output_prefix_override_; }
+  void set_output_prefix_override(bool prefix_override) {
+    output_prefix_override_ = prefix_override;
+  }
+
+  // The output extension is really a tri-state: unset (output_extension_set
+  // is false and the string is empty, meaning the default extension should be
+  // used), the output extension is set but empty (output should have no
+  // extension) and the output extension is set but nonempty (use the given
+  // extension).
   const std::string& output_extension() const { return output_extension_; }
   void set_output_extension(const std::string& extension) {
     output_extension_ = extension;
+    output_extension_set_ = true;
+  }
+  bool output_extension_set() const {
+    return output_extension_set_;
   }
 
   const FileList& sources() const { return sources_; }
@@ -318,7 +331,9 @@
 
   OutputType output_type_;
   std::string output_name_;
+  bool output_prefix_override_;
   std::string output_extension_;
+  bool output_extension_set_;
 
   FileList sources_;
   bool all_headers_public_;
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index b3a85b7..8629ff2 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -270,30 +270,32 @@
   // no prefix) or output name.
   TestTarget basic(setup, "//foo:bar", Target::EXECUTABLE);
   ASSERT_TRUE(basic.OnResolved(&err));
-  EXPECT_EQ("bar", basic.GetComputedOutputName(false));
-  EXPECT_EQ("bar", basic.GetComputedOutputName(true));
+  EXPECT_EQ("bar", basic.GetComputedOutputName());
 
   // Target with no prefix but an output name.
   TestTarget with_name(setup, "//foo:bar", Target::EXECUTABLE);
   with_name.set_output_name("myoutput");
   ASSERT_TRUE(with_name.OnResolved(&err));
-  EXPECT_EQ("myoutput", with_name.GetComputedOutputName(false));
-  EXPECT_EQ("myoutput", with_name.GetComputedOutputName(true));
+  EXPECT_EQ("myoutput", with_name.GetComputedOutputName());
 
   // Target with a "lib" prefix (the static library tool in the TestWithScope
   // should specify a "lib" output prefix).
   TestTarget with_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
   ASSERT_TRUE(with_prefix.OnResolved(&err));
-  EXPECT_EQ("bar", with_prefix.GetComputedOutputName(false));
-  EXPECT_EQ("libbar", with_prefix.GetComputedOutputName(true));
+  EXPECT_EQ("libbar", with_prefix.GetComputedOutputName());
 
   // Target with a "lib" prefix that already has it applied. The prefix should
   // not duplicate something already in the target name.
   TestTarget dup_prefix(setup, "//foo:bar", Target::STATIC_LIBRARY);
   dup_prefix.set_output_name("libbar");
   ASSERT_TRUE(dup_prefix.OnResolved(&err));
-  EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(false));
-  EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(true));
+  EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName());
+
+  // Target with an output prefix override should not have a prefix.
+  TestTarget override_prefix(setup, "//foo:bar", Target::SHARED_LIBRARY);
+  override_prefix.set_output_prefix_override(true);
+  ASSERT_TRUE(dup_prefix.OnResolved(&err));
+  EXPECT_EQ("bar", override_prefix.GetComputedOutputName());
 }
 
 // Test visibility failure case.
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 88e0ed1..4c5fae1 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1092,9 +1092,11 @@
     "  override the name (for example to use \"libfreetype.so.6\" instead\n"
     "  of libfreetype.so on Linux).\n"
     "\n"
-    "  This value should not include a leading dot. If undefined or empty,\n"
-    "  the default_output_extension specified on the tool will be used.\n"
-    "  The output_extension will be used in the \"{{output_extension}}\"\n"
+    "  This value should not include a leading dot. If undefined, the default\n"
+    "  specified on the tool will be used. If set to the empty string, no\n"
+    "  output extension will be used.\n"
+    "\n"
+    "  The output_extension will be used to set the \"{{output_extension}}\"\n"
     "  expansion which the linker tool will generally use to specify the\n"
     "  output file name. See \"gn help tool\".\n"
     "\n"
@@ -1145,6 +1147,33 @@
     "    output_name = \"fluffy_bunny\"\n"
     "  }\n";
 
+const char kOutputPrefixOverride[] = "output_prefix_override";
+const char kOutputPrefixOverride_HelpShort[] =
+    "output_prefix_override: [boolean] Don't use prefix for output name.";
+const char kOutputPrefixOverride_Help[] =
+    "output_prefix_override: Don't use prefix for output name.\n"
+    "\n"
+    "  A boolean that overrides the output prefix for a target. Defaults to\n"
+    "  false.\n"
+    "\n"
+    "  Some systems use prefixes for the names of the final target output\n"
+    "  file. The normal example is \"libfoo.so\" on Linux for a target\n"
+    "  named \"foo\".\n"
+    "\n"
+    "  The output prefix for a given target type is specified on the linker\n"
+    "  tool (see \"gn help tool\"). Sometimes this prefix is undesired.\n"
+    "\n"
+    "  See also \"gn help output_extension\".\n"
+    "\n"
+    "Example\n"
+    "\n"
+    "  shared_library(\"doom_melon\") {\n"
+    "    # Normally this will produce \"libdoom_melon.so\" on Linux, setting\n"
+    "    # Setting this flag will produce \"doom_melon.so\".\n"
+    "    output_prefix_override = true\n"
+    "    ...\n"
+    "  }\n";
+
 const char kOutputs[] = "outputs";
 const char kOutputs_HelpShort[] =
     "outputs: [file list] Output files for actions and copy targets.";
@@ -1609,6 +1638,7 @@
     INSERT_VARIABLE(LibDirs)
     INSERT_VARIABLE(OutputExtension)
     INSERT_VARIABLE(OutputName)
+    INSERT_VARIABLE(OutputPrefixOverride)
     INSERT_VARIABLE(Outputs)
     INSERT_VARIABLE(PrecompiledHeader)
     INSERT_VARIABLE(PrecompiledSource)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 27bde5a..08fea1a 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -191,6 +191,10 @@
 extern const char kOutputName_HelpShort[];
 extern const char kOutputName_Help[];
 
+extern const char kOutputPrefixOverride[];
+extern const char kOutputPrefixOverride_HelpShort[];
+extern const char kOutputPrefixOverride_Help[];
+
 extern const char kOutputs[];
 extern const char kOutputs_HelpShort[];
 extern const char kOutputs_Help[];