Add an output_dir override to GN.
This adds support for the ability for targets to override their output
directory. The tool defines a default output directory in terms of the normal
patterns. The target can optionally supply an override, and the tool uses the
new {{output_dir}} pattern when specifying tool output to express the result of
the default-or-override.
Review URL: https://codereview.chromium.org/1887533003
Cr-Original-Commit-Position: refs/heads/master@{#387366}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: f2eba7f76b6effb82ddce70d2bb227f4e25b1f69
diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc
index 7c81232..7828013 100644
--- a/tools/gn/binary_target_generator.cc
+++ b/tools/gn/binary_target_generator.cc
@@ -7,8 +7,10 @@
#include "tools/gn/config_values_generator.h"
#include "tools/gn/deps_iterator.h"
#include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/scope.h"
+#include "tools/gn/settings.h"
#include "tools/gn/value_extractors.h"
#include "tools/gn/variables.h"
@@ -34,6 +36,9 @@
if (!FillOutputPrefixOverride())
return;
+ if (!FillOutputDir())
+ return;
+
if (!FillOutputExtension())
return;
@@ -98,6 +103,29 @@
return true;
}
+bool BinaryTargetGenerator::FillOutputDir() {
+ const Value* value = scope_->GetValue(variables::kOutputDir, true);
+ if (!value)
+ return true;
+ if (!value->VerifyTypeIs(Value::STRING, err_))
+ return false;
+
+ if (value->string_value().empty())
+ return true; // Treat empty string as the default and do nothing.
+
+ const BuildSettings* build_settings = scope_->settings()->build_settings();
+ SourceDir dir = scope_->GetSourceDir().ResolveRelativeDir(
+ *value, err_, build_settings->root_path_utf8());
+ if (err_->has_error())
+ return false;
+
+ if (!EnsureStringIsInOutputDir(build_settings->build_dir(),
+ dir.value(), value->origin(), err_))
+ return false;
+ target_->set_output_dir(dir);
+ 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 40ed432..0788a20 100644
--- a/tools/gn/binary_target_generator.h
+++ b/tools/gn/binary_target_generator.h
@@ -27,6 +27,7 @@
bool FillCompleteStaticLib();
bool FillOutputName();
bool FillOutputPrefixOverride();
+ bool FillOutputDir();
bool FillOutputExtension();
bool FillAllowCircularIncludesFrom();
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 22cbee0..38e8ce7 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -2730,6 +2730,20 @@
The command to run.
+ default_output_dir [string with substitutions]
+ Valid for: linker tools
+
+ Default directory name for the output file relative to the
+ root_build_dir. It can contain other substitution patterns.
+ This will be the default value for the {{output_dir}} expansion
+ (discussed below) but will be overridden by the "output_dir"
+ variable in a target, if one is specified.
+
+ GN doesn't do anything with this string other than pass it
+ along, potentially with target-specific overrides. It is the
+ tool's job to use the expansion so that the files will be in
+ the right place.
+
default_output_extension [string]
Valid for: linker tools
@@ -2746,7 +2760,7 @@
Example: default_output_extension = ".exe"
- depfile [string]
+ depfile [string with substitutions]
Valid for: compiler tools (optional)
If the tool can write ".d" files, this specifies the name of
@@ -2808,13 +2822,11 @@
]
Example for a linker tool that produces a .dll and a .lib. The
- use of {{output_extension}} rather than hardcoding ".dll"
- allows the extension of the library to be overridden on a
- target-by-target basis, but in this example, it always
- produces a ".lib" import library:
+ use of {{target_output_name}}, {{output_extension}} and
+ {{output_dir}} allows the target to override these values.
outputs = [
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
- "{{root_out_dir}}/{{target_output_name}}.lib",
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+ "{{output_dir}}/{{target_output_name}}.lib",
]
link_output [string with substitutions]
@@ -2827,7 +2839,7 @@
should match entries in the "outputs". If unspecified, the
first item in the "outputs" array will be used for all. See
"Separate linking and dependencies for shared libraries"
- below for more. If link_output is set but runtime_link_output
+ below for more. If link_output is set but runtime_link_output
is not set, runtime_link_output defaults to link_output.
On Windows, where the tools produce a .dll shared library and
@@ -2937,7 +2949,7 @@
{{target_out_dir}}
The directory of the generated file and output directories,
respectively, for the current target. There is no trailing
- slash.
+ slash. See also {{output_dir}} for linker tools.
Example: "out/base/test"
{{target_output_name}}
@@ -3020,6 +3032,21 @@
Example: "-lfoo -lbar"
+ {{output_dir}}
+ The value of the "output_dir" variable in the target, or the
+ the value of the "default_output_dir" value in the tool if the
+ target does not override the output directory. This will be
+ relative to the root_build_dir and will not end in a slash.
+ Will be "." for output to the root_build_dir.
+
+ This is subtly different than {{target_out_dir}} which is
+ defined by GN based on the target's path and not overridable.
+ {{output_dir}} is for the final output, {{target_out_dir}} is
+ generally for object files and other outputs.
+
+ Usually {{output_dir}} would be defined in terms of either
+ {{target_out_dir}} or {{root_out_dir}}
+
{{output_extension}}
The value of the "output_extension" variable in the target,
or the value of the "default_output_extension" value in the
@@ -3075,13 +3102,13 @@
tool("solink") {
command = "..."
outputs = [
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.TOC",
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}",
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC",
]
link_output =
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}"
depend_output =
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.TOC"
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}.TOC"
restat = true
}
@@ -4660,6 +4687,36 @@
```
+## **output_dir**: [directory] Directory to put output file in.
+
+```
+ For library and executable targets, overrides the directory for the
+ final output. This must be in the root_build_dir or a child thereof.
+
+ This should generally be in the root_out_dir or a subdirectory thereof
+ (the root_out_dir will be the same as the root_build_dir for the
+ default toolchain, and will be a subdirectory for other toolchains).
+ Not putting the output in a subdirectory of root_out_dir can result
+ in collisions between different toolchains, so you will need to take
+ steps to ensure that your target is only present in one toolchain.
+
+ Normally the toolchain specifies the output directory for libraries
+ and executables (see "gn help tool"). You will have to consult that
+ for the default location. The default location will be used if
+ output_dir is undefined or empty.
+
+```
+
+### **Example**
+
+```
+ shared_library("doom_melon") {
+ output_dir = "$root_out_dir/plugin_libs"
+ ...
+ }
+
+
+```
## **output_extension**: Value to use for the output's file extension.
```
@@ -5724,6 +5781,10 @@
### **Placeholders**
```
+ This section discusses only placeholders for actions. There are other
+ placeholders used in the definition of tools. See "gn help tool" for
+ those.
+
{{source}}
The name of the source file including directory (*). This will
generally be used for specifying inputs to a script in the
diff --git a/tools/gn/example/build/toolchain/BUILD.gn b/tools/gn/example/build/toolchain/BUILD.gn
index 77e33fe..d9457d7 100644
--- a/tools/gn/example/build/toolchain/BUILD.gn
+++ b/tools/gn/example/build/toolchain/BUILD.gn
@@ -37,9 +37,10 @@
tool("solink") {
soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so".
+ sofile = "{{output_dir}}/$soname"
rspfile = soname + ".rsp"
- command = "g++ -shared {{ldflags}} -o $soname -Wl,-soname=$soname @$rspfile"
+ command = "g++ -shared {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
description = "SOLINK $soname"
@@ -49,11 +50,15 @@
# specifies).
default_output_extension = ".so"
+ # Use this for {{output_dir}} expansions unless a target manually overrides
+ # it (in which case {{output_dir}} will be what the target specifies).
+ default_output_dir = "{{root_out_dir}}"
+
outputs = [
- soname,
+ sofile,
]
- link_output = soname
- depend_output = soname
+ link_output = sofile
+ depend_output = sofile
output_prefix = "lib"
}
@@ -62,6 +67,7 @@
rspfile = "$outfile.rsp"
command = "g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
description = "LINK $outfile"
+ default_output_dir = "{{root_out_dir}}"
rspfile_content = "{{inputs}}"
outputs = [
outfile,
diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc
index f3344ff..bdf5385 100644
--- a/tools/gn/function_toolchain.cc
+++ b/tools/gn/function_toolchain.cc
@@ -418,6 +418,20 @@
"\n"
" The command to run.\n"
"\n"
+ " default_output_dir [string with substitutions]\n"
+ " Valid for: linker tools\n"
+ "\n"
+ " Default directory name for the output file relative to the\n"
+ " root_build_dir. It can contain other substitution patterns.\n"
+ " This will be the default value for the {{output_dir}} expansion\n"
+ " (discussed below) but will be overridden by the \"output_dir\"\n"
+ " variable in a target, if one is specified.\n"
+ "\n"
+ " GN doesn't do anything with this string other than pass it\n"
+ " along, potentially with target-specific overrides. It is the\n"
+ " tool's job to use the expansion so that the files will be in\n"
+ " the right place.\n"
+ "\n"
" default_output_extension [string]\n"
" Valid for: linker tools\n"
"\n"
@@ -434,7 +448,7 @@
"\n"
" Example: default_output_extension = \".exe\"\n"
"\n"
- " depfile [string]\n"
+ " depfile [string with substitutions]\n"
" Valid for: compiler tools (optional)\n"
"\n"
" If the tool can write \".d\" files, this specifies the name of\n"
@@ -496,14 +510,12 @@
" ]\n"
"\n"
" Example for a linker tool that produces a .dll and a .lib. The\n"
- " use of {{output_extension}} rather than hardcoding \".dll\"\n"
- " allows the extension of the library to be overridden on a\n"
- " target-by-target basis, but in this example, it always\n"
- " produces a \".lib\" import library:\n"
+ " use of {{target_output_name}}, {{output_extension}} and\n"
+ " {{output_dir}} allows the target to override these values.\n"
" outputs = [\n"
- " \"{{root_out_dir}}/{{target_output_name}}"
+ " \"{{output_dir}}/{{target_output_name}}"
"{{output_extension}}\",\n"
- " \"{{root_out_dir}}/{{target_output_name}}.lib\",\n"
+ " \"{{output_dir}}/{{target_output_name}}.lib\",\n"
" ]\n"
"\n"
" link_output [string with substitutions]\n"
@@ -516,7 +528,7 @@
" should match entries in the \"outputs\". If unspecified, the\n"
" first item in the \"outputs\" array will be used for all. See\n"
" \"Separate linking and dependencies for shared libraries\"\n"
- " below for more. If link_output is set but runtime_link_output\n"
+ " below for more. If link_output is set but runtime_link_output\n"
" is not set, runtime_link_output defaults to link_output.\n"
"\n"
" On Windows, where the tools produce a .dll shared library and\n"
@@ -624,7 +636,7 @@
" {{target_out_dir}}\n"
" The directory of the generated file and output directories,\n"
" respectively, for the current target. There is no trailing\n"
- " slash.\n"
+ " slash. See also {{output_dir}} for linker tools.\n"
" Example: \"out/base/test\"\n"
"\n"
" {{target_output_name}}\n"
@@ -707,6 +719,21 @@
"\n"
" Example: \"-lfoo -lbar\"\n"
"\n"
+ " {{output_dir}}\n"
+ " The value of the \"output_dir\" variable in the target, or the\n"
+ " the value of the \"default_output_dir\" value in the tool if the\n"
+ " target does not override the output directory. This will be\n"
+ " relative to the root_build_dir and will not end in a slash.\n"
+ " Will be \".\" for output to the root_build_dir.\n"
+ "\n"
+ " This is subtly different than {{target_out_dir}} which is\n"
+ " defined by GN based on the target's path and not overridable.\n"
+ " {{output_dir}} is for the final output, {{target_out_dir}} is\n"
+ " generally for object files and other outputs.\n"
+ "\n"
+ " Usually {{output_dir}} would be defined in terms of either\n"
+ " {{target_out_dir}} or {{root_out_dir}}\n"
+ "\n"
" {{output_extension}}\n"
" The value of the \"output_extension\" variable in the target,\n"
" or the value of the \"default_output_extension\" value in the\n"
@@ -759,14 +786,14 @@
" tool(\"solink\") {\n"
" command = \"...\"\n"
" outputs = [\n"
- " \"{{root_out_dir}}/{{target_output_name}}{{output_extension}}\",\n"
- " \"{{root_out_dir}}/{{target_output_name}}"
+ " \"{{output_dir}}/{{target_output_name}}{{output_extension}}\",\n"
+ " \"{{output_dir}}/{{target_output_name}}"
"{{output_extension}}.TOC\",\n"
" ]\n"
" link_output =\n"
- " \"{{root_out_dir}}/{{target_output_name}}{{output_extension}}\"\n"
+ " \"{{output_dir}}/{{target_output_name}}{{output_extension}}\"\n"
" depend_output =\n"
- " \"{{root_out_dir}}/{{target_output_name}}"
+ " \"{{output_dir}}/{{target_output_name}}"
"{{output_extension}}.TOC\"\n"
" restat = true\n"
" }\n"
@@ -866,6 +893,8 @@
tool.get(), &Tool::set_runtime_link_output, err) ||
!ReadString(&block_scope, "output_prefix", tool.get(),
&Tool::set_output_prefix, err) ||
+ !ReadPattern(&block_scope, "default_output_dir", subst_validator,
+ tool.get(), &Tool::set_default_output_dir, err) ||
!ReadPrecompiledHeaderType(&block_scope, tool.get(), err) ||
!ReadBool(&block_scope, "restat", tool.get(), &Tool::set_restat, err) ||
!ReadPattern(&block_scope, "rspfile", subst_validator, tool.get(),
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 7077b17..a62f872 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -780,7 +780,7 @@
WriteLinkerFlags(optional_def_file);
WriteLibs();
}
- WriteOutputExtension();
+ WriteOutputSubstitutions();
WriteSolibs(solibs);
}
@@ -847,11 +847,15 @@
out_ << std::endl;
}
-void NinjaBinaryTargetWriter::WriteOutputExtension() {
+void NinjaBinaryTargetWriter::WriteOutputSubstitutions() {
out_ << " output_extension = "
<< SubstitutionWriter::GetLinkerSubstitution(
target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
out_ << std::endl;
+ out_ << " output_dir = "
+ << SubstitutionWriter::GetLinkerSubstitution(
+ target_, tool_, SUBSTITUTION_OUTPUT_DIR);
+ out_ << std::endl;
}
void NinjaBinaryTargetWriter::WriteSolibs(
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index db2b15d..1876486 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -99,7 +99,7 @@
const std::vector<SourceFile>& other_files);
void WriteLinkerFlags(const SourceFile* optional_def_file);
void WriteLibs();
- void WriteOutputExtension();
+ void WriteOutputSubstitutions();
void WriteSolibs(const std::vector<OutputFile>& solibs);
// Writes the stamp line for a source set. These are not linked.
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index 1e02446..d620f94 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -82,7 +82,8 @@
"|| obj/foo/bar.stamp\n"
" ldflags =\n"
" libs =\n"
- " output_extension = .so\n";
+ " output_extension = .so\n"
+ " output_dir = \n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
}
@@ -110,7 +111,8 @@
// There are no sources so there are no params to alink. (In practice
// this will probably fail in the archive tool.)
"build obj/foo/libstlib.a: alink || obj/foo/bar.stamp\n"
- " output_extension = \n";
+ " output_extension = \n"
+ " output_dir = \n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
}
@@ -136,15 +138,16 @@
"build obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
"obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
"|| obj/foo/bar.stamp\n"
- " output_extension = \n";
+ " output_extension = \n"
+ " output_dir = \n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
}
}
-// This tests that output extension overrides apply, and input dependencies
-// are applied.
-TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) {
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST(NinjaBinaryTargetWriter, OutputExtensionAndInputDeps) {
TestWithScope setup;
Err err;
@@ -157,10 +160,11 @@
action.SetToolchain(setup.toolchain());
ASSERT_TRUE(action.OnResolved(&err));
- // A shared library w/ the product_extension set to a custom value.
+ // A shared library w/ the output_extension set to a custom value.
Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
target.set_output_type(Target::SHARED_LIBRARY);
target.set_output_extension(std::string("so.6"));
+ target.set_output_dir(SourceDir("//out/Debug/foo/"));
target.sources().push_back(SourceFile("//foo/input1.cc"));
target.sources().push_back(SourceFile("//foo/input2.cc"));
target.public_deps().push_back(LabelTargetPair(&action));
@@ -192,7 +196,8 @@
"obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
" ldflags =\n"
" libs =\n"
- " output_extension = .so.6\n";
+ " output_extension = .so.6\n"
+ " output_dir = foo\n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
@@ -229,19 +234,20 @@
"build ./libshlib.so: solink | ../../foo/lib1.a\n"
" ldflags = -L../../foo/bar\n"
" libs = ../../foo/lib1.a -lfoo\n"
- " output_extension = .so\n";
+ " output_extension = .so\n"
+ " output_dir = \n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
}
-TEST(NinjaBinaryTargetWriter, EmptyProductExtension) {
+TEST(NinjaBinaryTargetWriter, EmptyOutputExtension) {
TestWithScope setup;
Err err;
setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
- // This test is the same as ProductExtension, except that we call
+ // This test is the same as OutputExtensionAndInputDeps, 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"));
@@ -274,7 +280,8 @@
"obj/foo/shlib.input2.o\n"
" ldflags =\n"
" libs =\n"
- " output_extension = \n";
+ " output_extension = \n"
+ " output_dir = \n";
std::string out_str = out.str();
EXPECT_EQ(expected, out_str);
@@ -357,7 +364,8 @@
"obj/foo/inter.stamp\n"
" ldflags =\n"
" libs =\n"
- " output_extension = \n";
+ " output_extension = \n"
+ " output_dir = \n";
EXPECT_EQ(final_expected, final_out.str());
}
@@ -392,7 +400,8 @@
"build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
" ldflags = /DEF:../../foo/bar.def\n"
" libs =\n"
- " output_extension = .so\n";
+ " output_extension = .so\n"
+ " output_dir = \n";
EXPECT_EQ(expected, out.str());
}
@@ -427,7 +436,8 @@
"build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
" ldflags =\n"
" libs =\n"
- " output_extension = .so\n";
+ " output_extension = .so\n"
+ " output_dir = \n";
EXPECT_EQ(loadable_expected, out.str());
// Final target.
@@ -458,7 +468,8 @@
"build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
" ldflags =\n"
" libs =\n"
- " output_extension = \n";
+ " output_extension = \n"
+ " output_dir = \n";
EXPECT_EQ(final_expected, final_out.str());
}
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index e1ea14d..75d8b7a 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -43,6 +43,7 @@
"{{inputs_newline}}", // SUBSTITUTION_LINKER_INPUTS_NEWLINE
"{{ldflags}}", // SUBSTITUTION_LDFLAGS
"{{libs}}", // SUBSTITUTION_LIBS
+ "{{output_dir}}", // SUBSTITUTION_OUTPUT_DIR
"{{output_extension}}", // SUBSTITUTION_OUTPUT_EXTENSION
"{{solibs}}", // SUBSTITUTION_SOLIBS
@@ -91,6 +92,7 @@
"in_newline", // SUBSTITUTION_LINKER_INPUTS_NEWLINE
"ldflags", // SUBSTITUTION_LDFLAGS
"libs", // SUBSTITUTION_LIBS
+ "output_dir", // SUBSTITUTION_OUTPUT_DIR
"output_extension", // SUBSTITUTION_OUTPUT_EXTENSION
"solibs", // SUBSTITUTION_SOLIBS
@@ -193,6 +195,7 @@
type == SUBSTITUTION_LINKER_INPUTS_NEWLINE ||
type == SUBSTITUTION_LDFLAGS ||
type == SUBSTITUTION_LIBS ||
+ type == SUBSTITUTION_OUTPUT_DIR ||
type == SUBSTITUTION_OUTPUT_EXTENSION ||
type == SUBSTITUTION_SOLIBS;
}
@@ -200,6 +203,7 @@
bool IsValidLinkerOutputsSubstitution(SubstitutionType type) {
// All valid compiler outputs plus the output extension.
return IsValidCompilerOutputsSubstitution(type) ||
+ type == SUBSTITUTION_OUTPUT_DIR ||
type == SUBSTITUTION_OUTPUT_EXTENSION;
}
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 3f7ec97..2e63373 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -56,6 +56,7 @@
SUBSTITUTION_LINKER_INPUTS_NEWLINE, // {{inputs_newline}}
SUBSTITUTION_LDFLAGS, // {{ldflags}}
SUBSTITUTION_LIBS, // {{libs}}
+ SUBSTITUTION_OUTPUT_DIR, // {{output_dir}}
SUBSTITUTION_OUTPUT_EXTENSION, // {{output_extension}}
SUBSTITUTION_SOLIBS, // {{solibs}}
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 23d6914..a82e7ee 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -56,6 +56,10 @@
"\n"
"Placeholders\n"
"\n"
+ " This section discusses only placeholders for actions. There are other\n"
+ " placeholders used in the definition of tools. See \"gn help tool\" for\n"
+ " those.\n"
+ "\n"
" {{source}}\n"
" The name of the source file including directory (*). This will\n"
" generally be used for specifying inputs to a script in the\n"
@@ -546,6 +550,23 @@
// Fall-through to the linker-specific ones.
switch (type) {
+ case SUBSTITUTION_OUTPUT_DIR:
+ // Use the target's value if there is one (it will have no expansion
+ // patterns since it can directly use GN variables to compute whatever
+ // path it wants), or the tool's default (which will contain further
+ // expansions).
+ if (target->output_dir().is_null()) {
+ return ApplyPatternToLinkerAsOutputFile(
+ target, tool, tool->default_output_dir()).value();
+ } else {
+ SetDirOrDotWithNoSlash(RebasePath(
+ target->output_dir().value(),
+ target->settings()->build_settings()->build_dir()),
+ &result);
+ return result;
+ }
+ break;
+
case SUBSTITUTION_OUTPUT_EXTENSION:
// Use the extension provided on the target if specified, otherwise
// fall back on the default. Note that the target's output extension
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index 77374f1..d252c79 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -278,4 +278,46 @@
EXPECT_EQ("",
SubstitutionWriter::GetLinkerSubstitution(
&target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+
+ // Output directory is tested in a separate test below.
+}
+
+TEST(SubstitutionWriter, OutputDir) {
+ TestWithScope setup;
+ Err err;
+
+ // This tool has an output directory pattern and uses that for the
+ // output name.
+ Tool tool;
+ SubstitutionPattern out_dir_pattern;
+ ASSERT_TRUE(out_dir_pattern.Parse("{{root_out_dir}}/{{target_output_name}}",
+ nullptr, &err));
+ tool.set_default_output_dir(out_dir_pattern);
+ tool.SetComplete();
+
+ // Default target with no output dir overrides.
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "baz"));
+ target.set_output_type(Target::EXECUTABLE);
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ // The output should expand the default from the patterns in the tool.
+ SubstitutionPattern output_name;
+ ASSERT_TRUE(output_name.Parse("{{output_dir}}/{{target_output_name}}.exe",
+ nullptr, &err));
+ EXPECT_EQ("./baz/baz.exe",
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ &target, &tool, output_name).value());
+
+ // Override the output name to the root build dir.
+ target.set_output_dir(SourceDir("//out/Debug/"));
+ EXPECT_EQ("./baz.exe",
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ &target, &tool, output_name).value());
+
+ // Override the output name to a new subdirectory.
+ target.set_output_dir(SourceDir("//out/Debug/foo/bar"));
+ EXPECT_EQ("foo/bar/baz.exe",
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ &target, &tool, output_name).value());
}
diff --git a/tools/gn/target.h b/tools/gn/target.h
index fdd6f1a..ac159e1 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -100,6 +100,12 @@
output_prefix_override_ = prefix_override;
}
+ // Desired output directory for the final output. This will be used for
+ // the {{output_dir}} substitution in the tool if it is specified. If
+ // is_null, the tool default will be used.
+ const SourceDir& output_dir() const { return output_dir_; }
+ void set_output_dir(const SourceDir& dir) { output_dir_ = dir; }
+
// 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
@@ -332,6 +338,7 @@
OutputType output_type_;
std::string output_name_;
bool output_prefix_override_;
+ SourceDir output_dir_;
std::string output_extension_;
bool output_extension_set_;
diff --git a/tools/gn/tool.h b/tools/gn/tool.h
index 9360ce6..f6727ed 100644
--- a/tools/gn/tool.h
+++ b/tools/gn/tool.h
@@ -52,6 +52,14 @@
default_output_extension_ = ext;
}
+ const SubstitutionPattern& default_output_dir() const {
+ return default_output_dir_;
+ }
+ void set_default_output_dir(const SubstitutionPattern& dir) {
+ DCHECK(!complete_);
+ default_output_dir_ = dir;
+ }
+
// Dependency file (if supported).
const SubstitutionPattern& depfile() const {
return depfile_;
@@ -186,6 +194,7 @@
private:
SubstitutionPattern command_;
std::string default_output_extension_;
+ SubstitutionPattern default_output_dir_;
SubstitutionPattern depfile_;
DepsFormat depsformat_;
PrecompiledHeaderType precompiled_header_type_;
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 4c5fae1..46cd57e 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1119,6 +1119,34 @@
" }\n"
" }\n";
+const char kOutputDir[] = "output_dir";
+const char kOutputDir_HelpShort[] =
+ "output_dir: [directory] Directory to put output file in.";
+const char kOutputDir_Help[] =
+ "output_dir: [directory] Directory to put output file in.\n"
+ "\n"
+ " For library and executable targets, overrides the directory for the\n"
+ " final output. This must be in the root_build_dir or a child thereof.\n"
+ "\n"
+ " This should generally be in the root_out_dir or a subdirectory thereof\n"
+ " (the root_out_dir will be the same as the root_build_dir for the\n"
+ " default toolchain, and will be a subdirectory for other toolchains).\n"
+ " Not putting the output in a subdirectory of root_out_dir can result\n"
+ " in collisions between different toolchains, so you will need to take\n"
+ " steps to ensure that your target is only present in one toolchain.\n"
+ "\n"
+ " Normally the toolchain specifies the output directory for libraries\n"
+ " and executables (see \"gn help tool\"). You will have to consult that\n"
+ " for the default location. The default location will be used if\n"
+ " output_dir is undefined or empty.\n"
+ "\n"
+ "Example\n"
+ "\n"
+ " shared_library(\"doom_melon\") {\n"
+ " output_dir = \"$root_out_dir/plugin_libs\"\n"
+ " ...\n"
+ " }\n";
+
const char kOutputName[] = "output_name";
const char kOutputName_HelpShort[] =
"output_name: [string] Name for the output file other than the default.";
@@ -1636,6 +1664,7 @@
INSERT_VARIABLE(Ldflags)
INSERT_VARIABLE(Libs)
INSERT_VARIABLE(LibDirs)
+ INSERT_VARIABLE(OutputDir)
INSERT_VARIABLE(OutputExtension)
INSERT_VARIABLE(OutputName)
INSERT_VARIABLE(OutputPrefixOverride)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 08fea1a..2dafdee 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -183,6 +183,10 @@
extern const char kLibs_HelpShort[];
extern const char kLibs_Help[];
+extern const char kOutputDir[];
+extern const char kOutputDir_HelpShort[];
+extern const char kOutputDir_Help[];
+
extern const char kOutputExtension[];
extern const char kOutputExtension_HelpShort[];
extern const char kOutputExtension_Help[];