summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/gn/BUILD.gn11
-rw-r--r--tools/gn/builder.cc21
-rw-r--r--tools/gn/builder.h1
-rw-r--r--tools/gn/builder_unittest.cc2
-rw-r--r--tools/gn/command_gen.cc11
-rw-r--r--tools/gn/config_values_extractors_unittest.cc3
-rw-r--r--tools/gn/escape.cc14
-rw-r--r--tools/gn/escape.h10
-rw-r--r--tools/gn/escape_unittest.cc8
-rw-r--r--tools/gn/example/.gn2
-rw-r--r--tools/gn/example/BUILD.gn28
-rw-r--r--tools/gn/example/README.txt4
-rw-r--r--tools/gn/example/build/BUILD.gn19
-rw-r--r--tools/gn/example/build/BUILDCONFIG.gn26
-rw-r--r--tools/gn/example/build/toolchain/BUILD.gn78
-rw-r--r--tools/gn/example/hello.cc13
-rw-r--r--tools/gn/example/hello_shared.cc9
-rw-r--r--tools/gn/example/hello_shared.h32
-rw-r--r--tools/gn/example/hello_static.cc9
-rw-r--r--tools/gn/example/hello_static.h10
-rw-r--r--tools/gn/filesystem_utils.cc104
-rw-r--r--tools/gn/filesystem_utils.h36
-rw-r--r--tools/gn/filesystem_utils_unittest.cc117
-rw-r--r--tools/gn/function_get_target_outputs.cc75
-rw-r--r--tools/gn/function_get_target_outputs_unittest.cc24
-rw-r--r--tools/gn/function_rebase_path.cc2
-rw-r--r--tools/gn/function_toolchain.cc659
-rw-r--r--tools/gn/function_write_file.cc2
-rw-r--r--tools/gn/gn.gyp11
-rw-r--r--tools/gn/header_checker.cc1
-rw-r--r--tools/gn/ninja_action_target_writer.cc45
-rw-r--r--tools/gn/ninja_action_target_writer.h10
-rw-r--r--tools/gn/ninja_action_target_writer_unittest.cc43
-rw-r--r--tools/gn/ninja_binary_target_writer.cc389
-rw-r--r--tools/gn/ninja_binary_target_writer.h42
-rw-r--r--tools/gn/ninja_binary_target_writer_unittest.cc143
-rw-r--r--tools/gn/ninja_build_writer.cc11
-rw-r--r--tools/gn/ninja_build_writer.h3
-rw-r--r--tools/gn/ninja_copy_target_writer.cc65
-rw-r--r--tools/gn/ninja_copy_target_writer.h10
-rw-r--r--tools/gn/ninja_copy_target_writer_unittest.cc13
-rw-r--r--tools/gn/ninja_group_target_writer.cc30
-rw-r--r--tools/gn/ninja_group_target_writer.h4
-rw-r--r--tools/gn/ninja_helper.cc240
-rw-r--r--tools/gn/ninja_helper.h81
-rw-r--r--tools/gn/ninja_helper_unittest.cc99
-rw-r--r--tools/gn/ninja_target_writer.cc157
-rw-r--r--tools/gn/ninja_target_writer.h22
-rw-r--r--tools/gn/ninja_target_writer_unittest.cc10
-rw-r--r--tools/gn/ninja_toolchain_writer.cc93
-rw-r--r--tools/gn/ninja_toolchain_writer.h16
-rw-r--r--tools/gn/ninja_toolchain_writer_unittest.cc31
-rw-r--r--tools/gn/ninja_utils.cc27
-rw-r--r--tools/gn/ninja_utils.h25
-rw-r--r--tools/gn/output_file.cc39
-rw-r--r--tools/gn/output_file.h19
-rw-r--r--tools/gn/path_output.cc8
-rw-r--r--tools/gn/path_output.h5
-rw-r--r--tools/gn/source_file_type.cc31
-rw-r--r--tools/gn/source_file_type.h25
-rw-r--r--tools/gn/string_utils.cc7
-rw-r--r--tools/gn/string_utils.h5
-rw-r--r--tools/gn/substitution_list.cc19
-rw-r--r--tools/gn/substitution_list.h4
-rw-r--r--tools/gn/substitution_pattern.cc50
-rw-r--r--tools/gn/substitution_pattern.h14
-rw-r--r--tools/gn/substitution_type.cc56
-rw-r--r--tools/gn/substitution_type.h27
-rw-r--r--tools/gn/substitution_writer.cc323
-rw-r--r--tools/gn/substitution_writer.h113
-rw-r--r--tools/gn/substitution_writer_unittest.cc111
-rw-r--r--tools/gn/target.cc117
-rw-r--r--tools/gn/target.h52
-rw-r--r--tools/gn/target_generator.cc2
-rw-r--r--tools/gn/target_unittest.cc156
-rw-r--r--tools/gn/test_with_scope.cc100
-rw-r--r--tools/gn/test_with_scope.h6
-rw-r--r--tools/gn/tool.cc28
-rw-r--r--tools/gn/tool.h197
-rw-r--r--tools/gn/toolchain.cc92
-rw-r--r--tools/gn/toolchain.h57
81 files changed, 3251 insertions, 1363 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 7832727..e3724ad 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -93,8 +93,8 @@ static_library("gn_lib") {
"ninja_copy_target_writer.h",
"ninja_group_target_writer.cc",
"ninja_group_target_writer.h",
- "ninja_helper.cc",
- "ninja_helper.h",
+ "ninja_utils.cc",
+ "ninja_utils.h",
"ninja_target_writer.cc",
"ninja_target_writer.h",
"ninja_toolchain_writer.cc",
@@ -103,6 +103,7 @@ static_library("gn_lib") {
"ninja_writer.h",
"operators.cc",
"operators.h",
+ "output_file.cc",
"output_file.h",
"parse_tree.cc",
"parse_tree.h",
@@ -126,6 +127,8 @@ static_library("gn_lib") {
"source_dir.h",
"source_file.cc",
"source_file.h",
+ "source_file_type.cc",
+ "source_file_type.h",
"standard_out.cc",
"standard_out.h",
"string_utils.cc",
@@ -147,6 +150,8 @@ static_library("gn_lib") {
"token.cc",
"token.h",
"tokenizer.cc",
+ "tool.cc",
+ "tool.h",
"tokenizer.h",
"toolchain.cc",
"toolchain.h",
@@ -204,8 +209,8 @@ test("gn_unittests") {
"ninja_action_target_writer_unittest.cc",
"ninja_binary_target_writer_unittest.cc",
"ninja_copy_target_writer_unittest.cc",
- "ninja_helper_unittest.cc",
"ninja_target_writer_unittest.cc",
+ "ninja_toolchain_writer_unittest.cc",
"operators_unittest.cc",
"parse_tree_unittest.cc",
"parser_unittest.cc",
diff --git a/tools/gn/builder.cc b/tools/gn/builder.cc
index d498b10..dccc095 100644
--- a/tools/gn/builder.cc
+++ b/tools/gn/builder.cc
@@ -408,7 +408,8 @@ bool Builder::ResolveItem(BuilderRecord* record, Err* err) {
!ResolveConfigs(&target->configs(), err) ||
!ResolveConfigs(&target->all_dependent_configs(), err) ||
!ResolveConfigs(&target->direct_dependent_configs(), err) ||
- !ResolveForwardDependentConfigs(target, err))
+ !ResolveForwardDependentConfigs(target, err) ||
+ !ResolveToolchain(target, err))
return false;
} else if (record->type() == BuilderRecord::ITEM_TOOLCHAIN) {
Toolchain* toolchain = record->item()->AsToolchain();
@@ -499,6 +500,24 @@ bool Builder::ResolveForwardDependentConfigs(Target* target, Err* err) {
return true;
}
+bool Builder::ResolveToolchain(Target* target, Err* err) {
+ BuilderRecord* record = GetResolvedRecordOfType(
+ target->settings()->toolchain_label(), target->defined_from(),
+ BuilderRecord::ITEM_TOOLCHAIN, err);
+ if (!record) {
+ *err = Err(target->defined_from(),
+ "Toolchain for target not defined.",
+ "I was hoping to find a toolchain " +
+ target->settings()->toolchain_label().GetUserVisibleName(false));
+ return false;
+ }
+
+ if (!target->SetToolchain(record->item()->AsToolchain(), err))
+ return false;
+
+ return true;
+}
+
std::string Builder::CheckForCircularDependencies(
const std::vector<const BuilderRecord*>& bad_records) const {
std::vector<const BuilderRecord*> cycle;
diff --git a/tools/gn/builder.h b/tools/gn/builder.h
index 654a6ad..56aa6e4 100644
--- a/tools/gn/builder.h
+++ b/tools/gn/builder.h
@@ -118,6 +118,7 @@ class Builder : public base::RefCountedThreadSafe<Builder> {
bool ResolveDeps(LabelTargetVector* deps, Err* err);
bool ResolveConfigs(UniqueVector<LabelConfigPair>* configs, Err* err);
bool ResolveForwardDependentConfigs(Target* target, Err* err);
+ bool ResolveToolchain(Target* target, Err* err);
// Given a list of unresolved records, tries to find any circular
// dependencies and returns the string describing the problem. If no circular
diff --git a/tools/gn/builder_unittest.cc b/tools/gn/builder_unittest.cc
index cbeed90..bac9192 100644
--- a/tools/gn/builder_unittest.cc
+++ b/tools/gn/builder_unittest.cc
@@ -71,6 +71,7 @@ class BuilderTest : public testing::Test {
Toolchain* DefineToolchain() {
Toolchain* tc = new Toolchain(&settings_, settings_.toolchain_label());
+ TestWithScope::SetupToolchain(tc);
builder_->ItemDefined(scoped_ptr<Item>(tc));
return tc;
}
@@ -184,6 +185,7 @@ TEST_F(BuilderTest, ShouldGenerate) {
Label toolchain_label2(SourceDir("//tc/"), "secondary");
settings2.set_toolchain_label(toolchain_label2);
Toolchain* tc2 = new Toolchain(&settings2, toolchain_label2);
+ TestWithScope::SetupToolchain(tc2);
builder_->ItemDefined(scoped_ptr<Item>(tc2));
// Construct a dependency chain: A -> B. A is in the default toolchain, B
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index 086789d..0df121a 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -14,6 +14,7 @@
#include "tools/gn/scheduler.h"
#include "tools/gn/setup.h"
#include "tools/gn/standard_out.h"
+#include "tools/gn/target.h"
namespace commands {
@@ -25,7 +26,6 @@ const char kSwitchQuiet[] = "q";
const char kSwitchCheck[] = "check";
void BackgroundDoWrite(const Target* target,
- const Toolchain* toolchain,
const std::vector<const Item*>& deps_for_visibility) {
// Validate visibility.
Err err;
@@ -38,7 +38,7 @@ void BackgroundDoWrite(const Target* target,
}
if (!err.has_error())
- NinjaTargetWriter::RunAndWriteFile(target, toolchain);
+ NinjaTargetWriter::RunAndWriteFile(target);
g_scheduler->DecrementWorkCount();
}
@@ -51,10 +51,6 @@ void ItemResolvedCallback(base::subtle::Atomic32* write_counter,
const Item* item = record->item();
const Target* target = item->AsTarget();
if (target) {
- const Toolchain* toolchain =
- builder->GetToolchain(target->settings()->toolchain_label());
- DCHECK(toolchain);
-
// Collect all dependencies.
std::vector<const Item*> deps;
for (BuilderRecord::BuilderRecordSet::const_iterator iter =
@@ -64,8 +60,7 @@ void ItemResolvedCallback(base::subtle::Atomic32* write_counter,
deps.push_back((*iter)->item());
g_scheduler->IncrementWorkCount();
- g_scheduler->ScheduleWork(
- base::Bind(&BackgroundDoWrite, target, toolchain, deps));
+ g_scheduler->ScheduleWork(base::Bind(&BackgroundDoWrite, target, deps));
}
}
diff --git a/tools/gn/config_values_extractors_unittest.cc b/tools/gn/config_values_extractors_unittest.cc
index 0076b9e..a408226 100644
--- a/tools/gn/config_values_extractors_unittest.cc
+++ b/tools/gn/config_values_extractors_unittest.cc
@@ -45,6 +45,7 @@ TEST(ConfigValuesExtractors, IncludeOrdering) {
Target dep2(setup.settings(), Label(SourceDir("//dep2/"), "dep2"));
dep2.set_output_type(Target::SOURCE_SET);
+ dep2.SetToolchain(setup.toolchain());
dep2.all_dependent_configs().push_back(LabelConfigPair(&dep2_all));
dep2.direct_dependent_configs().push_back(LabelConfigPair(&dep2_direct));
@@ -60,6 +61,7 @@ TEST(ConfigValuesExtractors, IncludeOrdering) {
Target dep1(setup.settings(), Label(SourceDir("//dep1/"), "dep1"));
dep1.set_output_type(Target::SOURCE_SET);
+ dep1.SetToolchain(setup.toolchain());
dep1.all_dependent_configs().push_back(LabelConfigPair(&dep1_all));
dep1.direct_dependent_configs().push_back(LabelConfigPair(&dep1_direct));
dep1.deps().push_back(LabelTargetPair(&dep2));
@@ -85,6 +87,7 @@ TEST(ConfigValuesExtractors, IncludeOrdering) {
Target target(setup.settings(), Label(SourceDir("//target/"), "target"));
target.set_output_type(Target::SOURCE_SET);
+ target.SetToolchain(setup.toolchain());
target.all_dependent_configs().push_back(LabelConfigPair(&target_all));
target.direct_dependent_configs().push_back(LabelConfigPair(&target_direct));
target.configs().push_back(LabelConfigPair(&target_config));
diff --git a/tools/gn/escape.cc b/tools/gn/escape.cc
index 58bb04e..06f6ee8 100644
--- a/tools/gn/escape.cc
+++ b/tools/gn/escape.cc
@@ -48,6 +48,17 @@ void EscapeStringToString_Ninja(const base::StringPiece& str,
NinjaEscapeChar(str[i], dest);
}
+template<typename DestString>
+void EscapeStringToString_NinjaPreformatted(const base::StringPiece& str,
+ DestString* dest) {
+ // Only Ninja-escape $.
+ for (size_t i = 0; i < str.size(); i++) {
+ if (str[i] == '$')
+ dest->push_back('$');
+ dest->push_back(str[i]);
+ }
+}
+
// Escape for CommandLineToArgvW and additionally escape Ninja characters.
//
// The basic algorithm is if the string doesn't contain any parse-affecting
@@ -166,6 +177,9 @@ void EscapeStringToString(const base::StringPiece& str,
NOTREACHED();
}
break;
+ case ESCAPE_NINJA_PREFORMATTED_COMMAND:
+ EscapeStringToString_NinjaPreformatted(str, dest);
+ break;
default:
NOTREACHED();
}
diff --git a/tools/gn/escape.h b/tools/gn/escape.h
index 6f8cf9c..838de94 100644
--- a/tools/gn/escape.h
+++ b/tools/gn/escape.h
@@ -16,8 +16,16 @@ enum EscapingMode {
// Ninja string escaping.
ESCAPE_NINJA,
- // For writing commands to ninja files.
+ // For writing commands to ninja files. This assumes the output is "one
+ // thing" like a filename, so will escape or quote spaces as necessary for
+ // both Ninja and the shell to keep that thing together.
ESCAPE_NINJA_COMMAND,
+
+ // For writing preformatted shell commands to Ninja files. This assumes the
+ // output already has the proper quoting and may include special shell
+ // shell characters which we want to pass to the shell (like when writing
+ // tool commands). Only Ninja "$" are escaped.
+ ESCAPE_NINJA_PREFORMATTED_COMMAND,
};
enum EscapingPlatform {
diff --git a/tools/gn/escape_unittest.cc b/tools/gn/escape_unittest.cc
index 1a79a78..fd49348 100644
--- a/tools/gn/escape_unittest.cc
+++ b/tools/gn/escape_unittest.cc
@@ -50,3 +50,11 @@ TEST(Escape, PosixCommand) {
// Some more generic shell chars.
EXPECT_EQ("a_\\;\\<\\*b", EscapeString("a_;<*b", opts, NULL));
}
+
+TEST(Escape, NinjaPreformatted) {
+ EscapeOptions opts;
+ opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+
+ // Only $ is escaped.
+ EXPECT_EQ("a: \"$$\\b<;", EscapeString("a: \"$\\b<;", opts, NULL));
+}
diff --git a/tools/gn/example/.gn b/tools/gn/example/.gn
new file mode 100644
index 0000000..e5b6d4a
--- /dev/null
+++ b/tools/gn/example/.gn
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/BUILDCONFIG.gn"
diff --git a/tools/gn/example/BUILD.gn b/tools/gn/example/BUILD.gn
new file mode 100644
index 0000000..2cb0136
--- /dev/null
+++ b/tools/gn/example/BUILD.gn
@@ -0,0 +1,28 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+executable("hello") {
+ sources = [ "hello.cc" ]
+
+ deps = [
+ ":hello_static",
+ ":hello_shared",
+ ]
+}
+
+shared_library("hello_shared") {
+ sources = [
+ "hello_shared.cc",
+ "hello_shared.h"
+ ]
+
+ defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
+}
+
+static_library("hello_static") {
+ sources = [
+ "hello_static.cc",
+ "hello_static.h",
+ ]
+}
diff --git a/tools/gn/example/README.txt b/tools/gn/example/README.txt
new file mode 100644
index 0000000..d0ddeed
--- /dev/null
+++ b/tools/gn/example/README.txt
@@ -0,0 +1,4 @@
+This is an example directory structure that compiles some simple targets using
+gcc. It is intended to show how to set up a simple GN build.
+
+Don't miss the ".gn" file in this directory!
diff --git a/tools/gn/example/build/BUILD.gn b/tools/gn/example/build/BUILD.gn
new file mode 100644
index 0000000..6224372
--- /dev/null
+++ b/tools/gn/example/build/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("compiler_defaults") {
+ if (os == "linux") {
+ cflags = [
+ "-fPIC",
+ "-pthread",
+ ]
+ }
+}
+
+config("executable_ldconfig") {
+ ldflags = [
+ "-Wl,-rpath=\$ORIGIN/",
+ "-Wl,-rpath-link=",
+ ]
+}
diff --git a/tools/gn/example/build/BUILDCONFIG.gn b/tools/gn/example/build/BUILDCONFIG.gn
new file mode 100644
index 0000000..f36567d
--- /dev/null
+++ b/tools/gn/example/build/BUILDCONFIG.gn
@@ -0,0 +1,26 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# All binary targets will get this list of configs by default.
+_shared_binary_target_configs = [
+ "//build:compiler_defaults",
+]
+
+# Apply that default list to the binary target types.
+set_defaults("executable") {
+ configs = _shared_binary_target_configs
+ # Executables get this additional configuration.
+ configs += [ "//build:executable_ldconfig" ]
+}
+set_defaults("static_library") {
+ configs = _shared_binary_target_configs
+}
+set_defaults("shared_library") {
+ configs = _shared_binary_target_configs
+}
+set_defaults("source_set") {
+ configs = _shared_binary_target_configs
+}
+
+set_default_toolchain("//build/toolchain:gcc")
diff --git a/tools/gn/example/build/toolchain/BUILD.gn b/tools/gn/example/build/toolchain/BUILD.gn
new file mode 100644
index 0000000..27a1e31
--- /dev/null
+++ b/tools/gn/example/build/toolchain/BUILD.gn
@@ -0,0 +1,78 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+toolchain("gcc") {
+ tool("cc") {
+ depfile = "{{output}}.d"
+ command = "gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+ depsformat = "gcc"
+ description = "CC {{output}}"
+ outputs = [
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+ ]
+ }
+
+ tool("cxx") {
+ depfile = "{{output}}.d"
+ command = "g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+ depsformat = "gcc"
+ description = "CXX {{output}}"
+ outputs = [
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+ ]
+ }
+
+ tool("alink") {
+ rspfile = "{{output}}.rsp"
+ command = "rm -f {{output}} && ar rcs {{output}} @$rspfile"
+ description = "AR {{target_output_name}}{{output_extension}}"
+ rspfile_content = "{{inputs}}"
+ outputs = [
+ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"
+ ]
+ default_output_extension = ".a"
+ output_prefix = "lib"
+ }
+
+ tool("solink") {
+ soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so".
+ rspfile = soname + ".rsp"
+
+ command = "g++ -shared {{ldflags}} -o $soname -Wl,-soname=$soname @$rspfile"
+ rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+
+ description = "SOLINK $soname"
+
+ # Use this for {{output_extension}} expansions unless a target manually
+ # overrides it (in which case {{output_extension}} will be what the target
+ # specifies).
+ default_output_extension = ".so"
+
+ outputs = [
+ soname,
+ ]
+ link_output = soname
+ depend_output = soname
+ output_prefix = "lib"
+ }
+
+ tool("link") {
+ outfile = "{{target_output_name}}{{output_extension}}"
+ rspfile = "$outfile.rsp"
+ command = "g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
+ description = "LINK $outfile"
+ rspfile_content = "{{inputs}}"
+ outputs = [ outfile ]
+ }
+
+ tool("stamp") {
+ command = "touch {{output}}"
+ description = "STAMP {{output}}"
+ }
+
+ tool("copy") {
+ command = "cp -af {{source}} {{output}}"
+ description = "COPY {{source}} {{output}}"
+ }
+}
diff --git a/tools/gn/example/hello.cc b/tools/gn/example/hello.cc
new file mode 100644
index 0000000..c4aa448
--- /dev/null
+++ b/tools/gn/example/hello.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+
+#include "hello_shared.h"
+#include "hello_static.h"
+
+int main(int argc, char* argv[]) {
+ printf("%s, %s\n", GetStaticText(), GetSharedText());
+ return 0;
+}
diff --git a/tools/gn/example/hello_shared.cc b/tools/gn/example/hello_shared.cc
new file mode 100644
index 0000000..58be84c
--- /dev/null
+++ b/tools/gn/example/hello_shared.cc
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hello_shared.h"
+
+const char* GetSharedText() {
+ return "world";
+}
diff --git a/tools/gn/example/hello_shared.h b/tools/gn/example/hello_shared.h
new file mode 100644
index 0000000..f62b5ee
--- /dev/null
+++ b/tools/gn/example/hello_shared.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HELLO_SHARED_H_
+#define HELLO_SHARED_H_
+
+#if defined(WIN32)
+
+#if defined(HELLO_SHARED_IMPLEMENTATION)
+#define HELLO_EXPORT __declspec(dllexport)
+#define HELLO_EXPORT_PRIVATE __declspec(dllexport)
+#else
+#define HELLO_EXPORT __declspec(dllimport)
+#define HELLO_EXPORT_PRIVATE __declspec(dllimport)
+#endif // defined(HELLO_SHARED_IMPLEMENTATION)
+
+#else
+
+#if defined(HELLO_IMPLEMENTATION)
+#define HELLO_EXPORT __attribute__((visibility("default")))
+#define HELLO_EXPORT_PRIVATE __attribute__((visibility("default")))
+#else
+#define HELLO_EXPORT
+#define HELLO_EXPORT_PRIVATE
+#endif // defined(HELLO_IMPLEMENTATION)
+
+#endif
+
+HELLO_EXPORT const char* GetSharedText();
+
+#endif // HELLO_SHARED_H_
diff --git a/tools/gn/example/hello_static.cc b/tools/gn/example/hello_static.cc
new file mode 100644
index 0000000..cdf4e67
--- /dev/null
+++ b/tools/gn/example/hello_static.cc
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hello_static.h"
+
+const char* GetStaticText() {
+ return "Hello";
+}
diff --git a/tools/gn/example/hello_static.h b/tools/gn/example/hello_static.h
new file mode 100644
index 0000000..248ca05
--- /dev/null
+++ b/tools/gn/example/hello_static.h
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HELLO_STATIC_H_
+#define HELLO_STATIC_H_
+
+const char* GetStaticText();
+
+#endif // HELLO_STATIC_H_
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index c1e3200..7bda4c8 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -164,28 +164,6 @@ bool FilesystemStringsEqual(const base::FilePath::StringType& a,
} // namespace
-SourceFileType GetSourceFileType(const SourceFile& file) {
- base::StringPiece extension = FindExtension(&file.value());
- if (extension == "cc" || extension == "cpp" || extension == "cxx")
- return SOURCE_CC;
- if (extension == "h")
- return SOURCE_H;
- if (extension == "c")
- return SOURCE_C;
- if (extension == "m")
- return SOURCE_M;
- if (extension == "mm")
- return SOURCE_MM;
- if (extension == "rc")
- return SOURCE_RC;
- if (extension == "S" || extension == "s")
- return SOURCE_S;
- if (extension == "o" || extension == "obj")
- return SOURCE_O;
-
- return SOURCE_UNKNOWN;
-}
-
const char* GetExtensionForOutputType(Target::OutputType type,
Settings::TargetOS os) {
switch (os) {
@@ -333,7 +311,7 @@ base::StringPiece FindLastDirComponent(const SourceDir& dir) {
bool EnsureStringIsInOutputDir(const SourceDir& dir,
const std::string& str,
- const Value& originating,
+ const ParseNode* origin,
Err* err) {
// This check will be wrong for all proper prefixes "e.g. "/output" will
// match "/out" but we don't really care since this is just a sanity check.
@@ -341,7 +319,7 @@ bool EnsureStringIsInOutputDir(const SourceDir& dir,
if (str.compare(0, dir_str.length(), dir_str) == 0)
return true; // Output directory is hardcoded.
- *err = Err(originating, "File is not inside output directory.",
+ *err = Err(origin, "File is not inside output directory.",
"The given file should be in the output directory. Normally you would "
"specify\n\"$target_out_dir/foo\" or "
"\"$target_gen_dir/foo\". I interpreted this as\n\""
@@ -673,13 +651,8 @@ std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) {
}
SourceDir GetToolchainOutputDir(const Settings* settings) {
- const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
-
- std::string result = settings->build_settings()->build_dir().value();
- if (!toolchain_subdir.value().empty())
- result.append(toolchain_subdir.value());
-
- return SourceDir(SourceDir::SWAP_IN, &result);
+ return settings->toolchain_output_subdir().AsSourceDir(
+ settings->build_settings());
}
SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
@@ -690,14 +663,14 @@ SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
}
SourceDir GetToolchainGenDir(const Settings* settings) {
- const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
-
- std::string result = settings->build_settings()->build_dir().value();
- if (!toolchain_subdir.value().empty())
- result.append(toolchain_subdir.value());
+ return GetToolchainGenDirAsOutputFile(settings).AsSourceDir(
+ settings->build_settings());
+}
- result.append("gen/");
- return SourceDir(SourceDir::SWAP_IN, &result);
+OutputFile GetToolchainGenDirAsOutputFile(const Settings* settings) {
+ OutputFile result(settings->toolchain_output_subdir());
+ result.value().append("gen/");
+ return result;
}
SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
@@ -710,50 +683,69 @@ SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
SourceDir GetOutputDirForSourceDir(const Settings* settings,
const SourceDir& source_dir) {
- SourceDir toolchain = GetToolchainOutputDir(settings);
+ return GetOutputDirForSourceDirAsOutputFile(settings, source_dir).AsSourceDir(
+ settings->build_settings());
+}
- std::string ret;
- toolchain.SwapValue(&ret);
- ret.append("obj/");
+OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir) {
+ OutputFile result = settings->toolchain_output_subdir();
+ result.value().append("obj/");
if (source_dir.is_source_absolute()) {
// The source dir is source-absolute, so we trim off the two leading
// slashes to append to the toolchain object directory.
- ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
+ result.value().append(&source_dir.value()[2],
+ source_dir.value().size() - 2);
}
- // (Put system-absolute stuff in the root obj directory.)
-
- return SourceDir(SourceDir::SWAP_IN, &ret);
+ return result;
}
SourceDir GetGenDirForSourceDir(const Settings* settings,
const SourceDir& source_dir) {
- SourceDir toolchain = GetToolchainGenDir(settings);
+ return GetGenDirForSourceDirAsOutputFile(settings, source_dir).AsSourceDir(
+ settings->build_settings());
+}
- std::string ret;
- toolchain.SwapValue(&ret);
+OutputFile GetGenDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir) {
+ OutputFile result = GetToolchainGenDirAsOutputFile(settings);
if (source_dir.is_source_absolute()) {
// The source dir should be source-absolute, so we trim off the two leading
// slashes to append to the toolchain object directory.
DCHECK(source_dir.is_source_absolute());
- ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
+ result.value().append(&source_dir.value()[2],
+ source_dir.value().size() - 2);
}
- // (Put system-absolute stuff in the root gen directory.)
-
- return SourceDir(SourceDir::SWAP_IN, &ret);
+ return result;
}
SourceDir GetTargetOutputDir(const Target* target) {
- return GetOutputDirForSourceDir(target->settings(), target->label().dir());
+ return GetOutputDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir()).AsSourceDir(
+ target->settings()->build_settings());
+}
+
+OutputFile GetTargetOutputDirAsOutputFile(const Target* target) {
+ return GetOutputDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir());
}
SourceDir GetTargetGenDir(const Target* target) {
- return GetGenDirForSourceDir(target->settings(), target->label().dir());
+ return GetTargetGenDirAsOutputFile(target).AsSourceDir(
+ target->settings()->build_settings());
+}
+
+OutputFile GetTargetGenDirAsOutputFile(const Target* target) {
+ return GetGenDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir());
}
SourceDir GetCurrentOutputDir(const Scope* scope) {
- return GetOutputDirForSourceDir(scope->settings(), scope->GetSourceDir());
+ return GetOutputDirForSourceDirAsOutputFile(
+ scope->settings(), scope->GetSourceDir()).AsSourceDir(
+ scope->settings()->build_settings());
}
SourceDir GetCurrentGenDir(const Scope* scope) {
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index 71e7057..20773cc 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -16,21 +16,6 @@ class Err;
class Location;
class Value;
-enum SourceFileType {
- SOURCE_UNKNOWN,
- SOURCE_ASM,
- SOURCE_C,
- SOURCE_CC,
- SOURCE_H,
- SOURCE_M,
- SOURCE_MM,
- SOURCE_S,
- SOURCE_RC,
- SOURCE_O, // Object files can be inputs, too. Also counts .obj.
-};
-
-SourceFileType GetSourceFileType(const SourceFile& file);
-
// Returns the extension, not including the dot, for the given file type on the
// given system.
//
@@ -102,13 +87,13 @@ base::StringPiece FindLastDirComponent(const SourceDir& dir);
// it is designed for a sanity check to keep people from writing output files
// to the source directory accidentally.
//
-// The originating value will be blamed in the error.
+// The origin will be blamed in the error.
//
// If the file isn't in the dir, returns false and sets the error. Otherwise
// returns true and leaves the error untouched.
bool EnsureStringIsInOutputDir(const SourceDir& dir,
const std::string& str,
- const Value& originating,
+ const ParseNode* origin,
Err* err);
// ----------------------------------------------------------------------------
@@ -178,15 +163,28 @@ std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default);
SourceDir GetToolchainOutputDir(const Settings* settings);
SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
const Label& label, bool is_default);
+
SourceDir GetToolchainGenDir(const Settings* settings);
+OutputFile GetToolchainGenDirAsOutputFile(const Settings* settings);
SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
- const Label& toolchain_label, bool is_default);
+ const Label& toolchain_label,
+ bool is_default);
+
SourceDir GetOutputDirForSourceDir(const Settings* settings,
const SourceDir& source_dir);
+OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir);
+
SourceDir GetGenDirForSourceDir(const Settings* settings,
- const SourceDir& source_dir);
+ const SourceDir& source_dir);
+OutputFile GetGenDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir);
+
SourceDir GetTargetOutputDir(const Target* target);
+OutputFile GetTargetOutputDirAsOutputFile(const Target* target);
SourceDir GetTargetGenDir(const Target* target);
+OutputFile GetTargetGenDirAsOutputFile(const Target* target);
+
SourceDir GetCurrentOutputDir(const Scope* scope);
SourceDir GetCurrentGenDir(const Scope* scope);
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index 973eac5..ba6b9bd 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -7,6 +7,7 @@
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/target.h"
TEST(FilesystemUtils, FileExtensionOffset) {
EXPECT_EQ(std::string::npos, FindExtensionOffset(""));
@@ -95,25 +96,25 @@ TEST(FilesystemUtils, EnsureStringIsInOutputDir) {
// Some outside.
Err err;
- EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//foo", Value(), &err));
+ EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//foo", NULL, &err));
EXPECT_TRUE(err.has_error());
err = Err();
- EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//out/Debugit", Value(),
+ EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//out/Debugit", NULL,
&err));
EXPECT_TRUE(err.has_error());
// Some inside.
err = Err();
- EXPECT_TRUE(EnsureStringIsInOutputDir(output_dir, "//out/Debug/", Value(),
+ EXPECT_TRUE(EnsureStringIsInOutputDir(output_dir, "//out/Debug/", NULL,
&err));
EXPECT_FALSE(err.has_error());
- EXPECT_TRUE(EnsureStringIsInOutputDir(output_dir, "//out/Debug/foo", Value(),
+ EXPECT_TRUE(EnsureStringIsInOutputDir(output_dir, "//out/Debug/foo", NULL,
&err));
EXPECT_FALSE(err.has_error());
// Pattern but no template expansions are allowed.
EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "{{source_gen_dir}}",
- Value(), &err));
+ NULL, &err));
EXPECT_TRUE(err.has_error());
}
@@ -357,17 +358,49 @@ TEST(FilesystemUtils, GetToolchainDirs) {
BuildSettings build_settings;
build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+ // The default toolchain.
Settings default_settings(&build_settings, "");
+ Label default_toolchain_label(SourceDir("//toolchain/"), "default");
+ default_settings.set_toolchain_label(default_toolchain_label);
+ default_settings.set_default_toolchain_label(default_toolchain_label);
+
+ // Default toolchain out dir.
EXPECT_EQ("//out/Debug/",
GetToolchainOutputDir(&default_settings).value());
+ EXPECT_EQ("//out/Debug/",
+ GetToolchainOutputDir(&build_settings, default_toolchain_label,
+ true).value());
+
+ // Default toolchain gen dir.
EXPECT_EQ("//out/Debug/gen/",
GetToolchainGenDir(&default_settings).value());
+ EXPECT_EQ("gen/",
+ GetToolchainGenDirAsOutputFile(&default_settings).value());
+ EXPECT_EQ("//out/Debug/gen/",
+ GetToolchainGenDir(&build_settings, default_toolchain_label,
+ true).value());
+ // Check a secondary toolchain.
Settings other_settings(&build_settings, "two/");
+ Label other_toolchain_label(SourceDir("//toolchain/"), "two");
+ default_settings.set_toolchain_label(other_toolchain_label);
+ default_settings.set_default_toolchain_label(default_toolchain_label);
+
+ // Secondary toolchain out dir.
EXPECT_EQ("//out/Debug/two/",
GetToolchainOutputDir(&other_settings).value());
+ EXPECT_EQ("//out/Debug/two/",
+ GetToolchainOutputDir(&build_settings, other_toolchain_label,
+ false).value());
+
+ // Secondary toolchain gen dir.
EXPECT_EQ("//out/Debug/two/gen/",
GetToolchainGenDir(&other_settings).value());
+ EXPECT_EQ("two/gen/",
+ GetToolchainGenDirAsOutputFile(&other_settings).value());
+ EXPECT_EQ("//out/Debug/two/gen/",
+ GetToolchainGenDir(&build_settings, other_toolchain_label,
+ false).value());
}
TEST(FilesystemUtils, GetOutDirForSourceDir) {
@@ -377,19 +410,34 @@ TEST(FilesystemUtils, GetOutDirForSourceDir) {
// Test the default toolchain.
Settings default_settings(&build_settings, "");
EXPECT_EQ("//out/Debug/obj/",
- GetOutputDirForSourceDir(&default_settings,
- SourceDir("//")).value());
+ GetOutputDirForSourceDir(
+ &default_settings, SourceDir("//")).value());
+ EXPECT_EQ("obj/",
+ GetOutputDirForSourceDirAsOutputFile(
+ &default_settings, SourceDir("//")).value());
+
EXPECT_EQ("//out/Debug/obj/foo/bar/",
- GetOutputDirForSourceDir(&default_settings,
- SourceDir("//foo/bar/")).value());
+ GetOutputDirForSourceDir(
+ &default_settings, SourceDir("//foo/bar/")).value());
+ EXPECT_EQ("obj/foo/bar/",
+ GetOutputDirForSourceDirAsOutputFile(
+ &default_settings, SourceDir("//foo/bar/")).value());
// Secondary toolchain.
Settings other_settings(&build_settings, "two/");
EXPECT_EQ("//out/Debug/two/obj/",
- GetOutputDirForSourceDir(&other_settings, SourceDir("//")).value());
+ GetOutputDirForSourceDir(
+ &other_settings, SourceDir("//")).value());
+ EXPECT_EQ("two/obj/",
+ GetOutputDirForSourceDirAsOutputFile(
+ &other_settings, SourceDir("//")).value());
+
EXPECT_EQ("//out/Debug/two/obj/foo/bar/",
GetOutputDirForSourceDir(&other_settings,
SourceDir("//foo/bar/")).value());
+ EXPECT_EQ("two/obj/foo/bar/",
+ GetOutputDirForSourceDirAsOutputFile(
+ &other_settings, SourceDir("//foo/bar/")).value());
}
TEST(FilesystemUtils, GetGenDirForSourceDir) {
@@ -399,18 +447,46 @@ TEST(FilesystemUtils, GetGenDirForSourceDir) {
// Test the default toolchain.
Settings default_settings(&build_settings, "");
EXPECT_EQ("//out/Debug/gen/",
- GetGenDirForSourceDir(&default_settings, SourceDir("//")).value());
+ GetGenDirForSourceDir(
+ &default_settings, SourceDir("//")).value());
+ EXPECT_EQ("gen/",
+ GetGenDirForSourceDirAsOutputFile(
+ &default_settings, SourceDir("//")).value());
+
EXPECT_EQ("//out/Debug/gen/foo/bar/",
- GetGenDirForSourceDir(&default_settings,
- SourceDir("//foo/bar/")).value());
+ GetGenDirForSourceDir(
+ &default_settings, SourceDir("//foo/bar/")).value());
+ EXPECT_EQ("gen/foo/bar/",
+ GetGenDirForSourceDirAsOutputFile(
+ &default_settings, SourceDir("//foo/bar/")).value());
// Secondary toolchain.
Settings other_settings(&build_settings, "two/");
EXPECT_EQ("//out/Debug/two/gen/",
- GetGenDirForSourceDir(&other_settings, SourceDir("//")).value());
+ GetGenDirForSourceDir(
+ &other_settings, SourceDir("//")).value());
+ EXPECT_EQ("two/gen/",
+ GetGenDirForSourceDirAsOutputFile(
+ &other_settings, SourceDir("//")).value());
+
EXPECT_EQ("//out/Debug/two/gen/foo/bar/",
- GetGenDirForSourceDir(&other_settings,
- SourceDir("//foo/bar/")).value());
+ GetGenDirForSourceDir(
+ &other_settings, SourceDir("//foo/bar/")).value());
+ EXPECT_EQ("two/gen/foo/bar/",
+ GetGenDirForSourceDirAsOutputFile(
+ &other_settings, SourceDir("//foo/bar/")).value());
+}
+
+TEST(FilesystemUtils, GetTargetDirs) {
+ BuildSettings build_settings;
+ build_settings.SetBuildDir(SourceDir("//out/Debug/"));
+ Settings settings(&build_settings, "");
+
+ Target a(&settings, Label(SourceDir("//foo/bar/"), "baz"));
+ EXPECT_EQ("//out/Debug/obj/foo/bar/", GetTargetOutputDir(&a).value());
+ EXPECT_EQ("obj/foo/bar/", GetTargetOutputDirAsOutputFile(&a).value());
+ EXPECT_EQ("//out/Debug/gen/foo/bar/", GetTargetGenDir(&a).value());
+ EXPECT_EQ("gen/foo/bar/", GetTargetGenDirAsOutputFile(&a).value());
}
// Tests handling of output dirs when build dir is the same as the root.
@@ -421,8 +497,13 @@ TEST(FilesystemUtils, GetDirForEmptyBuildDir) {
EXPECT_EQ("//", GetToolchainOutputDir(&settings).value());
EXPECT_EQ("//gen/", GetToolchainGenDir(&settings).value());
+ EXPECT_EQ("gen/", GetToolchainGenDirAsOutputFile(&settings).value());
EXPECT_EQ("//obj/",
GetOutputDirForSourceDir(&settings, SourceDir("//")).value());
- EXPECT_EQ("//gen/",
- GetGenDirForSourceDir(&settings, SourceDir("//")).value());
+ EXPECT_EQ("obj/",
+ GetOutputDirForSourceDirAsOutputFile(
+ &settings, SourceDir("//")).value());
+ EXPECT_EQ("gen/",
+ GetGenDirForSourceDirAsOutputFile(
+ &settings, SourceDir("//")).value());
}
diff --git a/tools/gn/function_get_target_outputs.cc b/tools/gn/function_get_target_outputs.cc
index c8fedb9..5fb3b51 100644
--- a/tools/gn/function_get_target_outputs.cc
+++ b/tools/gn/function_get_target_outputs.cc
@@ -4,7 +4,6 @@
#include "tools/gn/build_settings.h"
#include "tools/gn/functions.h"
-#include "tools/gn/ninja_helper.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/settings.h"
#include "tools/gn/substitution_writer.h"
@@ -13,56 +12,6 @@
namespace functions {
-namespace {
-
-void GetOutputsForTarget(const Settings* settings,
- const Target* target,
- std::vector<SourceFile>* ret) {
- switch (target->output_type()) {
- case Target::ACTION: {
- // Actions just use the output list with no substitution.
- std::vector<SourceFile> sources;
- sources.push_back(SourceFile());
- SubstitutionWriter::GetListAsSourceFiles(
- settings, target->action_values().outputs(), ret);
- break;
- }
-
- case Target::COPY_FILES:
- case Target::ACTION_FOREACH:
- SubstitutionWriter::ApplyListToSources(
- settings, target->action_values().outputs(), target->sources(), ret);
- break;
-
- case Target::EXECUTABLE:
- case Target::SHARED_LIBRARY:
- case Target::STATIC_LIBRARY:
- // Return the resulting binary file. Currently, fall through to the
- // Ninja helper below which will compute the main output name.
- //
- // TODO(brettw) some targets have secondary files which should go into
- // the list after the main (like shared libraries on Windows have an
- // import library).
- case Target::GROUP:
- case Target::SOURCE_SET: {
- // These return the stamp file, which is computed by the NinjaHelper.
- NinjaHelper helper(settings->build_settings());
- OutputFile output_file = helper.GetTargetOutputFile(target);
-
- // The output file is relative to the build dir.
- ret->push_back(SourceFile(
- settings->build_settings()->build_dir().value() +
- output_file.value()));
- break;
- }
-
- default:
- NOTREACHED();
- }
-}
-
-} // namespace
-
const char kGetTargetOutputs[] = "get_target_outputs";
const char kGetTargetOutputs_HelpShort[] =
"get_target_outputs: [file list] Get the list of outputs from a target.";
@@ -77,6 +26,11 @@ const char kGetTargetOutputs_Help[] =
" there isn't a defined execution order, and it obviously can't\n"
" reference targets that are defined after the function call).\n"
"\n"
+ " Only copy and action targets are supported. The outputs from binary\n"
+ " targets will depend on the toolchain definition which won't\n"
+ " necessarily have been loaded by the time a given line of code has run,\n"
+ " and source sets and groups have no useful output file.\n"
+ "\n"
"Return value\n"
"\n"
" The names in the resulting list will be absolute file paths (normally\n"
@@ -162,9 +116,26 @@ Value RunGetTargetOutputs(Scope* scope,
return Value();
}
+ // Compute the output list.
std::vector<SourceFile> files;
- GetOutputsForTarget(scope->settings(), target, &files);
+ if (target->output_type() == Target::ACTION) {
+ // Actions just use the output list with no substitution.
+ SubstitutionWriter::GetListAsSourceFiles(
+ target->action_values().outputs(), &files);
+ } else if (target->output_type() == Target::COPY_FILES ||
+ target->output_type() == Target::ACTION_FOREACH) {
+ // Copy and foreach appllies the outputs to the sources.
+ SubstitutionWriter::ApplyListToSources(
+ target->settings(), target->action_values().outputs(),
+ target->sources(), &files);
+ } else {
+ // Other types of targets are not supported.
+ *err = Err(args[0], "Target is not an action, action_foreach, or copy.",
+ "Only these target types are supported by get_target_outputs.");
+ return Value();
+ }
+ // Convert to Values.
Value ret(function, Value::LIST);
ret.list_value().reserve(files.size());
for (size_t i = 0; i < files.size(); i++)
diff --git a/tools/gn/function_get_target_outputs_unittest.cc b/tools/gn/function_get_target_outputs_unittest.cc
index a38a17e..ab89343a 100644
--- a/tools/gn/function_get_target_outputs_unittest.cc
+++ b/tools/gn/function_get_target_outputs_unittest.cc
@@ -58,30 +58,6 @@ class GetTargetOutputsTest : public testing::Test {
} // namespace
-TEST_F(GetTargetOutputsTest, Executable) {
- Target* exe = new Target(setup_.settings(), GetLabel("//foo/", "bar"));
- exe->set_output_type(Target::EXECUTABLE);
- items_.push_back(new scoped_ptr<Item>(exe));
-
- Err err;
- Value result = GetTargetOutputs("//foo:bar", &err);
- ASSERT_FALSE(err.has_error());
- // The TestWithScope declares that the build is Linux, so we'll have no
- // extension for the binaries.
- AssertSingleStringEquals(result, "//out/Debug/bar");
-}
-
-TEST_F(GetTargetOutputsTest, SourceSet) {
- Target* source_set = new Target(setup_.settings(), GetLabel("//foo/", "bar"));
- source_set->set_output_type(Target::SOURCE_SET);
- items_.push_back(new scoped_ptr<Item>(source_set));
-
- Err err;
- Value result = GetTargetOutputs("//foo:bar", &err);
- ASSERT_FALSE(err.has_error());
- AssertSingleStringEquals(result, "//out/Debug/obj/foo/bar.stamp");
-}
-
TEST_F(GetTargetOutputsTest, Copy) {
Target* action = new Target(setup_.settings(), GetLabel("//foo/", "bar"));
action->set_output_type(Target::COPY_FILES);
diff --git a/tools/gn/function_rebase_path.cc b/tools/gn/function_rebase_path.cc
index 9d3a4e3..dd5e903 100644
--- a/tools/gn/function_rebase_path.cc
+++ b/tools/gn/function_rebase_path.cc
@@ -253,8 +253,6 @@ Value RunRebasePath(Scope* scope,
// Path conversion.
if (inputs.type() == Value::STRING) {
- if (inputs.string_value() == "//foo")
- printf("foo\n");
return ConvertOnePath(scope, function, inputs,
from_dir, to_dir, convert_to_system_absolute, err);
diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc
index c27163b..4f99778 100644
--- a/tools/gn/function_toolchain.cc
+++ b/tools/gn/function_toolchain.cc
@@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <algorithm>
+
#include "tools/gn/err.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
+#include "tools/gn/tool.h"
#include "tools/gn/toolchain.h"
#include "tools/gn/value_extractors.h"
#include "tools/gn/variables.h"
@@ -20,19 +23,171 @@ namespace {
// the toolchain property on a scope.
const int kToolchainPropertyKey = 0;
+bool ReadBool(Scope* scope,
+ const char* var,
+ Tool* tool,
+ void (Tool::*set)(bool),
+ Err* err) {
+ const Value* v = scope->GetValue(var, true);
+ if (!v)
+ return true; // Not present is fine.
+ if (!v->VerifyTypeIs(Value::BOOLEAN, err))
+ return false;
+
+ (tool->*set)(v->boolean_value());
+ return true;
+}
+
// Reads the given string from the scope (if present) and puts the result into
// dest. If the value is not a string, sets the error and returns false.
-bool ReadString(Scope& scope, const char* var, std::string* dest, Err* err) {
- const Value* v = scope.GetValue(var, true);
+bool ReadString(Scope* scope,
+ const char* var,
+ Tool* tool,
+ void (Tool::*set)(const std::string&),
+ Err* err) {
+ const Value* v = scope->GetValue(var, true);
if (!v)
return true; // Not present is fine.
-
if (!v->VerifyTypeIs(Value::STRING, err))
return false;
- *dest = v->string_value();
+
+ (tool->*set)(v->string_value());
return true;
}
+// Calls the given validate function on each type in the list. On failure,
+// sets the error, blame the value, and return false.
+bool ValidateSubstitutionList(const std::vector<SubstitutionType>& list,
+ bool (*validate)(SubstitutionType),
+ const Value* origin,
+ Err* err) {
+ for (size_t i = 0; i < list.size(); i++) {
+ SubstitutionType cur_type = list[i];
+ if (!validate(cur_type)) {
+ *err = Err(*origin, "Pattern not valid here.",
+ "You used the pattern " + std::string(kSubstitutionNames[cur_type]) +
+ " which is not valid\nfor this variable.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ReadPattern(Scope* scope,
+ const char* name,
+ bool (*validate)(SubstitutionType),
+ Tool* tool,
+ void (Tool::*set)(const SubstitutionPattern&),
+ Err* err) {
+ const Value* value = scope->GetValue(name, true);
+ if (!value)
+ return true; // Not present is fine.
+ if (!value->VerifyTypeIs(Value::STRING, err))
+ return false;
+
+ SubstitutionPattern pattern;
+ if (!pattern.Parse(*value, err))
+ return false;
+ if (!ValidateSubstitutionList(pattern.required_types(), validate, value, err))
+ return false;
+
+ (tool->*set)(pattern);
+ return true;
+}
+
+bool ReadOutputExtension(Scope* scope, Tool* tool, Err* err) {
+ const Value* value = scope->GetValue("default_output_extension", true);
+ if (!value)
+ return true; // Not present is fine.
+ if (!value->VerifyTypeIs(Value::STRING, err))
+ return false;
+
+ if (value->string_value().empty())
+ return true; // Accept empty string.
+
+ if (value->string_value()[0] != '.') {
+ *err = Err(*value, "default_output_extension must begin with a '.'");
+ return false;
+ }
+
+ tool->set_default_output_extension(value->string_value());
+ return true;
+}
+
+bool ReadDepsFormat(Scope* scope, Tool* tool, Err* err) {
+ const Value* value = scope->GetValue("depsformat", true);
+ if (!value)
+ return true; // Not present is fine.
+ if (!value->VerifyTypeIs(Value::STRING, err))
+ return false;
+
+ if (value->string_value() == "gcc") {
+ tool->set_depsformat(Tool::DEPS_GCC);
+ } else if (value->string_value() == "msvc") {
+ tool->set_depsformat(Tool::DEPS_MSVC);
+ } else {
+ *err = Err(*value, "Deps format must be \"gcc\" or \"msvc\".");
+ return false;
+ }
+ return true;
+}
+
+bool ReadOutputs(Scope* scope,
+ const FunctionCallNode* tool_function,
+ bool (*validate)(SubstitutionType),
+ Tool* tool,
+ Err* err) {
+ const Value* value = scope->GetValue("outputs", true);
+ if (!value) {
+ *err = Err(tool_function, "\"outputs\" must be specified for this tool.");
+ return false;
+ }
+
+ SubstitutionList list;
+ if (!list.Parse(*value, err))
+ return false;
+
+ // Validate the right kinds of patterns are used.
+ if (!ValidateSubstitutionList(list.required_types(), validate, value, err))
+ return false;
+
+ // There should always be at least one output.
+ if (list.list().empty()) {
+ *err = Err(*value, "Outputs list is empty.", "I need some outputs.");
+ return false;
+ }
+
+ tool->set_outputs(list);
+ return true;
+}
+
+bool IsCompilerTool(Toolchain::ToolType type) {
+ return type == Toolchain::TYPE_CC ||
+ type == Toolchain::TYPE_CXX ||
+ type == Toolchain::TYPE_OBJC ||
+ type == Toolchain::TYPE_OBJCXX ||
+ type == Toolchain::TYPE_RC ||
+ type == Toolchain::TYPE_ASM;
+}
+
+bool IsLinkerTool(Toolchain::ToolType type) {
+ return type == Toolchain::TYPE_ALINK ||
+ type == Toolchain::TYPE_SOLINK ||
+ type == Toolchain::TYPE_LINK;
+}
+
+bool IsPatternInOutputList(const SubstitutionList& output_list,
+ const SubstitutionPattern& pattern) {
+ for (size_t output_i = 0; output_i < output_list.list().size(); output_i++) {
+ const SubstitutionPattern& cur = output_list.list()[output_i];
+ if (pattern.ranges().size() == cur.ranges().size() &&
+ std::equal(pattern.ranges().begin(), pattern.ranges().end(),
+ cur.ranges().begin()))
+ return true;
+ }
+ return false;
+}
+
} // namespace
// toolchain -------------------------------------------------------------------
@@ -132,11 +287,11 @@ Value RunToolchain(Scope* scope,
return Value();
}
-
if (!block_scope.CheckForUnusedVars(err))
return Value();
// Save this toolchain.
+ toolchain->ToolchainSetupComplete();
Scope::ItemVector* collector = scope->GetItemCollector();
if (!collector) {
*err = Err(function, "Can't define a toolchain in this context.");
@@ -154,81 +309,342 @@ const char kTool_HelpShort[] =
const char kTool_Help[] =
"tool: Specify arguments to a toolchain tool.\n"
"\n"
- " tool(<command type>) { <command flags> }\n"
- "\n"
- " Used inside a toolchain definition to define a command to run for a\n"
- " given file type. See also \"gn help toolchain\".\n"
- "\n"
- "Command types\n"
- "\n"
- " The following values may be passed to the tool() function for the type\n"
- " of the command:\n"
- "\n"
- " \"cc\", \"cxx\", \"objc\", \"objcxx\", \"asm\", \"alink\", \"solink\",\n"
- " \"link\", \"stamp\", \"copy\"\n"
+ "Usage:\n"
"\n"
- "Tool-specific notes\n"
+ " tool(<tool type>) {\n"
+ " <tool variables...>\n"
+ " }\n"
"\n"
- " copy\n"
- " The copy command should be a native OS command since it does not\n"
- " implement toolchain dependencies (which would enable a copy tool to\n"
- " be compiled by a previous step).\n"
+ "Tool types\n"
"\n"
- " It is legal for the copy to not update the timestamp of the output\n"
- " file (as long as it's greater than or equal to the input file). This\n"
- " allows the copy command to be implemented as a hard link which can\n"
- " be more efficient.\n"
+ " Compiler tools:\n"
+ " \"cc\": C compiler\n"
+ " \"cxx\": C++ compiler\n"
+ " \"objc\": Objective C compiler\n"
+ " \"objcxx\": Objective C++ compiler\n"
+ " \"rc\": Resource compiler (Windows .rc files)\n"
+ " \"asm\": Assembler\n"
"\n"
- "Command flags\n"
+ " Linker tools:\n"
+ " \"alink\": Linker for static libraries (archives)\n"
+ " \"solink\": Linker for shared libraries\n"
+ " \"link\": Linker for executables\n"
"\n"
- " These variables may be specified in the { } block after the tool call.\n"
- " They are passed directly to Ninja. See the ninja documentation for how\n"
- " they work. Don't forget to backslash-escape $ required by Ninja to\n"
- " prevent GN from doing variable expansion.\n"
+ " Other tools:\n"
+ " \"stamp\": Tool for creating stamp files\n"
+ " \"copy\": Tool to copy files.\n"
"\n"
- " command, depfile, depsformat, description, pool, restat, rspfile,\n"
- " rspfile_content\n"
+ "Tool variables\n"
"\n"
- " (Note that GN uses \"depsformat\" for Ninja's \"deps\" variable to\n"
- " avoid confusion with dependency lists.)\n"
+ " command [string with substitutions]\n"
+ " Valid for: all tools (required)\n"
"\n"
- " Additionally, lib_prefix and lib_dir_prefix may be used for the link\n"
- " tools. These strings will be prepended to the libraries and library\n"
- " search directories, respectively, because linkers differ on how to\n"
- " specify them.\n"
+ " The command to run.\n"
"\n"
- " Note: On Mac libraries with names ending in \".framework\" will be\n"
- " added to the link like with a \"-framework\" switch and the lib prefix\n"
- " will be ignored.\n"
+ " default_output_extension [string]\n"
+ " Valid for: linker tools\n"
+ "\n"
+ " Extension for the main output of a linkable tool. It includes\n"
+ " the leading dot. This will be the default value for the\n"
+ " {{output_extension}} expansion (discussed below) but will be\n"
+ " overridden by by the \"output extension\" variable in a target,\n"
+ " if one is specified. Empty string means no extension.\n"
"\n"
- "Ninja variables available to tool invocations\n"
+ " GN doesn't actually do anything with this extension other than\n"
+ " pass it along, potentially with target-specific overrides. One\n"
+ " would typically use the {{output_extension}} value in the\n"
+ " \"outputs\" to read this value.\n"
"\n"
- " When writing tool commands, you use the various built-in Ninja\n"
- " variables like \"$in\" and \"$out\" (note that the $ must be escaped\n"
- " for it to be passed to Ninja, so write \"\\$in\" in the command\n"
- " string).\n"
+ " Example: default_output_extension = \".exe\"\n"
+ "\n"
+ " depfile [string]\n"
+ " Valid for: compiler tools (optional)\n"
+ "\n"
+ " If the tool can write \".d\" files, this specifies the name of\n"
+ " the resulting file. These files are used to list header file\n"
+ " dependencies (or other implicit input dependencies) that are\n"
+ " discovered at build time. See also \"depsformat\".\n"
+ "\n"
+ " Example: depfile = \"{{output}}.d\"\n"
+ "\n"
+ " depsformat [string]\n"
+ " Valid for: compiler tools (when depfile is specified)\n"
+ "\n"
+ " Format for the deps outputs. This is either \"gcc\" or \"msvc\".\n"
+ " See the ninja documentation for \"deps\" for more information.\n"
+ "\n"
+ " Example: depsformat = \"gcc\"\n"
+ "\n"
+ " description [string with substitutions, optional]\n"
+ " Valid for: all tools\n"
+ "\n"
+ " What to print when the command is run.\n"
+ "\n"
+ " Example: description = \"Compiling {{source}}\"\n"
+ "\n"
+ " lib_switch [string, optional, link tools only]\n"
+ " lib_dir_switch [string, optional, link tools only]\n"
+ " Valid for: Linker tools except \"alink\"\n"
"\n"
- " GN defines the following variables for binary targets to access the\n"
- " various computed information needed for compiling:\n"
- "\n"
- " - Compiler flags: \"cflags\", \"cflags_c\", \"cflags_cc\",\n"
- " \"cflags_objc\", \"cflags_objcc\"\n"
- "\n"
- " - Linker flags: \"ldflags\", \"libs\"\n"
- "\n"
- " GN sets these other variables with target information that can be\n"
- " used for computing names for supplimetary files:\n"
- "\n"
- " - \"target_name\": The name of the current target with no\n"
- " path information. For example \"mylib\".\n"
- "\n"
- " - \"target_out_dir\": The value of \"target_out_dir\" from the BUILD\n"
- " file for this target (see \"gn help target_out_dir\"), relative\n"
- " to the root build directory with no trailing slash.\n"
- "\n"
- " - \"root_out_dir\": The value of \"root_out_dir\" from the BUILD\n"
- " file for this target (see \"gn help root_out_dir\"), relative\n"
- " to the root build directory with no trailing slash.\n"
+ " These strings will be prepended to the libraries and library\n"
+ " search directories, respectively, because linkers differ on how\n"
+ " specify them. If you specified:\n"
+ " lib_switch = \"-l\"\n"
+ " lib_dir_switch = \"-L\"\n"
+ " then the \"{{libs}}\" expansion for [ \"freetype\", \"expat\"]\n"
+ " would be \"-lfreetype -lexpat\".\n"
+ "\n"
+ " outputs [list of strings with substitutions]\n"
+ " Valid for: Linker and compiler tools (required)\n"
+ "\n"
+ " An array of names for the output files the tool produces. These\n"
+ " are relative to the build output directory. There must always be\n"
+ " at least one output file. There can be more than one output (a\n"
+ " linker might produce a library and an import library, for\n"
+ " example).\n"
+ "\n"
+ " This array just declares to GN what files the tool will\n"
+ " produce. It is your responsibility to specify the tool command\n"
+ " that actually produces these files.\n"
+ "\n"
+ " If you specify more than one output for shared library links,\n"
+ " you should consider setting link_output and depend_output.\n"
+ " Otherwise, the first entry in the outputs list should always be\n"
+ " the main output which will be linked to.\n"
+ "\n"
+ " Example for a compiler tool that produces .obj files:\n"
+ " outputs = [\n"
+ " \"{{source_out_dir}}/{{source_name_part}}.obj\"\n"
+ " ]\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"
+ " outputs = [\n"
+ " \"{{root_out_dir}}/{{target_output_name}}"
+ "{{output_extension}}\",\n"
+ " \"{{root_out_dir}}/{{target_output_name}}.lib\",\n"
+ " ]\n"
+ "\n"
+ " link_output [string with substitutions]\n"
+ " depend_output [string with substitutions]\n"
+ " Valid for: \"solink\" only (optional)\n"
+ "\n"
+ " These two files specify whch of the outputs from the solink\n"
+ " tool should be used for linking and dependency tracking. These\n"
+ " should match entries in the \"outputs\". If unspecified, the\n"
+ " first item in the \"outputs\" array will be used for both. See\n"
+ " \"Separate linking and dependencies for shared libraries\"\n"
+ " below for more.\n"
+ "\n"
+ " On Windows, where the tools produce a .dll shared library and\n"
+ " a .lib import library, you will want both of these to be the\n"
+ " import library. On Linux, if you're not doing the separate\n"
+ " linking/dependency optimization, both of these should be the\n"
+ " .so output.\n"
+ "\n"
+ " output_prefix [string]\n"
+ " Valid for: Linker tools (optional)\n"
+ "\n"
+ " Prefix to use for the output name. Defaults to empty. This\n"
+ " prefix will be prepended to the name of the target (or the\n"
+ " output_name if one is manually specified for it) if the prefix\n"
+ " is not already there. The result will show up in the\n"
+ " {{output_name}} substitution pattern.\n"
+ "\n"
+ " This is typically used to prepend \"lib\" to libraries on\n"
+ " Posix systems:\n"
+ " output_prefix = \"lib\"\n"
+ "\n"
+ // TODO(brettw) document "pool" when it works.
+ //" pool [string, optional]\n"
+ //"\n"
+ " restat [boolean]\n"
+ " Valid for: all tools (optional, defaults to false)\n"
+ "\n"
+ " Requests that Ninja check the file timestamp after this tool has\n"
+ " run to determine if anything changed. Set this if your tool has\n"
+ " the ability to skip writing output if the output file has not\n"
+ " changed.\n"
+ "\n"
+ " Normally, Ninja will assume that when a tool runs the output\n"
+ " be new and downstream dependents must be rebuild. When this is\n"
+ " set to trye, Ninja can skip rebuilding downstream dependents for\n"
+ " input changes that don't actually affect the output.\n"
+ "\n"
+ " Example:\n"
+ " restat = true\n"
+ "\n"
+ " rspfile [string with substitutions]\n"
+ " Valid for: all tools (optional)\n"
+ "\n"
+ " Name of the response file. If empty, no response file will be\n"
+ " used. See \"rspfile_content\".\n"
+ "\n"
+ " rspfile_content [string with substitutions]\n"
+ " Valid for: all tools (required when \"rspfile\" is specified)\n"
+ "\n"
+ " The contents to be written to the response file. This may\n"
+ " include all or part of the command to send to the tool which\n"
+ " allows you to get around OS command-line length limits.\n"
+ "\n"
+ " This example adds the inputs and libraries to a response file,\n"
+ " but passes the linker flags directly on the command line:\n"
+ " tool(\"link\") {\n"
+ " command = \"link -o {{output}} {{ldflags}} @{{output}}.rsp\"\n"
+ " rspfile = \"{{output}}.rsp\"\n"
+ " rspfile_content = \"{{inputs}} {{solibs}} {{libs}}\"\n"
+ " }\n"
+ "\n"
+ "Expansions for tool variables"
+ "\n"
+ " All paths are relative to the root build directory, which is the\n"
+ " current directory for running all tools. These expansions are\n"
+ " available to all tools:\n"
+ "\n"
+ " {{label}}\n"
+ " The label of the current target. This is typically used in the\n"
+ " \"description\" field for link tools. The toolchain will be\n"
+ " omitted from the label for targets in the default toolchain, and\n"
+ " will be included for targets in other toolchains.\n"
+ "\n"
+ " {{output}}\n"
+ " The relative path and name of the output)((s) of the current\n"
+ " build step. If there is more than one output, this will expand\n"
+ " to a list of all of them.\n"
+ " Example: \"out/base/my_file.o\"\n"
+ "\n"
+ " {{target_gen_dir}}\n"
+ " {{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"
+ " Example: \"out/base/test\"\n"
+ "\n"
+ " {{target_output_name}}\n"
+ " The short name of the current target with no path information,\n"
+ " or the value of the \"output_name\" variable if one is specified\n"
+ " in the target. This will include the \"output_prefix\" if any.\n"
+ " Example: \"libfoo\" for the target named \"foo\" and an\n"
+ " output prefix for the linker tool of \"lib\".\n"
+ "\n"
+ " Compiler tools have the notion of a single input and a single output,\n"
+ " along with a set of compiler-specific flags. The following expansions\n"
+ " are available:\n"
+ "\n"
+ " {{cflags}}\n"
+ " {{cflags_c}}\n"
+ " {{cflags_cc}}\n"
+ " {{cflags_objc}}\n"
+ " {{cflags_objcc}}\n"
+ " {{defines}}\n"
+ " {{include_dirs}}\n"
+ " Strings correspond that to the processed flags/defines/include\n"
+ " directories specified for the target.\n"
+ " Example: \"--enable-foo --enable-bar\"\n"
+ "\n"
+ " Defines will be prefixed by \"-D\" and include directories will\n"
+ " be prefixed by \"-I\" (these work with Posix tools as well as\n"
+ " Microsoft ones).\n"
+ "\n"
+ " {{source}}\n"
+ " The relative path and name of the current input file.\n"
+ " Example: \"../../base/my_file.cc\"\n"
+ "\n"
+ " {{source_file_part}}\n"
+ " The file part of the source including the extension (with no\n"
+ " directory information).\n"
+ " Example: \"foo.cc\"\n"
+ "\n"
+ " {{source_name_part}}\n"
+ " The filename part of the source file with no directory or\n"
+ " extension.\n"
+ " Example: \"foo\"\n"
+ "\n"
+ " {{source_gen_dir}}\n"
+ " {{source_out_dir}}\n"
+ " The directory in the generated file and output directories,\n"
+ " respectively, for the current input file. If the source file\n"
+ " is in the same directory as the target is declared in, they will\n"
+ " will be the same as the \"target\" versions above.\n"
+ " Example: \"gen/base/test\"\n"
+ "\n"
+ " Linker tools have multiple inputs and (potentially) multiple outputs\n"
+ " The following expansions are available:\n"
+ "\n"
+ " {{inputs}}\n"
+ " Expands to the inputs to the link step. This will be a list of\n"
+ " object files and static libraries.\n"
+ " Example: \"obj/foo.o obj/bar.o obj/somelibrary.a\"\n"
+ "\n"
+ " {{ldflags}}\n"
+ " Expands to the processed set of ldflags and library search paths\n"
+ " specified for the target.\n"
+ " Example: \"-m64, -fPIC -pthread -L/usr/local/mylib\"\n"
+ "\n"
+ " {{libs}}\n"
+ " Expands to the list of system libraries to link to. Each will\n"
+ " be prefixed by the \"lib_prefix\".\n"
+ "\n"
+ " As a special case to support Mac, libraries with names ending in\n"
+ " \".framework\" will be added to the {{libs}} with \"-framework\"\n"
+ " preceeding it, and the lib prefix will be ignored.\n"
+ "\n"
+ " Example: \"-lfoo -lbar\"\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"
+ " tool if the target does not specify an output extension.\n"
+ " Example: \".so\"\n"
+ "\n"
+ " {{solibs}}\n"
+ " Extra libraries from shared library dependencide not specified\n"
+ " in the {{inputs}}. This is the list of link_output files from\n"
+ " shared libraries (if the solink tool specifies a \"link_output\"\n"
+ " variable separate from the \"depend_output\").\n"
+ "\n"
+ " These should generally be treated the same as libs by your tool.\n"
+ " Example: \"libfoo.so libbar.so\"\n"
+ "\n"
+ " The copy tool allows the common compiler/linker substitutions, plus\n"
+ " {{source}} which is the source of the copy. The stamp tool allows\n"
+ " only the common tool substitutions.\n"
+ "\n"
+ "Separate linking and dependencies for shared libraries\n"
+ "\n"
+ " Shared libraries are special in that not all changes to them require\n"
+ " that dependent targets be re-linked. If the shared library is changed\n"
+ " but no imports or exports are different, dependent code needn't be\n"
+ " relinked, which can speed up the build.\n"
+ "\n"
+ " If your link step can output a list of exports from a shared library\n"
+ " and writes the file only if the new one is different, the timestamp of\n"
+ " this file can be used for triggering re-links, while the actual shared\n"
+ " library would be used for linking.\n"
+ "\n"
+ " You will need to specify\n"
+ " restat = true\n"
+ " in the linker tool to make this work, so Ninja will detect if the\n"
+ " timestamp of the dependency file has changed after linking (otherwise\n"
+ " it will always assume that running a command updates the output):\n"
+ "\n"
+ " tool(\"solink\") {\n"
+ " command = \"...\"\n"
+ " outputs = [\n"
+ " \"{{root_out_dir}}/{{target_output_name}}{{output_extension}}\",\n"
+ " \"{{root_out_dir}}/{{target_output_name}}"
+ "{{output_extension}}.TOC\",\n"
+ " ]\n"
+ " link_output =\n"
+ " \"{{root_out_dir}}/{{target_output_name}}{{output_extension}}\",\n"
+ " depend_output =\n"
+ " \"{{root_out_dir}}/{{target_output_name}}"
+ "{{output_extension}}.TOC\",\n"
+ " restat = true\n"
+ " }\n"
"\n"
"Example\n"
"\n"
@@ -239,10 +655,12 @@ const char kTool_Help[] =
"\n"
" tool(\"cc\") {\n"
" command = \"gcc \\$in -o \\$out\"\n"
+ " outputs = [ \"{{source_out_dir}}/{{source_name_part}}.o\"\n"
" description = \"GCC \\$in\"\n"
" }\n"
" tool(\"cxx\") {\n"
" command = \"g++ \\$in -o \\$out\"\n"
+ " outputs = [ \"{{source_out_dir}}/{{source_name_part}}.o\"\n"
" description = \"G++ \\$in\"\n"
" }\n"
" }\n";
@@ -278,29 +696,100 @@ Value RunTool(Scope* scope,
if (err->has_error())
return Value();
- // Extract the stuff we need.
- Toolchain::Tool t;
- if (!ReadString(block_scope, "command", &t.command, err) ||
- !ReadString(block_scope, "depfile", &t.depfile, err) ||
- // TODO(brettw) delete this once we rename "deps" -> "depsformat" in
- // the toolchain definitions. This will avoid colliding with the
- // toolchain's "deps" list. For now, accept either.
- !ReadString(block_scope, "deps", &t.depsformat, err) ||
- !ReadString(block_scope, "depsformat", &t.depsformat, err) ||
- !ReadString(block_scope, "description", &t.description, err) ||
- !ReadString(block_scope, "lib_dir_prefix", &t.lib_dir_prefix, err) ||
- !ReadString(block_scope, "lib_prefix", &t.lib_prefix, err) ||
- !ReadString(block_scope, "pool", &t.pool, err) ||
- !ReadString(block_scope, "restat", &t.restat, err) ||
- !ReadString(block_scope, "rspfile", &t.rspfile, err) ||
- !ReadString(block_scope, "rspfile_content", &t.rspfile_content, err))
+ // Figure out which validator to use for the substitution pattern for this
+ // tool type. There are different validators for the "outputs" than for the
+ // rest of the strings.
+ bool (*subst_validator)(SubstitutionType) = NULL;
+ bool (*subst_output_validator)(SubstitutionType) = NULL;
+ if (IsCompilerTool(tool_type)) {
+ subst_validator = &IsValidCompilerSubstitution;
+ subst_output_validator = &IsValidCompilerOutputsSubstitution;
+ } else if (IsLinkerTool(tool_type)) {
+ subst_validator = &IsValidLinkerSubstitution;
+ subst_output_validator = &IsValidLinkerOutputsSubstitution;
+ } else if (tool_type == Toolchain::TYPE_COPY) {
+ subst_validator = &IsValidCopySubstitution;
+ subst_output_validator = &IsValidCopySubstitution;
+ } else {
+ subst_validator = &IsValidToolSubstutition;
+ subst_output_validator = &IsValidToolSubstutition;
+ }
+
+ scoped_ptr<Tool> tool(new Tool);
+
+ if (!ReadPattern(&block_scope, "command", subst_validator, tool.get(),
+ &Tool::set_command, err) ||
+ !ReadOutputExtension(&block_scope, tool.get(), err) ||
+ !ReadPattern(&block_scope, "depfile", subst_validator, tool.get(),
+ &Tool::set_depfile, err) ||
+ !ReadDepsFormat(&block_scope, tool.get(), err) ||
+ !ReadPattern(&block_scope, "description", subst_validator, tool.get(),
+ &Tool::set_description, err) ||
+ !ReadString(&block_scope, "lib_switch", tool.get(),
+ &Tool::set_lib_switch, err) ||
+ !ReadString(&block_scope, "lib_dir_switch", tool.get(),
+ &Tool::set_lib_dir_switch, err) ||
+ !ReadPattern(&block_scope, "link_output", subst_validator, tool.get(),
+ &Tool::set_link_output, err) ||
+ !ReadPattern(&block_scope, "depend_output", subst_validator, tool.get(),
+ &Tool::set_depend_output, err) ||
+ !ReadString(&block_scope, "output_prefix", tool.get(),
+ &Tool::set_output_prefix, err) ||
+ !ReadString(&block_scope, "pool", tool.get(), &Tool::set_pool, err) ||
+ !ReadBool(&block_scope, "restat", tool.get(), &Tool::set_restat, err) ||
+ !ReadPattern(&block_scope, "rspfile", subst_validator, tool.get(),
+ &Tool::set_rspfile, err) ||
+ !ReadPattern(&block_scope, "rspfile_content", subst_validator, tool.get(),
+ &Tool::set_rspfile_content, err)) {
+ return Value();
+ }
+
+ if (tool_type != Toolchain::TYPE_COPY && tool_type != Toolchain::TYPE_STAMP) {
+ // All tools except the copy and stamp tools should have outputs. The copy
+ // and stamp tool's outputs are generated internally.
+ if (!ReadOutputs(&block_scope, function, subst_output_validator,
+ tool.get(), err))
+ return Value();
+ }
+
+ // Validate that the link_output and depend_output refer to items in the
+ // outputs and aren't defined for irrelevant tool types.
+ if (!tool->link_output().empty()) {
+ if (tool_type != Toolchain::TYPE_SOLINK) {
+ *err = Err(function, "This tool specifies a link_output.",
+ "This is only valid for solink tools.");
+ return Value();
+ }
+ if (!IsPatternInOutputList(tool->outputs(), tool->link_output())) {
+ *err = Err(function, "This tool's link_output is bad.",
+ "It must match one of the outputs.");
+ return Value();
+ }
+ }
+ if (!tool->depend_output().empty()) {
+ if (tool_type != Toolchain::TYPE_SOLINK) {
+ *err = Err(function, "This tool specifies a depend_output.",
+ "This is only valid for solink tools.");
+ return Value();
+ }
+ if (!IsPatternInOutputList(tool->outputs(), tool->depend_output())) {
+ *err = Err(function, "This tool's depend_output is bad.",
+ "It must match one of the outputs.");
+ return Value();
+ }
+ }
+ if ((!tool->link_output().empty() && tool->depend_output().empty()) ||
+ (tool->link_output().empty() && !tool->depend_output().empty())) {
+ *err = Err(function, "Both link_output and depend_output should either "
+ "be specified or they should both be empty.");
return Value();
+ }
// Make sure there weren't any vars set in this tool that were unused.
if (!block_scope.CheckForUnusedVars(err))
return Value();
- toolchain->SetTool(tool_type, t);
+ toolchain->SetTool(tool_type, tool.Pass());
return Value();
}
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index 36bcbc5..322fa65 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -60,7 +60,7 @@ Value RunWriteFile(Scope* scope,
SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value());
if (!EnsureStringIsInOutputDir(
scope->settings()->build_settings()->build_dir(),
- source_file.value(), args[0], err))
+ source_file.value(), args[0].origin(), err))
return Value();
// Compute output.
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 5bf75e6..e3920cb 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -97,8 +97,8 @@
'ninja_copy_target_writer.h',
'ninja_group_target_writer.cc',
'ninja_group_target_writer.h',
- 'ninja_helper.cc',
- 'ninja_helper.h',
+ 'ninja_utils.cc',
+ 'ninja_utils.h',
'ninja_target_writer.cc',
'ninja_target_writer.h',
'ninja_toolchain_writer.cc',
@@ -107,6 +107,7 @@
'ninja_writer.h',
'operators.cc',
'operators.h',
+ 'output_file.cc',
'output_file.h',
'parse_tree.cc',
'parse_tree.h',
@@ -130,6 +131,8 @@
'source_dir.h',
'source_file.cc',
'source_file.h',
+ 'source_file_type.cc',
+ 'source_file_type.h',
'standard_out.cc',
'standard_out.h',
'string_utils.cc',
@@ -152,6 +155,8 @@
'token.h',
'tokenizer.cc',
'tokenizer.h',
+ 'tool.cc',
+ 'tool.h',
'toolchain.cc',
'toolchain.h',
'unique_vector.h',
@@ -205,8 +210,8 @@
'ninja_action_target_writer_unittest.cc',
'ninja_binary_target_writer_unittest.cc',
'ninja_copy_target_writer_unittest.cc',
- 'ninja_helper_unittest.cc',
'ninja_target_writer_unittest.cc',
+ 'ninja_toolchain_writer_unittest.cc',
'operators_unittest.cc',
'parse_tree_unittest.cc',
'parser_unittest.cc',
diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc
index 2193030..3fb6bd7 100644
--- a/tools/gn/header_checker.cc
+++ b/tools/gn/header_checker.cc
@@ -17,6 +17,7 @@
#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/source_file_type.h"
#include "tools/gn/target.h"
#include "tools/gn/trace.h"
diff --git a/tools/gn/ninja_action_target_writer.cc b/tools/gn/ninja_action_target_writer.cc
index cb5895e..6aff197 100644
--- a/tools/gn/ninja_action_target_writer.cc
+++ b/tools/gn/ninja_action_target_writer.cc
@@ -6,14 +6,14 @@
#include "base/strings/string_util.h"
#include "tools/gn/err.h"
+#include "tools/gn/settings.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/substitution_writer.h"
#include "tools/gn/target.h"
NinjaActionTargetWriter::NinjaActionTargetWriter(const Target* target,
- const Toolchain* toolchain,
std::ostream& out)
- : NinjaTargetWriter(target, toolchain, out),
+ : NinjaTargetWriter(target, out),
path_output_no_escaping_(
target->settings()->build_settings()->build_dir(),
ESCAPE_NONE) {
@@ -57,10 +57,7 @@ void NinjaActionTargetWriter::Run() {
out_ << "build";
SubstitutionWriter::GetListAsOutputFiles(
settings_, target_->action_values().outputs(), &output_files);
- for (size_t i = 0; i < output_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, output_files[i]);
- }
+ path_output_.WriteFiles(out_, output_files);
out_ << ": " << custom_rule_name << implicit_deps << std::endl;
if (target_->action_values().has_depfile()) {
@@ -71,7 +68,13 @@ void NinjaActionTargetWriter::Run() {
}
out_ << std::endl;
- WriteStamp(output_files);
+ // Write the stamp, which also depends on all datadeps. These are needed at
+ // runtime and should be compiled when the action is, but don't need to be
+ // done before we run the action.
+ std::vector<OutputFile> data_outs;
+ for (size_t i = 0; i < target_->datadeps().size(); i++)
+ data_outs.push_back(target_->datadeps()[i].ptr->dependency_output_file());
+ WriteStampForTarget(output_files, data_outs);
}
std::string NinjaActionTargetWriter::WriteRuleDefinition() {
@@ -92,7 +95,7 @@ std::string NinjaActionTargetWriter::WriteRuleDefinition() {
if (settings_->IsWin()) {
// Send through gyp-win-tool and use a response file.
std::string rspfile = custom_rule_name;
- if (has_sources())
+ if (!target_->sources().empty())
rspfile += ".$unique_name";
rspfile += ".rsp";
@@ -177,32 +180,6 @@ void NinjaActionTargetWriter::WriteSourceRules(
}
}
-void NinjaActionTargetWriter::WriteStamp(
- const std::vector<OutputFile>& output_files) {
- out_ << "build ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
- out_ << ": "
- << helper_.GetRulePrefix(target_->settings())
- << "stamp";
-
- // The action stamp depends on all output files from running the action.
- for (size_t i = 0; i < output_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, output_files[i]);
- }
-
- // It also depends on all datadeps. These are needed at runtime and should
- // be compiled when the action is, but don't need to be done before we run
- // the action.
- for (size_t i = 0; i < target_->datadeps().size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_,
- helper_.GetTargetOutputFile(target_->datadeps()[i].ptr));
- }
-
- out_ << std::endl;
-}
-
void NinjaActionTargetWriter::WriteOutputFilesForBuildLine(
const SourceFile& source,
std::vector<OutputFile>* output_files) {
diff --git a/tools/gn/ninja_action_target_writer.h b/tools/gn/ninja_action_target_writer.h
index f18a2b1..b440ac2 100644
--- a/tools/gn/ninja_action_target_writer.h
+++ b/tools/gn/ninja_action_target_writer.h
@@ -16,9 +16,7 @@ class OutputFile;
// Writes a .ninja file for a action target type.
class NinjaActionTargetWriter : public NinjaTargetWriter {
public:
- NinjaActionTargetWriter(const Target* target,
- const Toolchain* toolchain,
- std::ostream& out);
+ NinjaActionTargetWriter(const Target* target, std::ostream& out);
virtual ~NinjaActionTargetWriter();
virtual void Run() OVERRIDE;
@@ -31,8 +29,6 @@ class NinjaActionTargetWriter : public NinjaTargetWriter {
FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter,
WriteArgsSubstitutions);
- bool has_sources() const { return !target_->sources().empty(); }
-
// Writes the Ninja rule for invoking the script.
//
// Returns the name of the custom rule generated. This will be based on the
@@ -49,10 +45,6 @@ class NinjaActionTargetWriter : public NinjaTargetWriter {
const std::string& implicit_deps,
std::vector<OutputFile>* output_files);
- // Writes the .stamp rule that names this target and collects all invocations
- // of the script into one thing that other targets can depend on.
- void WriteStamp(const std::vector<OutputFile>& output_files);
-
// Writes the output files generated by the output template for the given
// source file. This will start with a space and will not include a newline.
// Appends the output files to the given vector.
diff --git a/tools/gn/ninja_action_target_writer_unittest.cc b/tools/gn/ninja_action_target_writer_unittest.cc
index b2b7f7f..b0161d6 100644
--- a/tools/gn/ninja_action_target_writer_unittest.cc
+++ b/tools/gn/ninja_action_target_writer_unittest.cc
@@ -8,18 +8,24 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/ninja_action_target_writer.h"
#include "tools/gn/substitution_list.h"
+#include "tools/gn/target.h"
#include "tools/gn/test_with_scope.h"
TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) {
TestWithScope setup;
setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+
Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::ACTION_FOREACH);
target.action_values().outputs() = SubstitutionList::MakeForTest(
"//out/Debug/gen/a b{{source_name_part}}.h",
"//out/Debug/gen/{{source_name_part}}.cc");
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
SourceFile source("//foo/bar.in");
std::vector<OutputFile> output_files;
@@ -41,12 +47,15 @@ TEST(NinjaActionTargetWriter, ActionNoSources) {
target.action_values().outputs() =
SubstitutionList::MakeForTest("//out/Debug/foo.out");
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
setup.settings()->set_target_os(Settings::LINUX);
setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
"/usr/bin/python")));
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected[] =
@@ -79,6 +88,9 @@ TEST(NinjaActionTargetWriter, ActionWithSources) {
target.action_values().outputs() =
SubstitutionList::MakeForTest("//out/Debug/foo.out");
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
// Posix.
{
setup.settings()->set_target_os(Settings::LINUX);
@@ -86,7 +98,7 @@ TEST(NinjaActionTargetWriter, ActionWithSources) {
"/usr/bin/python")));
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_linux[] =
@@ -112,7 +124,7 @@ TEST(NinjaActionTargetWriter, ActionWithSources) {
setup.settings()->set_target_os(Settings::WIN);
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_win[] =
@@ -142,8 +154,13 @@ TEST(NinjaActionTargetWriter, ForEach) {
// binaries).
Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
dep.set_output_type(Target::ACTION);
+ dep.SetToolchain(setup.toolchain());
+ dep.OnResolved();
+
Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
datadep.set_output_type(Target::ACTION);
+ datadep.SetToolchain(setup.toolchain());
+ datadep.OnResolved();
Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
target.set_output_type(Target::ACTION_FOREACH);
@@ -164,6 +181,9 @@ TEST(NinjaActionTargetWriter, ForEach) {
target.inputs().push_back(SourceFile("//foo/included.txt"));
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
// Posix.
{
setup.settings()->set_target_os(Settings::LINUX);
@@ -171,7 +191,7 @@ TEST(NinjaActionTargetWriter, ForEach) {
"/usr/bin/python")));
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_linux[] =
@@ -196,7 +216,7 @@ TEST(NinjaActionTargetWriter, ForEach) {
" source_name_part = input2\n"
"\n"
"build obj/foo/bar.stamp: "
- "stamp input1.out input2.out obj/foo/datadep.stamp\n";
+ "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
std::string out_str = out.str();
#if defined(OS_WIN)
@@ -212,7 +232,7 @@ TEST(NinjaActionTargetWriter, ForEach) {
setup.settings()->set_target_os(Settings::WIN);
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_win[] =
@@ -241,7 +261,7 @@ TEST(NinjaActionTargetWriter, ForEach) {
" source_name_part = input2\n"
"\n"
"build obj/foo/bar.stamp: "
- "stamp input1.out input2.out obj/foo/datadep.stamp\n";
+ "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
EXPECT_EQ(expected_win, out.str());
}
}
@@ -257,6 +277,9 @@ TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
target.action_values().set_script(SourceFile("//foo/script.py"));
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
SubstitutionPattern depfile;
Err err;
ASSERT_TRUE(
@@ -279,7 +302,7 @@ TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
"/usr/bin/python")));
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_linux[] =
@@ -315,7 +338,7 @@ TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
setup.settings()->set_target_os(Settings::WIN);
std::ostringstream out;
- NinjaActionTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaActionTargetWriter writer(&target, out);
writer.Run();
const char expected_win[] =
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index a5b858e..b96979d 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -10,7 +10,11 @@
#include "tools/gn/config_values_extractors.h"
#include "tools/gn/err.h"
#include "tools/gn/escape.h"
+#include "tools/gn/ninja_utils.h"
+#include "tools/gn/settings.h"
#include "tools/gn/string_utils.h"
+#include "tools/gn/substitution_writer.h"
+#include "tools/gn/target.h"
namespace {
@@ -41,9 +45,7 @@ struct DefineWriter {
};
struct IncludeWriter {
- IncludeWriter(PathOutput& path_output, const NinjaHelper& h)
- : helper(h),
- path_output_(path_output) {
+ IncludeWriter(PathOutput& path_output) : path_output_(path_output) {
}
~IncludeWriter() {
}
@@ -53,30 +55,15 @@ struct IncludeWriter {
path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH);
}
- const NinjaHelper& helper;
PathOutput& path_output_;
};
-Toolchain::ToolType GetToolTypeForTarget(const Target* target) {
- switch (target->output_type()) {
- case Target::STATIC_LIBRARY:
- return Toolchain::TYPE_ALINK;
- case Target::SHARED_LIBRARY:
- return Toolchain::TYPE_SOLINK;
- case Target::EXECUTABLE:
- return Toolchain::TYPE_LINK;
- default:
- return Toolchain::TYPE_NONE;
- }
-}
-
} // namespace
NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
- const Toolchain* toolchain,
std::ostream& out)
- : NinjaTargetWriter(target, toolchain, out),
- tool_type_(GetToolTypeForTarget(target)){
+ : NinjaTargetWriter(target, out),
+ tool_(target->toolchain()->GetToolForTargetFinalOutput(target)) {
}
NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() {
@@ -95,46 +82,44 @@ void NinjaBinaryTargetWriter::Run() {
}
void NinjaBinaryTargetWriter::WriteCompilerVars() {
+ const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
// Defines.
- out_ << "defines =";
- RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
- DefineWriter(), out_);
- out_ << std::endl;
+ if (subst.used[SUBSTITUTION_DEFINES]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
+ RecursiveTargetConfigToStream<std::string>(
+ target_, &ConfigValues::defines, DefineWriter(), out_);
+ out_ << std::endl;
+ }
// Include directories.
- out_ << "includes =";
- RecursiveTargetConfigToStream<SourceDir>(target_, &ConfigValues::include_dirs,
- IncludeWriter(path_output_, helper_),
- out_);
-
- out_ << std::endl;
+ if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
+ RecursiveTargetConfigToStream<SourceDir>(
+ target_, &ConfigValues::include_dirs,
+ IncludeWriter(path_output_), out_);
+ out_ << std::endl;
+ }
// C flags and friends.
EscapeOptions flag_escape_options = GetFlagOptions();
-#define WRITE_FLAGS(name) \
- out_ << #name " ="; \
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, \
- flag_escape_options, out_); \
- out_ << std::endl;
+#define WRITE_FLAGS(name, subst_enum) \
+ if (subst.used[subst_enum]) { \
+ out_ << kSubstitutionNinjaNames[subst_enum] << " ="; \
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, \
+ flag_escape_options, out_); \
+ out_ << std::endl; \
+ }
- WRITE_FLAGS(cflags)
- WRITE_FLAGS(cflags_c)
- WRITE_FLAGS(cflags_cc)
- WRITE_FLAGS(cflags_objc)
- WRITE_FLAGS(cflags_objcc)
+ WRITE_FLAGS(cflags, SUBSTITUTION_CFLAGS)
+ WRITE_FLAGS(cflags_c, SUBSTITUTION_CFLAGS_C)
+ WRITE_FLAGS(cflags_cc, SUBSTITUTION_CFLAGS_CC)
+ WRITE_FLAGS(cflags_objc, SUBSTITUTION_CFLAGS_OBJC)
+ WRITE_FLAGS(cflags_objcc, SUBSTITUTION_CFLAGS_OBJCC)
#undef WRITE_FLAGS
- // Write some variables about the target for the toolchain definition to use.
- out_ << "target_name = " << target_->label().name() << std::endl;
- out_ << "target_out_dir = ";
- path_output_.WriteDir(out_, helper_.GetTargetOutputDir(target_),
- PathOutput::DIR_NO_LAST_SLASH);
- out_ << std::endl;
- out_ << "root_out_dir = ";
- path_output_.WriteDir(out_, target_->settings()->toolchain_output_subdir(),
- PathOutput::DIR_NO_LAST_SLASH);
- out_ << std::endl << std::endl;
+ WriteSharedVars(subst);
}
void NinjaBinaryTargetWriter::WriteSources(
@@ -145,113 +130,105 @@ void NinjaBinaryTargetWriter::WriteSources(
std::string implicit_deps =
WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+ std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_);
+
+ std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
for (size_t i = 0; i < sources.size(); i++) {
- const SourceFile& input_file = sources[i];
-
- SourceFileType input_file_type = GetSourceFileType(input_file);
- if (input_file_type == SOURCE_UNKNOWN)
- continue; // Skip unknown file types.
- if (input_file_type == SOURCE_O) {
- // Object files just get passed to the output and not compiled.
- object_files->push_back(helper_.GetOutputFileForSource(
- target_, input_file, input_file_type));
- continue;
+ Toolchain::ToolType tool_type = Toolchain::TYPE_NONE;
+ if (!GetOutputFilesForSource(target_, sources[i],
+ &tool_type, &tool_outputs))
+ continue; // No output for this source.
+
+ if (tool_type != Toolchain::TYPE_NONE) {
+ out_ << "build";
+ path_output_.WriteFiles(out_, tool_outputs);
+ out_ << ": " << rule_prefix << Toolchain::ToolTypeToName(tool_type);
+ out_ << " ";
+ path_output_.WriteFile(out_, sources[i]);
+ out_ << implicit_deps << std::endl;
}
- std::string command =
- helper_.GetRuleForSourceType(settings_, input_file_type);
- if (command.empty())
- continue; // Skip files not needing compilation.
-
- OutputFile output_file = helper_.GetOutputFileForSource(
- target_, input_file, input_file_type);
- object_files->push_back(output_file);
-
- out_ << "build ";
- path_output_.WriteFile(out_, output_file);
- out_ << ": " << command << " ";
- path_output_.WriteFile(out_, input_file);
- out_ << implicit_deps << std::endl;
+
+ // It's theoretically possible for a compiler to produce more than one
+ // output, but we'll only link to the first output.
+ object_files->push_back(tool_outputs[0]);
}
out_ << std::endl;
}
void NinjaBinaryTargetWriter::WriteLinkerStuff(
const std::vector<OutputFile>& object_files) {
- // Manifest file on Windows.
- // TODO(brettw) this seems not to be necessary for static libs, skip in
- // that case?
- OutputFile windows_manifest;
- if (settings_->IsWin()) {
- windows_manifest = helper_.GetTargetOutputDir(target_);
- windows_manifest.value().append(target_->label().name());
- windows_manifest.value().append(".intermediate.manifest");
- out_ << "manifests = ";
- path_output_.WriteFile(out_, windows_manifest);
- out_ << std::endl;
- }
+ std::vector<OutputFile> output_files;
+ SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+ target_, tool_, tool_->outputs(), &output_files);
- const Toolchain::Tool& tool = toolchain_->GetTool(tool_type_);
- WriteLinkerFlags(tool, windows_manifest);
- WriteLibs(tool);
-
- // The external output file is the one that other libs depend on.
- OutputFile external_output_file = helper_.GetTargetOutputFile(target_);
-
- // The internal output file is the "main thing" we think we're making. In
- // the case of shared libraries, this is the shared library and the external
- // output file is the import library. In other cases, the internal one and
- // the external one are the same.
- OutputFile internal_output_file;
- if (target_->output_type() == Target::SHARED_LIBRARY) {
- if (settings_->IsWin()) {
- internal_output_file.value() =
- target_->settings()->toolchain_output_subdir().value();
- internal_output_file.value().append(target_->label().name());
- internal_output_file.value().append(".dll");
- } else {
- internal_output_file = external_output_file;
- }
- } else {
- internal_output_file = external_output_file;
- }
+ out_ << "build";
+ path_output_.WriteFiles(out_, output_files);
- // In Python see "self.ninja.build(output, command, input,"
- WriteLinkCommand(external_output_file, internal_output_file, object_files);
+ out_ << ": "
+ << GetNinjaRulePrefixForToolchain(settings_)
+ << Toolchain::ToolTypeToName(
+ target_->toolchain()->GetToolTypeForTargetFinalOutput(target_));
- if (target_->output_type() == Target::SHARED_LIBRARY) {
- // The shared object name doesn't include a path.
- out_ << " soname = ";
- out_ << FindFilename(&internal_output_file.value());
- out_ << std::endl;
+ UniqueVector<OutputFile> extra_object_files;
+ UniqueVector<const Target*> linkable_deps;
+ UniqueVector<const Target*> non_linkable_deps;
+ GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
- out_ << " lib = ";
- path_output_.WriteFile(out_, internal_output_file);
- out_ << std::endl;
+ // Object files.
+ for (size_t i = 0; i < object_files.size(); i++) {
+ out_ << " ";
+ path_output_.WriteFile(out_, object_files[i]);
+ }
+ for (size_t i = 0; i < extra_object_files.size(); i++) {
+ out_ << " ";
+ path_output_.WriteFile(out_, extra_object_files[i]);
+ }
- if (settings_->IsWin()) {
- out_ << " dll = ";
- path_output_.WriteFile(out_, internal_output_file);
- out_ << std::endl;
- }
+ std::vector<OutputFile> implicit_deps;
+ std::vector<OutputFile> solibs;
- if (settings_->IsWin()) {
- out_ << " implibflag = /IMPLIB:";
- path_output_.WriteFile(out_, external_output_file);
- out_ << std::endl;
+ for (size_t i = 0; i < linkable_deps.size(); i++) {
+ const Target* cur = linkable_deps[i];
+
+ // All linkable deps should have a link output file.
+ DCHECK(!cur->link_output_file().value().empty())
+ << "No link output file for "
+ << target_->label().GetUserVisibleName(false);
+
+ if (cur->dependency_output_file().value() !=
+ cur->link_output_file().value()) {
+ // This is a shared library with separate link and deps files. Save for
+ // later.
+ implicit_deps.push_back(cur->dependency_output_file());
+ solibs.push_back(cur->link_output_file());
+ } else {
+ // Normal case, just link to this target.
+ out_ << " ";
+ path_output_.WriteFile(out_, cur->link_output_file());
}
+ }
- // TODO(brettw) postbuild steps.
- if (settings_->IsMac())
- out_ << " postbuilds = $ && (export BUILT_PRODUCTS_DIR=/Users/brettw/prj/src/out/gn; export CONFIGURATION=Debug; export DYLIB_INSTALL_NAME_BASE=@rpath; export EXECUTABLE_NAME=libbase.dylib; export EXECUTABLE_PATH=libbase.dylib; export FULL_PRODUCT_NAME=libbase.dylib; export LD_DYLIB_INSTALL_NAME=@rpath/libbase.dylib; export MACH_O_TYPE=mh_dylib; export PRODUCT_NAME=base; export PRODUCT_TYPE=com.apple.product-type.library.dynamic; export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk; export SRCROOT=/Users/brettw/prj/src/out/gn/../../base; export SOURCE_ROOT=\"$${SRCROOT}\"; export TARGET_BUILD_DIR=/Users/brettw/prj/src/out/gn; export TEMP_DIR=\"$${TMPDIR}\"; (cd ../../base && ../build/mac/strip_from_xcode); G=$$?; ((exit $$G) || rm -rf libbase.dylib) && exit $$G)";
+ // Append implicit dependencies collected above.
+ if (!implicit_deps.empty()) {
+ out_ << " |";
+ path_output_.WriteFiles(out_, implicit_deps);
}
+ // Append data dependencies as order-only dependencies.
+ WriteOrderOnlyDependencies(non_linkable_deps);
+
+ // End of the link "build" line.
out_ << std::endl;
+
+ // These go in the inner scope of the link line.
+ WriteLinkerFlags();
+ WriteLibs();
+ WriteOutputExtension();
+ WriteSolibs(solibs);
}
-void NinjaBinaryTargetWriter::WriteLinkerFlags(
- const Toolchain::Tool& tool,
- const OutputFile& windows_manifest) {
- out_ << "ldflags =";
+void NinjaBinaryTargetWriter::WriteLinkerFlags() {
+ out_ << " ldflags =";
// First the ldflags from the target and its config.
EscapeOptions flag_options = GetFlagOptions();
@@ -267,23 +244,16 @@ void NinjaBinaryTargetWriter::WriteLinkerFlags(
PathOutput lib_path_output(path_output_.current_dir(),
ESCAPE_NINJA_COMMAND);
for (size_t i = 0; i < all_lib_dirs.size(); i++) {
- out_ << " " << tool.lib_dir_prefix;
+ out_ << " " << tool_->lib_dir_switch();
lib_path_output.WriteDir(out_, all_lib_dirs[i],
PathOutput::DIR_NO_LAST_SLASH);
}
}
-
- // Append manifest flag on Windows to reference our file.
- // HACK ERASEME BRETTW FIXME
- if (settings_->IsWin()) {
- out_ << " /MANIFEST /ManifestFile:";
- path_output_.WriteFile(out_, windows_manifest);
- }
out_ << std::endl;
}
-void NinjaBinaryTargetWriter::WriteLibs(const Toolchain::Tool& tool) {
- out_ << "libs =";
+void NinjaBinaryTargetWriter::WriteLibs() {
+ out_ << " libs =";
// Libraries that have been recursively pushed through the dependency tree.
EscapeOptions lib_escape_opts;
@@ -299,51 +269,33 @@ void NinjaBinaryTargetWriter::WriteLibs(const Toolchain::Tool& tool) {
all_libs[i].substr(0, all_libs[i].size() - framework_ending.size()),
lib_escape_opts);
} else {
- out_ << " " << tool.lib_prefix;
+ out_ << " " << tool_->lib_switch();
EscapeStringToStream(out_, all_libs[i], lib_escape_opts);
}
}
out_ << std::endl;
}
-void NinjaBinaryTargetWriter::WriteLinkCommand(
- const OutputFile& external_output_file,
- const OutputFile& internal_output_file,
- const std::vector<OutputFile>& object_files) {
- out_ << "build ";
- path_output_.WriteFile(out_, internal_output_file);
- if (external_output_file != internal_output_file) {
- out_ << " ";
- path_output_.WriteFile(out_, external_output_file);
- }
- out_ << ": "
- << helper_.GetRulePrefix(target_->settings())
- << Toolchain::ToolTypeToName(tool_type_);
-
- UniqueVector<OutputFile> extra_object_files;
- UniqueVector<const Target*> linkable_deps;
- UniqueVector<const Target*> non_linkable_deps;
- GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
-
- // Object files.
- for (size_t i = 0; i < object_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, object_files[i]);
- }
- for (size_t i = 0; i < extra_object_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, extra_object_files[i]);
- }
-
- // Libs.
- for (size_t i = 0; i < linkable_deps.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(linkable_deps[i]));
+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_ << std::endl;
+}
- // Append data dependencies as implicit dependencies.
- WriteImplicitDependencies(non_linkable_deps);
+void NinjaBinaryTargetWriter::WriteSolibs(
+ const std::vector<OutputFile>& solibs) {
+ if (solibs.empty())
+ return;
+ out_ << " solibs =";
+ path_output_.WriteFiles(out_, solibs);
out_ << std::endl;
}
@@ -353,12 +305,6 @@ void NinjaBinaryTargetWriter::WriteSourceSetStamp(
// depend on this will reference the object files directly. However, writing
// this rule allows the user to type the name of the target and get a build
// which can be convenient for development.
- out_ << "build ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
- out_ << ": "
- << helper_.GetRulePrefix(target_->settings())
- << "stamp";
-
UniqueVector<OutputFile> extra_object_files;
UniqueVector<const Target*> linkable_deps;
UniqueVector<const Target*> non_linkable_deps;
@@ -369,15 +315,11 @@ void NinjaBinaryTargetWriter::WriteSourceSetStamp(
// deps instead.
DCHECK(extra_object_files.empty());
- for (size_t i = 0; i < object_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, object_files[i]);
- }
+ std::vector<OutputFile> order_only_deps;
+ for (size_t i = 0; i < non_linkable_deps.size(); i++)
+ order_only_deps.push_back(non_linkable_deps[i]->dependency_output_file());
- // Append data dependencies as implicit dependencies.
- WriteImplicitDependencies(non_linkable_deps);
-
- out_ << std::endl;
+ WriteStampForTarget(object_files, order_only_deps);
}
void NinjaBinaryTargetWriter::GetDeps(
@@ -432,14 +374,13 @@ void NinjaBinaryTargetWriter::ClassifyDependency(
target_->output_type() != Target::STATIC_LIBRARY) {
// Linking in a source set to an executable or shared library, copy its
// object files.
+ std::vector<OutputFile> tool_outputs; // Prevent allocation in loop.
for (size_t i = 0; i < dep->sources().size(); i++) {
- SourceFileType input_file_type = GetSourceFileType(dep->sources()[i]);
- if (input_file_type != SOURCE_UNKNOWN &&
- input_file_type != SOURCE_H) {
- // Note we need to specify the target as the source_set target
- // itself, since this is used to prefix the object file name.
- extra_object_files->push_back(helper_.GetOutputFileForSource(
- dep, dep->sources()[i], input_file_type));
+ Toolchain::ToolType tool_type = Toolchain::TYPE_NONE;
+ if (GetOutputFilesForSource(dep, dep->sources()[i], &tool_type,
+ &tool_outputs)) {
+ // Only link the first output if there are more than one.
+ extra_object_files->push_back(tool_outputs[0]);
}
}
}
@@ -450,7 +391,7 @@ void NinjaBinaryTargetWriter::ClassifyDependency(
}
}
-void NinjaBinaryTargetWriter::WriteImplicitDependencies(
+void NinjaBinaryTargetWriter::WriteOrderOnlyDependencies(
const UniqueVector<const Target*>& non_linkable_deps) {
const std::vector<SourceFile>& data = target_->data();
if (!non_linkable_deps.empty() || !data.empty()) {
@@ -459,15 +400,39 @@ void NinjaBinaryTargetWriter::WriteImplicitDependencies(
// Non-linkable targets.
for (size_t i = 0; i < non_linkable_deps.size(); i++) {
out_ << " ";
- path_output_.WriteFile(out_,
- helper_.GetTargetOutputFile(non_linkable_deps[i]));
+ path_output_.WriteFile(
+ out_, non_linkable_deps[i]->dependency_output_file());
}
+ }
+}
- // Data files.
- const std::vector<SourceFile>& data = target_->data();
- for (size_t i = 0; i < data.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, data[i]);
- }
+bool NinjaBinaryTargetWriter::GetOutputFilesForSource(
+ const Target* target,
+ const SourceFile& source,
+ Toolchain::ToolType* computed_tool_type,
+ std::vector<OutputFile>* outputs) const {
+ outputs->clear();
+ *computed_tool_type = Toolchain::TYPE_NONE;
+
+ SourceFileType file_type = GetSourceFileType(source);
+ if (file_type == SOURCE_UNKNOWN)
+ return false;
+ if (file_type == SOURCE_O) {
+ // Object files just get passed to the output and not compiled.
+ outputs->push_back(OutputFile(settings_->build_settings(), source));
+ return true;
}
+
+ *computed_tool_type =
+ target->toolchain()->GetToolTypeForSourceType(file_type);
+ if (*computed_tool_type == Toolchain::TYPE_NONE)
+ return false; // No tool for this file (it's a header file or something).
+ const Tool* tool = target->toolchain()->GetTool(*computed_tool_type);
+ if (!tool)
+ return false; // Tool does not apply for this toolchain.file.
+
+ // Figure out what output(s) this compiler produces.
+ SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+ target, source, tool->outputs(), outputs);
+ return !outputs->empty();
}
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index 1476253..e3236ce 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -6,17 +6,18 @@
#define TOOLS_GN_NINJA_BINARY_TARGET_WRITER_H_
#include "base/compiler_specific.h"
+#include "tools/gn/config_values.h"
#include "tools/gn/ninja_target_writer.h"
#include "tools/gn/toolchain.h"
#include "tools/gn/unique_vector.h"
+struct EscapeOptions;
+
// Writes a .ninja file for a binary target type (an executable, a shared
// library, or a static library).
class NinjaBinaryTargetWriter : public NinjaTargetWriter {
public:
- NinjaBinaryTargetWriter(const Target* target,
- const Toolchain* toolchain,
- std::ostream& out);
+ NinjaBinaryTargetWriter(const Target* target, std::ostream& out);
virtual ~NinjaBinaryTargetWriter();
virtual void Run() OVERRIDE;
@@ -27,14 +28,10 @@ class NinjaBinaryTargetWriter : public NinjaTargetWriter {
void WriteCompilerVars();
void WriteSources(std::vector<OutputFile>* object_files);
void WriteLinkerStuff(const std::vector<OutputFile>& object_files);
- void WriteLinkerFlags(const Toolchain::Tool& tool,
- const OutputFile& windows_manifest);
- void WriteLibs(const Toolchain::Tool& tool);
-
- // Writes the build line for linking the target. Includes newline.
- void WriteLinkCommand(const OutputFile& external_output_file,
- const OutputFile& internal_output_file,
- const std::vector<OutputFile>& object_files);
+ void WriteLinkerFlags();
+ void WriteLibs();
+ void WriteOutputExtension();
+ void WriteSolibs(const std::vector<OutputFile>& solibs);
// Writes the stamp line for a source set. These are not linked.
void WriteSourceSetStamp(const std::vector<OutputFile>& object_files);
@@ -57,12 +54,29 @@ class NinjaBinaryTargetWriter : public NinjaTargetWriter {
// Writes the implicit dependencies for the link or stamp line. This is
// the "||" and everything following it on the ninja line.
//
- // The implicit dependencies are the non-linkable deps passed in as an
+ // The order-only dependencies are the non-linkable deps passed in as an
// argument, plus the data file depdencies in the target.
- void WriteImplicitDependencies(
+ void WriteOrderOnlyDependencies(
const UniqueVector<const Target*>& non_linkable_deps);
- Toolchain::ToolType tool_type_;
+ // Computes the set of output files resulting from compiling the given source
+ // file. If the file can be compiled and the tool exists, fills the outputs in
+ // and writes the tool type to computed_tool_type. If the file is not
+ // compilable, returns false.
+ //
+ // The target that the source belongs to is passed as an argument. In the
+ // case of linking to source sets, this can be different than the target
+ // this class is currently writing.
+ //
+ // The function can succeed with a "NONE" tool type for object files which are
+ // just passed to the output. The output will always be overwritten, not
+ // appended to.
+ bool GetOutputFilesForSource(const Target* target,
+ const SourceFile& source,
+ Toolchain::ToolType* computed_tool_type,
+ std::vector<OutputFile>* outputs) const;
+
+ const Tool* tool_;
DISALLOW_COPY_AND_ASSIGN(NinjaBinaryTargetWriter);
};
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index 5e03871..20f9a8b 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/ninja_binary_target_writer.h"
+#include "tools/gn/target.h"
#include "tools/gn/test_with_scope.h"
TEST(NinjaBinaryTargetWriter, SourceSet) {
@@ -21,119 +22,107 @@ TEST(NinjaBinaryTargetWriter, SourceSet) {
// dependents to link.
target.sources().push_back(SourceFile("//foo/input3.o"));
target.sources().push_back(SourceFile("//foo/input4.obj"));
+ target.SetToolchain(setup.toolchain());
target.OnResolved();
// Source set itself.
{
std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaBinaryTargetWriter writer(&target, out);
writer.Run();
- const char expected_win[] =
+ const char expected[] =
"defines =\n"
- "includes =\n"
+ "include_dirs =\n"
"cflags =\n"
"cflags_c =\n"
"cflags_cc =\n"
"cflags_objc =\n"
"cflags_objcc =\n"
- "target_name = bar\n"
- "target_out_dir = obj/foo\n"
"root_out_dir = \n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = bar\n"
"\n"
- "build obj/foo/bar.input1.obj: cxx ../../foo/input1.cc\n"
- "build obj/foo/bar.input2.obj: cxx ../../foo/input2.cc\n"
+ "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
+ "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
"\n"
- "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.obj "
- "obj/foo/bar.input2.obj ../../foo/input3.o ../../foo/input4.obj\n";
+ "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
std::string out_str = out.str();
-#if defined(OS_WIN)
- std::replace(out_str.begin(), out_str.end(), '\\', '/');
-#endif
- EXPECT_EQ(expected_win, out_str);
+ EXPECT_EQ(expected, out_str);
}
// A shared library that depends on the source set.
Target shlib_target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
shlib_target.set_output_type(Target::SHARED_LIBRARY);
shlib_target.deps().push_back(LabelTargetPair(&target));
+ shlib_target.SetToolchain(setup.toolchain());
shlib_target.OnResolved();
{
std::ostringstream out;
- NinjaBinaryTargetWriter writer(&shlib_target, setup.toolchain(), out);
+ NinjaBinaryTargetWriter writer(&shlib_target, out);
writer.Run();
- const char expected_win[] =
+ const char expected[] =
"defines =\n"
- "includes =\n"
+ "include_dirs =\n"
"cflags =\n"
"cflags_c =\n"
"cflags_cc =\n"
"cflags_objc =\n"
"cflags_objcc =\n"
- "target_name = shlib\n"
- "target_out_dir = obj/foo\n"
"root_out_dir = \n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
"\n"
"\n"
- "manifests = obj/foo/shlib.intermediate.manifest\n"
- "ldflags = /MANIFEST /ManifestFile:obj/foo/shlib.intermediate."
- "manifest\n"
- "libs =\n"
// Ordering of the obj files here should come out in the order
// specified, with the target's first, followed by the source set's, in
// order.
- "build shlib.dll shlib.dll.lib: solink obj/foo/bar.input1.obj "
- "obj/foo/bar.input2.obj ../../foo/input3.o "
- "../../foo/input4.obj\n"
- " soname = shlib.dll\n"
- " lib = shlib.dll\n"
- " dll = shlib.dll\n"
- " implibflag = /IMPLIB:shlib.dll.lib\n\n";
+ "build libshlib.so: solink obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n";
std::string out_str = out.str();
-#if defined(OS_WIN)
- std::replace(out_str.begin(), out_str.end(), '\\', '/');
-#endif
- EXPECT_EQ(expected_win, out_str);
+ EXPECT_EQ(expected, out_str);
}
// A static library that depends on the source set (should not link it).
Target stlib_target(setup.settings(), Label(SourceDir("//foo/"), "stlib"));
stlib_target.set_output_type(Target::STATIC_LIBRARY);
stlib_target.deps().push_back(LabelTargetPair(&target));
+ stlib_target.SetToolchain(setup.toolchain());
stlib_target.OnResolved();
{
std::ostringstream out;
- NinjaBinaryTargetWriter writer(&stlib_target, setup.toolchain(), out);
+ NinjaBinaryTargetWriter writer(&stlib_target, out);
writer.Run();
- const char expected_win[] =
+ const char expected[] =
"defines =\n"
- "includes =\n"
+ "include_dirs =\n"
"cflags =\n"
"cflags_c =\n"
"cflags_cc =\n"
"cflags_objc =\n"
"cflags_objcc =\n"
- "target_name = stlib\n"
- "target_out_dir = obj/foo\n"
"root_out_dir = \n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libstlib\n"
"\n"
"\n"
- "manifests = obj/foo/stlib.intermediate.manifest\n"
- "ldflags = /MANIFEST /ManifestFile:obj/foo/stlib.intermediate.manifest\n"
- "libs =\n"
- // There are no sources so there are no params to alink.
- "build obj/foo/stlib.lib: alink\n\n";
+ // 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\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = \n";
std::string out_str = out.str();
-#if defined(OS_WIN)
- std::replace(out_str.begin(), out_str.end(), '\\', '/');
-#endif
- EXPECT_EQ(expected_win, out_str);
+ EXPECT_EQ(expected, out_str);
}
-
}
TEST(NinjaBinaryTargetWriter, ProductExtension) {
@@ -147,39 +136,35 @@ TEST(NinjaBinaryTargetWriter, ProductExtension) {
target.set_output_extension(std::string("so.6"));
target.sources().push_back(SourceFile("//foo/input1.cc"));
target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.SetToolchain(setup.toolchain());
target.OnResolved();
std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaBinaryTargetWriter writer(&target, out);
writer.Run();
const char expected[] =
"defines =\n"
- "includes =\n"
+ "include_dirs =\n"
"cflags =\n"
"cflags_c =\n"
"cflags_cc =\n"
"cflags_objc =\n"
"cflags_objcc =\n"
- "target_name = shlib\n"
- "target_out_dir = obj/foo\n"
"root_out_dir = \n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
"\n"
- "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
- "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
+ "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc\n"
+ "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc\n"
"\n"
- "ldflags =\n"
- "libs =\n"
- "build libshlib.so.6: solink obj/foo/shlib.input1.o "
- "obj/foo/shlib.input2.o\n"
- " soname = libshlib.so.6\n"
- " lib = libshlib.so.6\n"
- "\n";
+ "build libshlib.so.6: solink obj/foo/libshlib.input1.o "
+ "obj/foo/libshlib.input2.o\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so.6\n";
std::string out_str = out.str();
-#if defined(OS_WIN)
- std::replace(out_str.begin(), out_str.end(), '\\', '/');
-#endif
EXPECT_EQ(expected, out_str);
}
@@ -196,36 +181,34 @@ TEST(NinjaBinaryTargetWriter, EmptyProductExtension) {
target.sources().push_back(SourceFile("//foo/input1.cc"));
target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaBinaryTargetWriter writer(&target, out);
writer.Run();
const char expected[] =
"defines =\n"
- "includes =\n"
+ "include_dirs =\n"
"cflags =\n"
"cflags_c =\n"
"cflags_cc =\n"
"cflags_objc =\n"
"cflags_objcc =\n"
- "target_name = shlib\n"
- "target_out_dir = obj/foo\n"
"root_out_dir = \n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
"\n"
- "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
- "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
+ "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc\n"
+ "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc\n"
"\n"
- "ldflags =\n"
- "libs =\n"
- "build libshlib.so: solink obj/foo/shlib.input1.o "
- "obj/foo/shlib.input2.o\n"
- " soname = libshlib.so\n"
- " lib = libshlib.so\n"
- "\n";
+ "build libshlib.so: solink obj/foo/libshlib.input1.o "
+ "obj/foo/libshlib.input2.o\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n";
std::string out_str = out.str();
-#if defined(OS_WIN)
- std::replace(out_str.begin(), out_str.end(), '\\', '/');
-#endif
EXPECT_EQ(expected, out_str);
}
diff --git a/tools/gn/ninja_build_writer.cc b/tools/gn/ninja_build_writer.cc
index 007089d..07c54f2 100644
--- a/tools/gn/ninja_build_writer.cc
+++ b/tools/gn/ninja_build_writer.cc
@@ -18,6 +18,7 @@
#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/input_file_manager.h"
+#include "tools/gn/ninja_utils.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/target.h"
#include "tools/gn/trace.h"
@@ -82,8 +83,7 @@ NinjaBuildWriter::NinjaBuildWriter(
default_toolchain_targets_(default_toolchain_targets),
out_(out),
dep_out_(dep_out),
- path_output_(build_settings->build_dir(), ESCAPE_NINJA),
- helper_(build_settings) {
+ path_output_(build_settings->build_dir(), ESCAPE_NINJA) {
}
NinjaBuildWriter::~NinjaBuildWriter() {
@@ -155,8 +155,7 @@ void NinjaBuildWriter::WriteNinjaRules() {
void NinjaBuildWriter::WriteSubninjas() {
for (size_t i = 0; i < all_settings_.size(); i++) {
out_ << "subninja ";
- path_output_.WriteFile(out_,
- helper_.GetNinjaFileForToolchain(all_settings_[i]));
+ path_output_.WriteFile(out_, GetNinjaFileForToolchain(all_settings_[i]));
out_ << std::endl;
}
out_ << std::endl;
@@ -192,7 +191,7 @@ void NinjaBuildWriter::WritePhonyAndAllRules() {
for (size_t i = 0; i < default_toolchain_targets_.size(); i++) {
const Target* target = default_toolchain_targets_[i];
const Label& label = target->label();
- OutputFile target_file = helper_.GetTargetOutputFile(target);
+ const OutputFile& target_file = target->dependency_output_file();
// Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
std::string long_name = label.GetUserVisibleName(false);
@@ -224,7 +223,7 @@ void NinjaBuildWriter::WritePhonyAndAllRules() {
for (size_t i = 0; i < toplevel_targets.size(); i++) {
if (small_name_count[toplevel_targets[i]->label().name()] > 1) {
const Target* target = toplevel_targets[i];
- WritePhonyRule(target, helper_.GetTargetOutputFile(target),
+ WritePhonyRule(target, target->dependency_output_file(),
target->label().name());
}
}
diff --git a/tools/gn/ninja_build_writer.h b/tools/gn/ninja_build_writer.h
index 9567456..b8c8742 100644
--- a/tools/gn/ninja_build_writer.h
+++ b/tools/gn/ninja_build_writer.h
@@ -8,7 +8,6 @@
#include <iosfwd>
#include <vector>
-#include "tools/gn/ninja_helper.h"
#include "tools/gn/path_output.h"
class BuildSettings;
@@ -49,8 +48,6 @@ class NinjaBuildWriter {
std::ostream& dep_out_;
PathOutput path_output_;
- NinjaHelper helper_;
-
DISALLOW_COPY_AND_ASSIGN(NinjaBuildWriter);
};
diff --git a/tools/gn/ninja_copy_target_writer.cc b/tools/gn/ninja_copy_target_writer.cc
index 4a88102..ca34852 100644
--- a/tools/gn/ninja_copy_target_writer.cc
+++ b/tools/gn/ninja_copy_target_writer.cc
@@ -5,20 +5,61 @@
#include "tools/gn/ninja_copy_target_writer.h"
#include "base/strings/string_util.h"
+#include "tools/gn/ninja_utils.h"
+#include "tools/gn/output_file.h"
+#include "tools/gn/scheduler.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/substitution_list.h"
#include "tools/gn/substitution_writer.h"
+#include "tools/gn/target.h"
+#include "tools/gn/toolchain.h"
NinjaCopyTargetWriter::NinjaCopyTargetWriter(const Target* target,
- const Toolchain* toolchain,
std::ostream& out)
- : NinjaTargetWriter(target, toolchain, out) {
+ : NinjaTargetWriter(target, out) {
}
NinjaCopyTargetWriter::~NinjaCopyTargetWriter() {
}
void NinjaCopyTargetWriter::Run() {
+ const Tool* copy_tool = target_->toolchain()->GetTool(Toolchain::TYPE_COPY);
+ if (!copy_tool) {
+ g_scheduler->FailWithError(Err(NULL,
+ "Copy tool not defined",
+ "The toolchain " +
+ target_->toolchain()->label().GetUserVisibleName(false) +
+ "\n used by target " + target_->label().GetUserVisibleName(false) +
+ "\n doesn't define a \"copy\" tool."));
+ return;
+ }
+
+ const Tool* stamp_tool = target_->toolchain()->GetTool(Toolchain::TYPE_STAMP);
+ if (!stamp_tool) {
+ g_scheduler->FailWithError(Err(NULL,
+ "Copy tool not defined",
+ "The toolchain " +
+ target_->toolchain()->label().GetUserVisibleName(false) +
+ "\n used by target " + target_->label().GetUserVisibleName(false) +
+ "\n doesn't define a \"stamp\" tool."));
+ return;
+ }
+
+ // Figure out the substitutions used by the copy and stamp tools.
+ SubstitutionBits required_bits = copy_tool->substitution_bits();
+ required_bits.MergeFrom(stamp_tool->substitution_bits());
+
+ // General target-related substitutions needed by both tools.
+ WriteSharedVars(required_bits);
+
+ std::vector<OutputFile> output_files;
+ WriteCopyRules(&output_files);
+ out_ << std::endl;
+ WriteStampForTarget(output_files, std::vector<OutputFile>());
+}
+
+void NinjaCopyTargetWriter::WriteCopyRules(
+ std::vector<OutputFile>* output_files) {
CHECK(target_->action_values().outputs().list().size() == 1);
const SubstitutionList& output_subst_list =
target_->action_values().outputs();
@@ -26,9 +67,9 @@ void NinjaCopyTargetWriter::Run() {
<< "Should have one entry exactly.";
const SubstitutionPattern& output_subst = output_subst_list.list()[0];
- std::vector<OutputFile> output_files;
-
- std::string rule_prefix = helper_.GetRulePrefix(target_->settings());
+ std::string tool_name =
+ GetNinjaRulePrefixForToolchain(settings_) +
+ Toolchain::ToolTypeToName(Toolchain::TYPE_COPY);
// Note that we don't write implicit deps for copy steps. "copy" only
// depends on the output files themselves, rather than having includes
@@ -57,22 +98,12 @@ void NinjaCopyTargetWriter::Run() {
OutputFile output_file =
SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
target_->settings(), output_subst, input_file);
- output_files.push_back(output_file);
+ output_files->push_back(output_file);
out_ << "build ";
path_output_.WriteFile(out_, output_file);
- out_ << ": " << rule_prefix << "copy ";
+ out_ << ": " << tool_name << " ";
path_output_.WriteFile(out_, input_file);
out_ << std::endl;
}
-
- // Write out the rule for the target to copy all of them.
- out_ << std::endl << "build ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
- out_ << ": " << rule_prefix << "stamp";
- for (size_t i = 0; i < output_files.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, output_files[i]);
- }
- out_ << std::endl;
}
diff --git a/tools/gn/ninja_copy_target_writer.h b/tools/gn/ninja_copy_target_writer.h
index cadbc38..58003dc 100644
--- a/tools/gn/ninja_copy_target_writer.h
+++ b/tools/gn/ninja_copy_target_writer.h
@@ -8,17 +8,21 @@
#include "base/compiler_specific.h"
#include "tools/gn/ninja_target_writer.h"
+class Tool;
+
// Writes a .ninja file for a copy target type.
class NinjaCopyTargetWriter : public NinjaTargetWriter {
public:
- NinjaCopyTargetWriter(const Target* target,
- const Toolchain* toolchain,
- std::ostream& out);
+ NinjaCopyTargetWriter(const Target* target, std::ostream& out);
virtual ~NinjaCopyTargetWriter();
virtual void Run() OVERRIDE;
private:
+ // Writes the rules top copy the file(s), putting the computed output file
+ // name(s) into the given vector.
+ void WriteCopyRules(std::vector<OutputFile>* output_files);
+
DISALLOW_COPY_AND_ASSIGN(NinjaCopyTargetWriter);
};
diff --git a/tools/gn/ninja_copy_target_writer_unittest.cc b/tools/gn/ninja_copy_target_writer_unittest.cc
index a99d0cb..2e21693 100644
--- a/tools/gn/ninja_copy_target_writer_unittest.cc
+++ b/tools/gn/ninja_copy_target_writer_unittest.cc
@@ -7,11 +7,13 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/ninja_copy_target_writer.h"
+#include "tools/gn/target.h"
#include "tools/gn/test_with_scope.h"
// Tests mutliple files with an output pattern and no toolchain dependency.
TEST(NinjaCopyTargetWriter, Run) {
TestWithScope setup;
+
setup.settings()->set_target_os(Settings::LINUX);
setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
@@ -23,8 +25,11 @@ TEST(NinjaCopyTargetWriter, Run) {
target.action_values().outputs() =
SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
std::ostringstream out;
- NinjaCopyTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaCopyTargetWriter writer(&target, out);
writer.Run();
const char expected_linux[] =
@@ -39,6 +44,7 @@ TEST(NinjaCopyTargetWriter, Run) {
// Tests a single file with no output pattern.
TEST(NinjaCopyTargetWriter, ToolchainDeps) {
TestWithScope setup;
+
setup.settings()->set_target_os(Settings::LINUX);
setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
@@ -49,8 +55,11 @@ TEST(NinjaCopyTargetWriter, ToolchainDeps) {
target.action_values().outputs() =
SubstitutionList::MakeForTest("//out/Debug/output.out");
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
std::ostringstream out;
- NinjaCopyTargetWriter writer(&target, setup.toolchain(), out);
+ NinjaCopyTargetWriter writer(&target, out);
writer.Run();
const char expected_linux[] =
diff --git a/tools/gn/ninja_group_target_writer.cc b/tools/gn/ninja_group_target_writer.cc
index d29f917..6a02cf1 100644
--- a/tools/gn/ninja_group_target_writer.cc
+++ b/tools/gn/ninja_group_target_writer.cc
@@ -5,12 +5,13 @@
#include "tools/gn/ninja_group_target_writer.h"
#include "base/strings/string_util.h"
+#include "tools/gn/output_file.h"
#include "tools/gn/string_utils.h"
+#include "tools/gn/target.h"
NinjaGroupTargetWriter::NinjaGroupTargetWriter(const Target* target,
- const Toolchain* toolchain,
std::ostream& out)
- : NinjaTargetWriter(target, toolchain, out) {
+ : NinjaTargetWriter(target, out) {
}
NinjaGroupTargetWriter::~NinjaGroupTargetWriter() {
@@ -18,23 +19,16 @@ NinjaGroupTargetWriter::~NinjaGroupTargetWriter() {
void NinjaGroupTargetWriter::Run() {
// A group rule just generates a stamp file with dependencies on each of
- // the deps in the group.
- out_ << std::endl << "build ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
- out_ << ": "
- << helper_.GetRulePrefix(target_->settings())
- << "stamp";
-
+ // the deps and datadeps in the group.
+ std::vector<OutputFile> output_files;
const LabelTargetVector& deps = target_->deps();
- for (size_t i = 0; i < deps.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(deps[i].ptr));
- }
+ for (size_t i = 0; i < deps.size(); i++)
+ output_files.push_back(deps[i].ptr->dependency_output_file());
+ std::vector<OutputFile> data_output_files;
const LabelTargetVector& datadeps = target_->datadeps();
- for (size_t i = 0; i < datadeps.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(datadeps[i].ptr));
- }
- out_ << std::endl;
+ for (size_t i = 0; i < datadeps.size(); i++)
+ data_output_files.push_back(deps[i].ptr->dependency_output_file());
+
+ WriteStampForTarget(output_files, data_output_files);
}
diff --git a/tools/gn/ninja_group_target_writer.h b/tools/gn/ninja_group_target_writer.h
index 862b920..31625f8 100644
--- a/tools/gn/ninja_group_target_writer.h
+++ b/tools/gn/ninja_group_target_writer.h
@@ -11,9 +11,7 @@
// Writes a .ninja file for a group target type.
class NinjaGroupTargetWriter : public NinjaTargetWriter {
public:
- NinjaGroupTargetWriter(const Target* target,
- const Toolchain* toolchain,
- std::ostream& out);
+ NinjaGroupTargetWriter(const Target* target, std::ostream& out);
virtual ~NinjaGroupTargetWriter();
virtual void Run() OVERRIDE;
diff --git a/tools/gn/ninja_helper.cc b/tools/gn/ninja_helper.cc
deleted file mode 100644
index 956e74c..0000000
--- a/tools/gn/ninja_helper.cc
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "tools/gn/ninja_helper.h"
-
-#include "base/logging.h"
-#include "base/strings/string_util.h"
-#include "tools/gn/filesystem_utils.h"
-#include "tools/gn/string_utils.h"
-#include "tools/gn/target.h"
-
-namespace {
-
-const char kObjectDirNoSlash[] = "obj";
-
-} // namespace
-
-NinjaHelper::NinjaHelper(const BuildSettings* build_settings)
- : build_settings_(build_settings) {
- build_to_src_no_last_slash_ = build_settings->build_to_source_dir_string();
- if (!build_to_src_no_last_slash_.empty() &&
- build_to_src_no_last_slash_[build_to_src_no_last_slash_.size() - 1] ==
- '/')
- build_to_src_no_last_slash_.resize(build_to_src_no_last_slash_.size() - 1);
-
- build_to_src_system_no_last_slash_ = build_to_src_no_last_slash_;
-}
-
-NinjaHelper::~NinjaHelper() {
-}
-
-std::string NinjaHelper::GetTopleveOutputDir() const {
- return kObjectDirNoSlash;
-}
-
-OutputFile NinjaHelper::GetTargetOutputDir(const Target* target) const {
- OutputFile ret(target->settings()->toolchain_output_subdir());
- ret.value().append(kObjectDirNoSlash);
- AppendStringPiece(&ret.value(),
- target->label().dir().SourceAbsoluteWithOneSlash());
- return ret;
-}
-
-OutputFile NinjaHelper::GetNinjaFileForTarget(const Target* target) const {
- OutputFile ret = GetTargetOutputDir(target);
- ret.value().append(target->label().name());
- ret.value().append(".ninja");
- return ret;
-}
-
-OutputFile NinjaHelper::GetNinjaFileForToolchain(
- const Settings* settings) const {
- OutputFile ret;
- ret.value().append(settings->toolchain_output_subdir().value());
- ret.value().append("toolchain.ninja");
- return ret;
-}
-
-// In Python, GypPathToUniqueOutput does the qualification. The only case where
-// the Python version doesn't qualify the name is for target outputs, which we
-// handle in a separate function.
-OutputFile NinjaHelper::GetOutputFileForSource(
- const Target* target,
- const SourceFile& source,
- SourceFileType type) const {
- // Extract the filename and remove the extension (keep the dot).
- base::StringPiece filename = FindFilename(&source.value());
- std::string name(filename.data(), filename.size());
- size_t extension_offset = FindExtensionOffset(name);
- CHECK(extension_offset != std::string::npos);
- name.resize(extension_offset);
-
- // Append the new extension.
- switch (type) {
- case SOURCE_ASM:
- case SOURCE_C:
- case SOURCE_CC:
- case SOURCE_M:
- case SOURCE_MM:
- case SOURCE_S:
- name.append(target->settings()->IsWin() ? "obj" : "o");
- break;
-
- case SOURCE_RC:
- name.append("res");
- break;
-
- // Pass .o/.obj files through unchanged.
- case SOURCE_O: {
- // System-absolute file names get preserved (they don't need to be
- // rebased relative to the build dir).
- if (source.is_system_absolute())
- return OutputFile(source.value());
-
- // Files that are already inside the build dir should not be made
- // relative to the source tree. Doing so will insert an unnecessary
- // "../.." into the path which won't match the corresponding target
- // name in ninja.
- CHECK(build_settings_->build_dir().is_source_absolute());
- CHECK(source.is_source_absolute());
- if (StartsWithASCII(source.value(),
- build_settings_->build_dir().value(),
- true)) {
- return OutputFile(
- source.value().substr(
- build_settings_->build_dir().value().size()));
- }
-
- // Construct the relative location of the file from the build dir.
- OutputFile ret(build_to_src_no_last_slash());
- source.SourceAbsoluteWithOneSlash().AppendToString(&ret.value());
- return ret;
- }
-
- case SOURCE_H:
- case SOURCE_UNKNOWN:
- NOTREACHED();
- return OutputFile();
- }
-
- // Use the scheme <path>/<target>.<name>.<extension> so that all output
- // names are unique to different targets.
-
- // This will look like "obj" or "toolchain_name/obj".
- OutputFile ret(target->settings()->toolchain_output_subdir());
- ret.value().append(kObjectDirNoSlash);
-
- // Find the directory, assume it starts with two slashes, and trim to one.
- base::StringPiece dir = FindDir(&source.value());
- CHECK(dir.size() >= 2 && dir[0] == '/' && dir[1] == '/')
- << "Source file isn't in the source repo: " << dir;
- AppendStringPiece(&ret.value(), dir.substr(1));
-
- ret.value().append(target->label().name());
- ret.value().append(".");
- ret.value().append(name);
- return ret;
-}
-
-OutputFile NinjaHelper::GetTargetOutputFile(const Target* target) const {
- OutputFile ret;
-
- // Use the output name if given, fall back to target name if not.
- const std::string& name = target->output_name().empty() ?
- target->label().name() : target->output_name();
-
- // This is prepended to the output file name. Some platforms get "lib"
- // prepended to library names. but be careful not to make a duplicate (e.g.
- // some targets like "libxml" already have the "lib" in the name).
- const char* prefix;
- if (!target->settings()->IsWin() &&
- (target->output_type() == Target::SHARED_LIBRARY ||
- target->output_type() == Target::STATIC_LIBRARY) &&
- name.compare(0, 3, "lib") != 0)
- prefix = "lib";
- else
- prefix = "";
-
- const char* extension;
- if (target->output_extension().empty()) {
- if (target->output_type() == Target::GROUP ||
- target->output_type() == Target::SOURCE_SET ||
- target->output_type() == Target::COPY_FILES ||
- target->output_type() == Target::ACTION ||
- target->output_type() == Target::ACTION_FOREACH) {
- extension = "stamp";
- } else {
- extension = GetExtensionForOutputType(target->output_type(),
- target->settings()->target_os());
- }
- } else {
- extension = target->output_extension().c_str();
- }
-
- // Everything goes into the toolchain directory (which will be empty for the
- // default toolchain, and will end in a slash otherwise).
- ret.value().append(target->settings()->toolchain_output_subdir().value());
-
- // Binaries and shared libraries go into the toolchain root.
- if (target->output_type() == Target::EXECUTABLE ||
- target->output_type() == Target::SHARED_LIBRARY) {
- // Generate a name like "<toolchain>/<prefix><name>.<extension>".
- ret.value().append(prefix);
- ret.value().append(name);
- if (extension[0]) {
- ret.value().push_back('.');
- ret.value().append(extension);
- }
- return ret;
- }
-
- // Everything else goes next to the target's .ninja file like
- // "<toolchain>/obj/<path>/<name>.<extension>".
- ret.value().append(kObjectDirNoSlash);
- AppendStringPiece(&ret.value(),
- target->label().dir().SourceAbsoluteWithOneSlash());
- ret.value().append(prefix);
- ret.value().append(name);
- if (extension[0]) {
- ret.value().push_back('.');
- ret.value().append(extension);
- }
- return ret;
-}
-
-std::string NinjaHelper::GetRulePrefix(const Settings* settings) const {
- // Don't prefix the default toolchain so it looks prettier, prefix everything
- // else.
- if (settings->is_default())
- return std::string(); // Default toolchain has no prefix.
- return settings->toolchain_label().name() + "_";
-}
-
-std::string NinjaHelper::GetRuleForSourceType(const Settings* settings,
- SourceFileType type) const {
- // This function may be hot since it will be called for every source file
- // in the tree. We could cache the results to avoid making a string for
- // every invocation.
- std::string prefix = GetRulePrefix(settings);
-
- if (type == SOURCE_C)
- return prefix + "cc";
- if (type == SOURCE_CC)
- return prefix + "cxx";
- if (type == SOURCE_M)
- return prefix + "objc";
- if (type == SOURCE_MM)
- return prefix + "objcxx";
- if (type == SOURCE_RC)
- return prefix + "rc";
- if (type == SOURCE_S)
- return prefix + "cc"; // Assembly files just get compiled by CC.
-
- // TODO(brettw) asm files.
-
- // .obj files have no rules to make them (they're already built) so we return
- // the enpty string for SOURCE_O.
- return std::string();
-}
diff --git a/tools/gn/ninja_helper.h b/tools/gn/ninja_helper.h
deleted file mode 100644
index 69a9564..0000000
--- a/tools/gn/ninja_helper.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef TOOLS_GN_NINJA_HELPER_H_
-#define TOOLS_GN_NINJA_HELPER_H_
-
-#include <iosfwd>
-#include <string>
-
-#include "tools/gn/filesystem_utils.h"
-#include "tools/gn/output_file.h"
-#include "tools/gn/target.h"
-
-class BuildSettings;
-class SourceDir;
-class SourceFile;
-class Target;
-
-// NinjaHelper -----------------------------------------------------------------
-
-class NinjaHelper {
- public:
- NinjaHelper(const BuildSettings* build_settings);
- ~NinjaHelper();
-
- // Ends in a slash.
- std::string GetTopleveOutputDir() const;
-
- // Ends in a slash.
- OutputFile GetTargetOutputDir(const Target* target) const;
-
- // Example: "base/base.ninja". The string version will not be escaped, and
- // will always have slashes for path separators.
- OutputFile GetNinjaFileForTarget(const Target* target) const;
-
- // Returns the name of the root .ninja file for the given toolchain.
- OutputFile GetNinjaFileForToolchain(const Settings* settings) const;
-
- // Given a source file relative to the source root, returns the output
- // filename.
- OutputFile GetOutputFileForSource(const Target* target,
- const SourceFile& source,
- SourceFileType type) const;
-
- // Returns the filename produced by the given output.
- //
- // Some targets make multiple files (like a .dll and an import library). This
- // function returns the name of the file other targets should depend on and
- // link to (so in this example, the import library).
- OutputFile GetTargetOutputFile(const Target* target) const;
-
- // Returns the prefix for rules on the given toolchain. We need this to
- // disambiguate a given rule for each toolchain.
- std::string GetRulePrefix(const Settings* settings) const;
-
- // Returns the name of the rule name for the given toolchain and file/target
- // type. Returns the empty string for source files with no command.
- std::string GetRuleForSourceType(const Settings* settings,
- SourceFileType type) const;
-
- // Returns the relative directory in either slashes or the system separator
- // from the ninja directory (e.g. "out/Debug") to the source root (e.g.
- // "../.."). It has no terminating slash.
- const std::string& build_to_src_no_last_slash() const {
- return build_to_src_no_last_slash_;
- }
- const std::string& build_to_src_system_no_last_slash() const {
- return build_to_src_system_no_last_slash_;
- }
-
- private:
- const BuildSettings* build_settings_;
-
- std::string build_to_src_no_last_slash_;
- std::string build_to_src_system_no_last_slash_;
-
- DISALLOW_COPY_AND_ASSIGN(NinjaHelper);
-};
-
-#endif // TOOLS_GN_NINJA_HELPER_H_
diff --git a/tools/gn/ninja_helper_unittest.cc b/tools/gn/ninja_helper_unittest.cc
deleted file mode 100644
index 3df7a13..0000000
--- a/tools/gn/ninja_helper_unittest.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-#include "tools/gn/build_settings.h"
-#include "tools/gn/filesystem_utils.h"
-#include "tools/gn/ninja_helper.h"
-#include "tools/gn/settings.h"
-#include "tools/gn/target.h"
-#include "tools/gn/toolchain.h"
-
-namespace {
-
-class HelperSetterUpper {
- public:
- HelperSetterUpper()
- : build_settings(),
- settings(&build_settings, std::string()),
- toolchain(&settings, Label(SourceDir("//"), "tc")),
- target(&settings, Label(SourceDir("//tools/gn/"), "name")) {
- settings.set_toolchain_label(toolchain.label());
- settings.set_target_os(Settings::WIN);
-
- // Output going to "out/Debug".
- build_settings.SetBuildDir(SourceDir("//out/Debug/"));
-
- // Our source target is in "tools/gn".
- target.set_output_type(Target::EXECUTABLE);
- }
-
- BuildSettings build_settings;
- Settings settings;
- Toolchain toolchain;
- Target target;
-};
-
-} // namespace
-
-TEST(NinjaHelper, GetNinjaFileForTarget) {
- HelperSetterUpper setup;
- NinjaHelper helper(&setup.build_settings);
-
- // Default toolchain.
- EXPECT_EQ(OutputFile("obj/tools/gn/name.ninja").value(),
- helper.GetNinjaFileForTarget(&setup.target).value());
-}
-
-TEST(NinjaHelper, GetOutputFileForSource) {
- HelperSetterUpper setup;
- NinjaHelper helper(&setup.build_settings);
-
- // On Windows, expect ".obj"
- EXPECT_EQ(OutputFile("obj/tools/gn/name.foo.obj").value(),
- helper.GetOutputFileForSource(&setup.target,
- SourceFile("//tools/gn/foo.cc"),
- SOURCE_CC).value());
-}
-
-TEST(NinjaHelper, GetOutputFileForObject) {
- HelperSetterUpper setup;
- NinjaHelper helper(&setup.build_settings);
-
- EXPECT_EQ(OutputFile("../../tools/gn/foo.o").value(),
- helper.GetOutputFileForSource(&setup.target,
- SourceFile("//tools/gn/foo.o"),
- SOURCE_O).value());
-
- EXPECT_EQ(OutputFile("../../tools/gn/foo.obj").value(),
- helper.GetOutputFileForSource(&setup.target,
- SourceFile("//tools/gn/foo.obj"),
- SOURCE_O).value());
-
- EXPECT_EQ(OutputFile("nested/foo.o").value(),
- helper.GetOutputFileForSource(
- &setup.target,
- SourceFile("//out/Debug/nested/foo.o"),
- SOURCE_O).value());
-
- EXPECT_EQ(OutputFile("/abs/rooted/foo.o").value(),
- helper.GetOutputFileForSource(&setup.target,
- SourceFile("/abs/rooted/foo.o"),
- SOURCE_O).value());
-}
-
-TEST(NinjaHelper, GetTargetOutputFile) {
- HelperSetterUpper setup;
- NinjaHelper helper(&setup.build_settings);
- EXPECT_EQ(OutputFile("name.exe"),
- helper.GetTargetOutputFile(&setup.target));
-
- // Static library on Windows goes alongside the object files.
- setup.target.set_output_type(Target::STATIC_LIBRARY);
- EXPECT_EQ(OutputFile("obj/tools/gn/name.lib"),
- helper.GetTargetOutputFile(&setup.target));
-
- // TODO(brettw) test output to library and other OS types.
-}
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index ade81a0..5ff6e0f 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -8,43 +8,41 @@
#include <sstream>
#include "base/file_util.h"
+#include "base/strings/string_util.h"
#include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
#include "tools/gn/ninja_action_target_writer.h"
#include "tools/gn/ninja_binary_target_writer.h"
#include "tools/gn/ninja_copy_target_writer.h"
#include "tools/gn/ninja_group_target_writer.h"
+#include "tools/gn/ninja_utils.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/string_utils.h"
+#include "tools/gn/substitution_writer.h"
#include "tools/gn/target.h"
#include "tools/gn/trace.h"
NinjaTargetWriter::NinjaTargetWriter(const Target* target,
- const Toolchain* toolchain,
std::ostream& out)
: settings_(target->settings()),
target_(target),
- toolchain_(toolchain),
out_(out),
- path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA),
- helper_(settings_->build_settings()) {
+ path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA) {
}
NinjaTargetWriter::~NinjaTargetWriter() {
}
// static
-void NinjaTargetWriter::RunAndWriteFile(const Target* target,
- const Toolchain* toolchain) {
+void NinjaTargetWriter::RunAndWriteFile(const Target* target) {
const Settings* settings = target->settings();
- NinjaHelper helper(settings->build_settings());
ScopedTrace trace(TraceItem::TRACE_FILE_WRITE,
target->label().GetUserVisibleName(false));
trace.SetToolchain(settings->toolchain_label());
base::FilePath ninja_file(settings->build_settings()->GetFullPath(
- helper.GetNinjaFileForTarget(target).GetSourceFile(
- settings->build_settings())));
+ GetNinjaFileForTarget(target)));
if (g_scheduler->verbose_logging())
g_scheduler->Log("Writing", FilePathToUTF8(ninja_file));
@@ -57,20 +55,20 @@ void NinjaTargetWriter::RunAndWriteFile(const Target* target,
// Call out to the correct sub-type of writer.
if (target->output_type() == Target::COPY_FILES) {
- NinjaCopyTargetWriter writer(target, toolchain, file);
+ NinjaCopyTargetWriter writer(target, file);
writer.Run();
} else if (target->output_type() == Target::ACTION ||
target->output_type() == Target::ACTION_FOREACH) {
- NinjaActionTargetWriter writer(target, toolchain, file);
+ NinjaActionTargetWriter writer(target, file);
writer.Run();
} else if (target->output_type() == Target::GROUP) {
- NinjaGroupTargetWriter writer(target, toolchain, file);
+ NinjaGroupTargetWriter writer(target, file);
writer.Run();
} else if (target->output_type() == Target::EXECUTABLE ||
target->output_type() == Target::STATIC_LIBRARY ||
target->output_type() == Target::SHARED_LIBRARY ||
target->output_type() == Target::SOURCE_SET) {
- NinjaBinaryTargetWriter writer(target, toolchain, file);
+ NinjaBinaryTargetWriter writer(target, file);
writer.Run();
} else {
CHECK(0);
@@ -81,8 +79,75 @@ void NinjaTargetWriter::RunAndWriteFile(const Target* target,
static_cast<int>(contents.size()));
}
+void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) {
+ bool written_anything = false;
+
+ // Target label.
+ if (bits.used[SUBSTITUTION_LABEL]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_LABEL] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_LABEL)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // Root gen dir.
+ if (bits.used[SUBSTITUTION_ROOT_GEN_DIR]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_ROOT_GEN_DIR] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_ROOT_GEN_DIR)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // Root out dir.
+ if (bits.used[SUBSTITUTION_ROOT_OUT_DIR]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_ROOT_OUT_DIR] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_ROOT_OUT_DIR)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // Target gen dir.
+ if (bits.used[SUBSTITUTION_TARGET_GEN_DIR]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_TARGET_GEN_DIR] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_TARGET_GEN_DIR)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // Target out dir.
+ if (bits.used[SUBSTITUTION_TARGET_OUT_DIR]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_TARGET_OUT_DIR] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_TARGET_OUT_DIR)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // Target output name.
+ if (bits.used[SUBSTITUTION_TARGET_OUTPUT_NAME]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_TARGET_OUTPUT_NAME] << " = "
+ << SubstitutionWriter::GetTargetSubstitution(
+ target_, SUBSTITUTION_TARGET_OUTPUT_NAME)
+ << std::endl;
+ written_anything = true;
+ }
+
+ // If we wrote any vars, separate them from the rest of the file that follows
+ // with a blank line.
+ if (written_anything)
+ out_ << std::endl;
+}
+
std::string NinjaTargetWriter::WriteInputDepsStampAndGetDep(
const std::vector<const Target*>& extra_hard_deps) const {
+ CHECK(target_->toolchain())
+ << "Toolchain not set on target "
+ << target_->label().GetUserVisibleName(true);
+
// For an action (where we run a script only once) the sources are the same
// as the source prereqs.
bool list_sources_as_input_deps = (target_->output_type() == Target::ACTION);
@@ -97,7 +162,7 @@ std::string NinjaTargetWriter::WriteInputDepsStampAndGetDep(
target_->inputs().empty() &&
target_->recursive_hard_deps().empty() &&
(!list_sources_as_input_deps || target_->sources().empty()) &&
- toolchain_->deps().empty())
+ target_->toolchain()->deps().empty())
return std::string(); // No input/hard deps.
// One potential optimization is if there are few input dependencies (or
@@ -107,7 +172,9 @@ std::string NinjaTargetWriter::WriteInputDepsStampAndGetDep(
// source file can really explode the ninja file but this won't be the most
// optimal thing in all cases.
- OutputFile input_stamp_file = helper_.GetTargetOutputDir(target_);
+ OutputFile input_stamp_file(
+ RebaseSourceAbsolutePath(GetTargetOutputDir(target_).value(),
+ settings_->build_settings()->build_dir()));
input_stamp_file.value().append(target_->label().name());
input_stamp_file.value().append(".inputdeps.stamp");
@@ -115,8 +182,9 @@ std::string NinjaTargetWriter::WriteInputDepsStampAndGetDep(
path_output_.WriteFile(stamp_file_stream, input_stamp_file);
std::string stamp_file_string = stamp_file_stream.str();
- out_ << "build " << stamp_file_string << ": " +
- helper_.GetRulePrefix(settings_) + "stamp";
+ out_ << "build " << stamp_file_string << ": "
+ << GetNinjaRulePrefixForToolchain(settings_)
+ << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP);
// Script file (if applicable).
if (add_script_source_as_dep) {
@@ -138,32 +206,61 @@ std::string NinjaTargetWriter::WriteInputDepsStampAndGetDep(
}
}
- // Add on any hard deps that are direct or indirect dependencies.
+ // The different souces of input deps may duplicate some targets, so uniquify
+ // them (ordering doesn't matter for this case).
+ std::set<const Target*> unique_deps;
+
+ // Hard dependencies that are direct or indirect dependencies.
const std::set<const Target*>& hard_deps = target_->recursive_hard_deps();
for (std::set<const Target*>::const_iterator i = hard_deps.begin();
i != hard_deps.end(); ++i) {
- out_ << " ";
- path_output_.WriteFile(out_, helper_.GetTargetOutputFile(*i));
+ unique_deps.insert(*i);
}
+ // Extra hard dependencies passed in.
+ unique_deps.insert(extra_hard_deps.begin(), extra_hard_deps.end());
+
// Toolchain dependencies. These must be resolved before doing anything.
// This just writs all toolchain deps for simplicity. If we find that
// toolchains often have more than one dependency, we could consider writing
// a toolchain-specific stamp file and only include the stamp here.
- const LabelTargetVector& toolchain_deps = toolchain_->deps();
- for (size_t i = 0; i < toolchain_deps.size(); i++) {
- out_ << " ";
- path_output_.WriteFile(out_,
- helper_.GetTargetOutputFile(toolchain_deps[i].ptr));
- }
+ const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
+ for (size_t i = 0; i < toolchain_deps.size(); i++)
+ unique_deps.insert(toolchain_deps[i].ptr);
- // Extra hard deps passed in.
- for (size_t i = 0; i < extra_hard_deps.size(); i++) {
+ for (std::set<const Target*>::const_iterator i = unique_deps.begin();
+ i != unique_deps.end(); ++i) {
+ DCHECK(!(*i)->dependency_output_file().value().empty());
out_ << " ";
- path_output_.WriteFile(out_,
- helper_.GetTargetOutputFile(extra_hard_deps[i]));
+ path_output_.WriteFile(out_, (*i)->dependency_output_file());
}
out_ << "\n";
return " | " + stamp_file_string;
}
+
+void NinjaTargetWriter::WriteStampForTarget(
+ const std::vector<OutputFile>& files,
+ const std::vector<OutputFile>& order_only_deps) {
+ const OutputFile& stamp_file = target_->dependency_output_file();
+
+ // First validate that the target's dependency is a stamp file. Otherwise,
+ // we shouldn't have gotten here!
+ CHECK(EndsWith(stamp_file.value(), ".stamp", false))
+ << "Output should end in \".stamp\" for stamp file output. Instead got: "
+ << "\"" << stamp_file.value() << "\"";
+
+ out_ << "build ";
+ path_output_.WriteFile(out_, stamp_file);
+
+ out_ << ": "
+ << GetNinjaRulePrefixForToolchain(settings_)
+ << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP);
+ path_output_.WriteFiles(out_, files);
+
+ if (!order_only_deps.empty()) {
+ out_ << " ||";
+ path_output_.WriteFiles(out_, order_only_deps);
+ }
+ out_ << std::endl;
+}
diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h
index 6cd0a0a..e2dbd25 100644
--- a/tools/gn/ninja_target_writer.h
+++ b/tools/gn/ninja_target_writer.h
@@ -8,8 +8,8 @@
#include <iosfwd>
#include "base/basictypes.h"
-#include "tools/gn/ninja_helper.h"
#include "tools/gn/path_output.h"
+#include "tools/gn/substitution_type.h"
class FileTemplate;
class Settings;
@@ -19,16 +19,19 @@ class Target;
// generated by the NinjaBuildWriter.
class NinjaTargetWriter {
public:
- NinjaTargetWriter(const Target* target,
- const Toolchain* toolchain,
- std::ostream& out);
+ NinjaTargetWriter(const Target* target, std::ostream& out);
virtual ~NinjaTargetWriter();
- static void RunAndWriteFile(const Target* target, const Toolchain* toolchain);
+ static void RunAndWriteFile(const Target* target);
virtual void Run() = 0;
protected:
+ // Writes out the substitution values that are shared between the different
+ // types of tools (target gen dir, target label, etc.). Only the substitutions
+ // identified by the given bits will be written.
+ void WriteSharedVars(const SubstitutionBits& bits);
+
// Writes to the output stream a stamp rule for input dependencies, and
// returns the string to be appended to source rules that encodes the
// order-only dependencies for the current target. This will include the "|"
@@ -38,14 +41,17 @@ class NinjaTargetWriter {
std::string WriteInputDepsStampAndGetDep(
const std::vector<const Target*>& extra_hard_deps) const;
+ // Writes to the output file a final stamp rule for the target that stamps
+ // the given list of files. This function assumes the stamp is for the target
+ // as a whole so the stamp file is set as the target's dependency output.
+ void WriteStampForTarget(const std::vector<OutputFile>& deps,
+ const std::vector<OutputFile>& order_only_deps);
+
const Settings* settings_; // Non-owning.
const Target* target_; // Non-owning.
- const Toolchain* toolchain_; // Non-owning.
std::ostream& out_;
PathOutput path_output_;
- NinjaHelper helper_;
-
private:
void WriteCopyRules();
diff --git a/tools/gn/ninja_target_writer_unittest.cc b/tools/gn/ninja_target_writer_unittest.cc
index fe4c9a6..354170a 100644
--- a/tools/gn/ninja_target_writer_unittest.cc
+++ b/tools/gn/ninja_target_writer_unittest.cc
@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/ninja_target_writer.h"
+#include "tools/gn/target.h"
#include "tools/gn/test_with_scope.h"
namespace {
@@ -15,7 +16,7 @@ class TestingNinjaTargetWriter : public NinjaTargetWriter {
TestingNinjaTargetWriter(const Target* target,
const Toolchain* toolchain,
std::ostream& out)
- : NinjaTargetWriter(target, toolchain, out) {
+ : NinjaTargetWriter(target, out) {
}
virtual void Run() OVERRIDE {}
@@ -35,12 +36,14 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) {
// Make a base target that's a hard dep (action).
Target base_target(setup.settings(), Label(SourceDir("//foo/"), "base"));
base_target.set_output_type(Target::ACTION);
+ base_target.SetToolchain(setup.toolchain());
base_target.action_values().set_script(SourceFile("//foo/script.py"));
// Dependent target that also includes a source prerequisite (should get
// included) and a source (should not be included).
Target target(setup.settings(), Label(SourceDir("//foo/"), "target"));
target.set_output_type(Target::EXECUTABLE);
+ target.SetToolchain(setup.toolchain());
target.inputs().push_back(SourceFile("//foo/input.txt"));
target.sources().push_back(SourceFile("//foo/source.txt"));
target.deps().push_back(LabelTargetPair(&base_target));
@@ -49,6 +52,7 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) {
// inputs.
Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
action.set_output_type(Target::ACTION);
+ action.SetToolchain(setup.toolchain());
action.action_values().set_script(SourceFile("//foo/script.py"));
action.sources().push_back(SourceFile("//foo/action_source.txt"));
action.deps().push_back(LabelTargetPair(&target));
@@ -109,11 +113,15 @@ TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) {
Target toolchain_dep_target(setup.settings(),
Label(SourceDir("//foo/"), "setup"));
toolchain_dep_target.set_output_type(Target::ACTION);
+ toolchain_dep_target.SetToolchain(setup.toolchain());
+ toolchain_dep_target.OnResolved();
setup.toolchain()->deps().push_back(LabelTargetPair(&toolchain_dep_target));
// Make a binary target
Target target(setup.settings(), Label(SourceDir("//foo/"), "target"));
target.set_output_type(Target::EXECUTABLE);
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
std::ostringstream stream;
TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
diff --git a/tools/gn/ninja_toolchain_writer.cc b/tools/gn/ninja_toolchain_writer.cc
index dac5921..ae2b6c9 100644
--- a/tools/gn/ninja_toolchain_writer.cc
+++ b/tools/gn/ninja_toolchain_writer.cc
@@ -9,11 +9,20 @@
#include "base/file_util.h"
#include "base/strings/stringize_macros.h"
#include "tools/gn/build_settings.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/ninja_utils.h"
#include "tools/gn/settings.h"
+#include "tools/gn/substitution_writer.h"
#include "tools/gn/target.h"
#include "tools/gn/toolchain.h"
#include "tools/gn/trace.h"
+namespace {
+
+const char kIndent[] = " ";
+
+} // namespace
+
NinjaToolchainWriter::NinjaToolchainWriter(
const Settings* settings,
const Toolchain* toolchain,
@@ -23,8 +32,7 @@ NinjaToolchainWriter::NinjaToolchainWriter(
toolchain_(toolchain),
targets_(targets),
out_(out),
- path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA),
- helper_(settings->build_settings()) {
+ path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA) {
}
NinjaToolchainWriter::~NinjaToolchainWriter() {
@@ -40,10 +48,8 @@ bool NinjaToolchainWriter::RunAndWriteFile(
const Settings* settings,
const Toolchain* toolchain,
const std::vector<const Target*>& targets) {
- NinjaHelper helper(settings->build_settings());
base::FilePath ninja_file(settings->build_settings()->GetFullPath(
- helper.GetNinjaFileForToolchain(settings).GetSourceFile(
- settings->build_settings())));
+ GetNinjaFileForToolchain(settings)));
ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, FilePathToUTF8(ninja_file));
base::CreateDirectory(ninja_file.DirName());
@@ -60,43 +66,66 @@ bool NinjaToolchainWriter::RunAndWriteFile(
}
void NinjaToolchainWriter::WriteRules() {
- std::string indent(" ");
-
- NinjaHelper helper(settings_->build_settings());
- std::string rule_prefix = helper.GetRulePrefix(settings_);
+ std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_);
for (int i = Toolchain::TYPE_NONE + 1; i < Toolchain::TYPE_NUMTYPES; i++) {
Toolchain::ToolType tool_type = static_cast<Toolchain::ToolType>(i);
- const Toolchain::Tool& tool = toolchain_->GetTool(tool_type);
- if (tool.command.empty())
- continue;
-
- out_ << "rule " << rule_prefix << Toolchain::ToolTypeToName(tool_type)
- << std::endl;
-
- #define WRITE_ARG(name) \
- if (!tool.name.empty()) \
- out_ << indent << " " STRINGIZE(name) " = " << tool.name << std::endl;
- WRITE_ARG(command);
- WRITE_ARG(depfile);
- WRITE_ARG(description);
- WRITE_ARG(pool);
- WRITE_ARG(restat);
- WRITE_ARG(rspfile);
- WRITE_ARG(rspfile_content);
- #undef WRITE_ARG
-
- // Deps is called "depsformat" in GN to avoid confusion with dependencies.
- if (!tool.depsformat.empty()) \
- out_ << indent << " deps = " << tool.depsformat << std::endl;
+ const Tool* tool = toolchain_->GetTool(tool_type);
+ if (tool)
+ WriteToolRule(tool_type, tool, rule_prefix);
}
out_ << std::endl;
}
+void NinjaToolchainWriter::WriteToolRule(const Toolchain::ToolType type,
+ const Tool* tool,
+ const std::string& rule_prefix) {
+ out_ << "rule " << rule_prefix << Toolchain::ToolTypeToName(type)
+ << std::endl;
+
+ // Rules explicitly include shell commands, so don't try to escape.
+ EscapeOptions options;
+ options.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+
+ CHECK(!tool->command().empty()) << "Command should not be empty";
+ WriteRulePattern("command", tool->command(), options);
+
+ WriteRulePattern("description", tool->description(), options);
+ WriteRulePattern("rspfile", tool->rspfile(), options);
+ WriteRulePattern("rspfile_content", tool->rspfile_content(), options);
+
+ if (tool->depsformat() == Tool::DEPS_GCC) {
+ // GCC-style deps require a depfile.
+ if (!tool->depfile().empty()) {
+ WriteRulePattern("depfile", tool->depfile(), options);
+ out_ << kIndent << "deps = gcc" << std::endl;
+ }
+ } else if (tool->depsformat() == Tool::DEPS_MSVC) {
+ // MSVC deps don't have a depfile.
+ out_ << kIndent << "deps = msvc" << std::endl;
+ }
+
+ if (!tool->pool().empty())
+ out_ << kIndent << "pool = " << tool->pool() << std::endl;
+ if (tool->restat())
+ out_ << kIndent << "restat = 1" << std::endl;
+}
+
+void NinjaToolchainWriter::WriteRulePattern(const char* name,
+ const SubstitutionPattern& pattern,
+ const EscapeOptions& options) {
+ if (pattern.empty())
+ return;
+ out_ << kIndent << name << " = ";
+ SubstitutionWriter::WriteWithNinjaVariables(pattern, options, out_);
+ out_ << std::endl;
+}
+
void NinjaToolchainWriter::WriteSubninjas() {
// Write subninja commands for each generated target.
for (size_t i = 0; i < targets_.size(); i++) {
- OutputFile ninja_file = helper_.GetNinjaFileForTarget(targets_[i]);
+ OutputFile ninja_file(targets_[i]->settings()->build_settings(),
+ GetNinjaFileForTarget(targets_[i]));
out_ << "subninja ";
path_output_.WriteFile(out_, ninja_file);
out_ << std::endl;
diff --git a/tools/gn/ninja_toolchain_writer.h b/tools/gn/ninja_toolchain_writer.h
index e3e26b0..50cb7ef 100644
--- a/tools/gn/ninja_toolchain_writer.h
+++ b/tools/gn/ninja_toolchain_writer.h
@@ -10,13 +10,15 @@
#include <string>
#include <vector>
-#include "tools/gn/ninja_helper.h"
+#include "base/gtest_prod_util.h"
#include "tools/gn/path_output.h"
+#include "tools/gn/toolchain.h"
class BuildSettings;
+struct EscapeOptions;
class Settings;
class Target;
-class Toolchain;
+class Tool;
class NinjaToolchainWriter {
public:
@@ -27,6 +29,8 @@ class NinjaToolchainWriter {
const std::vector<const Target*>& targets);
private:
+ FRIEND_TEST_ALL_PREFIXES(NinjaToolchainWriter, WriteToolRule);
+
NinjaToolchainWriter(const Settings* settings,
const Toolchain* toolchain,
const std::vector<const Target*>& targets,
@@ -36,6 +40,12 @@ class NinjaToolchainWriter {
void Run();
void WriteRules();
+ void WriteToolRule(Toolchain::ToolType type,
+ const Tool* tool,
+ const std::string& rule_prefix);
+ void WriteRulePattern(const char* name,
+ const SubstitutionPattern& pattern,
+ const EscapeOptions& options);
void WriteSubninjas();
const Settings* settings_;
@@ -44,8 +54,6 @@ class NinjaToolchainWriter {
std::ostream& out_;
PathOutput path_output_;
- NinjaHelper helper_;
-
DISALLOW_COPY_AND_ASSIGN(NinjaToolchainWriter);
};
diff --git a/tools/gn/ninja_toolchain_writer_unittest.cc b/tools/gn/ninja_toolchain_writer_unittest.cc
new file mode 100644
index 0000000..fc52f7d
--- /dev/null
+++ b/tools/gn/ninja_toolchain_writer_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/ninja_toolchain_writer.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(NinjaToolchainWriter, WriteToolRule) {
+ TestWithScope setup;
+
+ //Target target(setup.settings(), Label(SourceDir("//foo/"), "target"));
+ //target.set_output_type(Target::EXECUTABLE);
+ //target.SetToolchain(setup.toolchain());
+
+ std::ostringstream stream;
+
+ NinjaToolchainWriter writer(setup.settings(), setup.toolchain(),
+ std::vector<const Target*>(), stream);
+ writer.WriteToolRule(Toolchain::TYPE_CC,
+ setup.toolchain()->GetTool(Toolchain::TYPE_CC),
+ std::string("prefix_"));
+
+ EXPECT_EQ(
+ "rule prefix_cc\n"
+ " command = cc ${in} ${cflags} ${cflags_c} ${defines} ${include_dirs} "
+ "-o ${out}\n",
+ stream.str());
+}
diff --git a/tools/gn/ninja_utils.cc b/tools/gn/ninja_utils.cc
new file mode 100644
index 0000000..60d4d36
--- /dev/null
+++ b/tools/gn/ninja_utils.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/ninja_utils.h"
+
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/settings.h"
+#include "tools/gn/target.h"
+
+SourceFile GetNinjaFileForTarget(const Target* target) {
+ return SourceFile(GetTargetOutputDir(target).value() +
+ target->label().name() + ".ninja");
+}
+
+SourceFile GetNinjaFileForToolchain(const Settings* settings) {
+ return SourceFile(GetToolchainOutputDir(settings).value() +
+ "toolchain.ninja");
+}
+
+std::string GetNinjaRulePrefixForToolchain(const Settings* settings) {
+ // Don't prefix the default toolchain so it looks prettier, prefix everything
+ // else.
+ if (settings->is_default())
+ return std::string(); // Default toolchain has no prefix.
+ return settings->toolchain_label().name() + "_";
+}
diff --git a/tools/gn/ninja_utils.h b/tools/gn/ninja_utils.h
new file mode 100644
index 0000000..60ae6b2
--- /dev/null
+++ b/tools/gn/ninja_utils.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_UTILS_H_
+#define TOOLS_GN_NINJA_UTILS_H_
+
+#include <string>
+
+class Settings;
+class SourceFile;
+class Target;
+
+// Example: "base/base.ninja". The string version will not be escaped, and
+// will always have slashes for path separators.
+SourceFile GetNinjaFileForTarget(const Target* target);
+
+// Returns the name of the root .ninja file for the given toolchain.
+SourceFile GetNinjaFileForToolchain(const Settings* settings);
+
+// Returns the prefix applied to the Ninja rules in a given toolchain so they
+// don't collide with rules from other toolchains.
+std::string GetNinjaRulePrefixForToolchain(const Settings* settings);
+
+#endif // TOOLS_GN_NINJA_UTILS_H_
diff --git a/tools/gn/output_file.cc b/tools/gn/output_file.cc
new file mode 100644
index 0000000..d1fb88e
--- /dev/null
+++ b/tools/gn/output_file.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/output_file.h"
+
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/source_file.h"
+
+OutputFile::OutputFile() : value_() {
+}
+
+OutputFile::OutputFile(const base::StringPiece& str)
+ : value_(str.data(), str.size()) {
+}
+
+OutputFile::OutputFile(const BuildSettings* build_settings,
+ const SourceFile& source_file)
+ : value_(RebaseSourceAbsolutePath(source_file.value(),
+ build_settings->build_dir())) {
+}
+
+OutputFile::~OutputFile() {
+}
+
+SourceFile OutputFile::AsSourceFile(const BuildSettings* build_settings) const {
+ DCHECK(!value_.empty());
+ DCHECK(value_[value_.size() - 1] != '/');
+ return SourceFile(build_settings->build_dir().value() + value_);
+}
+
+SourceDir OutputFile::AsSourceDir(const BuildSettings* build_settings) const {
+ if (!value_.empty()) {
+ // Empty means the root build dir. Otherwise, we expect it to end in a
+ // slash.
+ DCHECK(value_[value_.size() - 1] == '/');
+ }
+ return SourceDir(build_settings->build_dir().value() + value_);
+}
diff --git a/tools/gn/output_file.h b/tools/gn/output_file.h
index 252fae4..eb6faf0 100644
--- a/tools/gn/output_file.h
+++ b/tools/gn/output_file.h
@@ -9,24 +9,27 @@
#include "base/containers/hash_tables.h"
#include "tools/gn/build_settings.h"
-#include "tools/gn/source_file.h"
+
+class SourceFile;
// A simple wrapper around a string that indicates the string is a path
// relative to the output directory.
class OutputFile {
public:
- OutputFile() : value_() {}
- explicit OutputFile(const base::StringPiece& str)
- : value_(str.data(), str.size()) {
- }
+ OutputFile();
+ explicit OutputFile(const base::StringPiece& str);
+ OutputFile(const BuildSettings* build_settings,
+ const SourceFile& source_file);
+ ~OutputFile();
std::string& value() { return value_; }
const std::string& value() const { return value_; }
// Converts to a SourceFile by prepending the build directory to the file.
- SourceFile GetSourceFile(const BuildSettings* build_settings) const {
- return SourceFile(build_settings->build_dir().value() + value_);
- }
+ // The *Dir version requires that the current OutputFile ends in a slash, and
+ // the *File version is the opposite.
+ SourceFile AsSourceFile(const BuildSettings* build_settings) const;
+ SourceDir AsSourceDir(const BuildSettings* build_settings) const;
bool operator==(const OutputFile& other) const {
return value_ == other.value_;
diff --git a/tools/gn/path_output.cc b/tools/gn/path_output.cc
index dc142b7..2ef060a 100644
--- a/tools/gn/path_output.cc
+++ b/tools/gn/path_output.cc
@@ -75,6 +75,14 @@ void PathOutput::WriteFile(std::ostream& out, const OutputFile& file) const {
EscapeStringToStream(out, file.value(), options_);
}
+void PathOutput::WriteFiles(std::ostream& out,
+ const std::vector<OutputFile>& files) const {
+ for (size_t i = 0; i < files.size(); i++) {
+ out << " ";
+ WriteFile(out, files[i]);
+ }
+}
+
void PathOutput::WriteDir(std::ostream& out,
const OutputFile& file,
DirSlashEnding slash_ending) const {
diff --git a/tools/gn/path_output.h b/tools/gn/path_output.h
index 681ee2f..12fc5ac 100644
--- a/tools/gn/path_output.h
+++ b/tools/gn/path_output.h
@@ -50,6 +50,11 @@ class PathOutput {
void WriteFile(std::ostream& out, const OutputFile& file) const;
void WriteFile(std::ostream& out, const base::FilePath& file) const;
+ // Writes the given OutputFiles with spaces separating them. This will also
+ // write an initial space before the first item.
+ void WriteFiles(std::ostream& out,
+ const std::vector<OutputFile>& files) const;
+
// This variant assumes the dir ends in a trailing slash or is empty.
void WriteDir(std::ostream& out,
const SourceDir& dir,
diff --git a/tools/gn/source_file_type.cc b/tools/gn/source_file_type.cc
new file mode 100644
index 0000000..b58ecb3
--- /dev/null
+++ b/tools/gn/source_file_type.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/source_file_type.h"
+
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/source_file.h"
+
+SourceFileType GetSourceFileType(const SourceFile& file) {
+ base::StringPiece extension = FindExtension(&file.value());
+ if (extension == "cc" || extension == "cpp" || extension == "cxx")
+ return SOURCE_CC;
+ if (extension == "h")
+ return SOURCE_H;
+ if (extension == "c")
+ return SOURCE_C;
+ if (extension == "m")
+ return SOURCE_M;
+ if (extension == "mm")
+ return SOURCE_MM;
+ if (extension == "rc")
+ return SOURCE_RC;
+ if (extension == "S" || extension == "s")
+ return SOURCE_S;
+ if (extension == "o" || extension == "obj")
+ return SOURCE_O;
+
+ return SOURCE_UNKNOWN;
+}
+
diff --git a/tools/gn/source_file_type.h b/tools/gn/source_file_type.h
new file mode 100644
index 0000000..f8d9a09
--- /dev/null
+++ b/tools/gn/source_file_type.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_SOURCE_FILE_TYPE_H_
+#define TOOLS_GN_SOURCE_FILE_TYPE_H_
+
+class SourceFile;
+
+enum SourceFileType {
+ SOURCE_UNKNOWN,
+ SOURCE_ASM,
+ SOURCE_C,
+ SOURCE_CC,
+ SOURCE_H,
+ SOURCE_M,
+ SOURCE_MM,
+ SOURCE_S,
+ SOURCE_RC,
+ SOURCE_O, // Object files can be inputs, too. Also counts .obj.
+};
+
+SourceFileType GetSourceFileType(const SourceFile& file);
+
+#endif // TOOLS_GN_SOURCE_FILE_TYPE_H_
diff --git a/tools/gn/string_utils.cc b/tools/gn/string_utils.cc
index 60e4e8c..2603c15 100644
--- a/tools/gn/string_utils.cc
+++ b/tools/gn/string_utils.cc
@@ -168,3 +168,10 @@ std::string RemovePrefix(const std::string& str, const std::string& prefix) {
str.compare(0, prefix.size(), prefix) == 0);
return str.substr(prefix.size());
}
+
+void TrimTrailingSlash(std::string* str) {
+ if (!str->empty()) {
+ DCHECK((*str)[str->size() - 1] == '/');
+ str->resize(str->size() - 1);
+ }
+}
diff --git a/tools/gn/string_utils.h b/tools/gn/string_utils.h
index 7fff1d8..a405cc5 100644
--- a/tools/gn/string_utils.h
+++ b/tools/gn/string_utils.h
@@ -48,4 +48,9 @@ inline void AppendStringPiece(std::string* dest,
dest->append(piece.data(), piece.size());
}
+// Removes the trailing slash from the given string. This asserts that either
+// the string is empty or it ends with a slash (normally used to process
+// directories).
+void TrimTrailingSlash(std::string* str);
+
#endif // TOOLS_GN_STRING_UTILS_H_
diff --git a/tools/gn/substitution_list.cc b/tools/gn/substitution_list.cc
index 26b3909..826f2c1 100644
--- a/tools/gn/substitution_list.cc
+++ b/tools/gn/substitution_list.cc
@@ -25,7 +25,9 @@ bool SubstitutionList::Parse(const Value& value, Err* err) {
return false;
}
- FillRequiredTypes();
+ SubstitutionBits bits;
+ FillRequiredTypes(&bits);
+ bits.FillVector(&required_types_);
return true;
}
@@ -38,7 +40,9 @@ bool SubstitutionList::Parse(const std::vector<std::string>& values,
return false;
}
- FillRequiredTypes();
+ SubstitutionBits bits;
+ FillRequiredTypes(&bits);
+ bits.FillVector(&required_types_);
return true;
}
@@ -59,14 +63,7 @@ SubstitutionList SubstitutionList::MakeForTest(
return result;
}
-void SubstitutionList::FillRequiredTypes() {
- bool required_type_bits[SUBSTITUTION_NUM_TYPES];
- memset(&required_type_bits, 0, SUBSTITUTION_NUM_TYPES);
+void SubstitutionList::FillRequiredTypes(SubstitutionBits* bits) const {
for (size_t i = 0; i < list_.size(); i++)
- list_[i].FillRequiredTypes(required_type_bits);
-
- for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES; i++) {
- if (required_type_bits[i])
- required_types_.push_back(static_cast<SubstitutionType>(i));
- }
+ list_[i].FillRequiredTypes(bits);
}
diff --git a/tools/gn/substitution_list.h b/tools/gn/substitution_list.h
index 297ba48..af0a973 100644
--- a/tools/gn/substitution_list.h
+++ b/tools/gn/substitution_list.h
@@ -35,9 +35,9 @@ class SubstitutionList {
return required_types_;
}
- private:
- void FillRequiredTypes();
+ void FillRequiredTypes(SubstitutionBits* bits) const;
+ private:
std::vector<SubstitutionPattern> list_;
std::vector<SubstitutionType> required_types_;
diff --git a/tools/gn/substitution_pattern.cc b/tools/gn/substitution_pattern.cc
index b589aaea..6a4d0c5 100644
--- a/tools/gn/substitution_pattern.cc
+++ b/tools/gn/substitution_pattern.cc
@@ -5,7 +5,9 @@
#include "tools/gn/substitution_pattern.h"
#include "base/strings/string_number_conversions.h"
+#include "tools/gn/build_settings.h"
#include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
#include "tools/gn/value.h"
SubstitutionPattern::Subrange::Subrange()
@@ -21,7 +23,7 @@ SubstitutionPattern::Subrange::Subrange(SubstitutionType t,
SubstitutionPattern::Subrange::~Subrange() {
}
-SubstitutionPattern::SubstitutionPattern() {
+SubstitutionPattern::SubstitutionPattern() : origin_(NULL) {
}
SubstitutionPattern::~SubstitutionPattern() {
@@ -80,14 +82,12 @@ bool SubstitutionPattern::Parse(const std::string& str,
}
}
- // Fill required types vector.
- bool required_type_bits[SUBSTITUTION_NUM_TYPES] = {0};
- FillRequiredTypes(required_type_bits);
+ origin_ = origin;
- for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES; i++) {
- if (required_type_bits[i])
- required_types_.push_back(static_cast<SubstitutionType>(i));
- }
+ // Fill required types vector.
+ SubstitutionBits bits;
+ FillRequiredTypes(&bits);
+ bits.FillVector(&required_types_);
return true;
}
@@ -102,10 +102,38 @@ std::string SubstitutionPattern::AsString() const {
return result;
}
-void SubstitutionPattern::FillRequiredTypes(
- bool required_types[SUBSTITUTION_NUM_TYPES]) const {
+void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
for (size_t i = 0; i < ranges_.size(); i++) {
if (ranges_[i].type != SUBSTITUTION_LITERAL)
- required_types[static_cast<size_t>(ranges_[i].type)] = true;
+ bits->used[static_cast<size_t>(ranges_[i].type)] = true;
+ }
+}
+
+bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
+ Err* err) const {
+ if (ranges_.empty()) {
+ *err = Err(origin_, "This is empty but I was expecting an output file.");
+ return false;
}
+
+ if (ranges_[0].type == SUBSTITUTION_LITERAL) {
+ // If the first thing is a literal, it must start with the output dir.
+ if (!EnsureStringIsInOutputDir(
+ build_settings->build_dir(),
+ ranges_[0].literal, origin_, err))
+ return false;
+ } else {
+ // Otherwise, the first subrange must be a pattern that expands to
+ // something in the output directory.
+ if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
+ *err = Err(origin_,
+ "File is not inside output directory.",
+ "The given file should be in the output directory. Normally you\n"
+ "would specify\n\"$target_out_dir/foo\" or "
+ "\"{{source_gen_dir}}/foo\".");
+ return false;
+ }
+ }
+
+ return true;
}
diff --git a/tools/gn/substitution_pattern.h b/tools/gn/substitution_pattern.h
index 70fc5eb..7e6c662 100644
--- a/tools/gn/substitution_pattern.h
+++ b/tools/gn/substitution_pattern.h
@@ -10,6 +10,7 @@
#include "tools/gn/substitution_type.h"
+class BuildSettings;
class Err;
class ParseNode;
class Value;
@@ -22,6 +23,10 @@ class SubstitutionPattern {
Subrange(SubstitutionType t, const std::string& l = std::string());
~Subrange();
+ inline bool operator==(const Subrange& other) const {
+ return type == other.type && literal == other.literal;
+ }
+
SubstitutionType type;
// When type_ == LITERAL, this specifies the literal.
@@ -41,7 +46,13 @@ class SubstitutionPattern {
// Sets the bits in the given vector corresponding to the substitutions used
// by this pattern. SUBSTITUTION_LITERAL is ignored.
- void FillRequiredTypes(bool required_types[SUBSTITUTION_NUM_TYPES]) const;
+ void FillRequiredTypes(SubstitutionBits* bits) const;
+
+ // Checks whether this pattern resolves to something in the output directory
+ // for the given build settings. If not, returns false and fills in the given
+ // error.
+ bool IsInOutputDir(const BuildSettings* build_settings,
+ Err* err) const;
// Returns a vector listing the substitutions used by this pattern, not
// counting SUBSTITUTION_LITERAL.
@@ -54,6 +65,7 @@ class SubstitutionPattern {
private:
std::vector<Subrange> ranges_;
+ const ParseNode* origin_;
std::vector<SubstitutionType> required_types_;
};
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index 739208a..06157e3 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -9,9 +9,11 @@
#include "tools/gn/err.h"
const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES] = {
- NULL, // SUBSTITUTION_LITERAL
+ "<<literal>>", // SUBSTITUTION_LITERAL
"{{source}}", // SUBSTITUTION_SOURCE
+ "{{output}}", // SUBSTITUTION_OUTPUT
+
"{{source_name_part}}", // SUBSTITUTION_NAME_PART
"{{source_file_part}}", // SUBSTITUTION_FILE_PART
"{{source_dir}}", // SUBSTITUTION_SOURCE_DIR
@@ -19,7 +21,7 @@ const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES] = {
"{{source_gen_dir}}", // SUBSTITUTION_SOURCE_GEN_DIR
"{{source_out_dir}}", // SUBSTITUTION_SOURCE_OUT_DIR
- "{{output}}", // SUBSTITUTION_OUTPUT
+ "{{label}}", // SUBSTITUTION_LABEL
"{{root_gen_dir}}", // SUBSTITUTION_ROOT_GEN_DIR
"{{root_out_dir}}", // SUBSTITUTION_ROOT_OUT_DIR
"{{target_gen_dir}}", // SUBSTITUTION_TARGET_GEN_DIR
@@ -44,9 +46,9 @@ const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES] = {
const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES] = {
NULL, // SUBSTITUTION_LITERAL
- // This isn't written by GN, the name here is referring to the Ninja variable
- // since when we would use this would be for writing source rules.
"in", // SUBSTITUTION_SOURCE
+ "out", // SUBSTITUTION_OUTPUT
+
"source_name_part", // SUBSTITUTION_NAME_PART
"source_file_part", // SUBSTITUTION_FILE_PART
"source_dir", // SUBSTITUTION_SOURCE_DIR
@@ -54,7 +56,7 @@ const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES] = {
"source_gen_dir", // SUBSTITUTION_SOURCE_GEN_DIR
"source_out_dir", // SUBSTITUTION_SOURCE_OUT_DIR
- "output", // SUBSTITUTION_OUTPUT
+ "label", // SUBSTITUTION_LABEL
"root_gen_dir", // SUBSTITUTION_ROOT_GEN_DIR
"root_out_dir", // SUBSTITUTION_ROOT_OUT_DIR
"target_gen_dir", // SUBSTITUTION_TARGET_GEN_DIR
@@ -69,20 +71,43 @@ const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES] = {
"defines", // SUBSTITUTION_DEFINES
"include_dirs", // SUBSTITUTION_INCLUDE_DIRS
- "inputs", // SUBSTITUTION_LINKER_INPUTS
+ // LINKER_INPUTS expands to the same Ninja var as SUBSTITUTION_SOURCE. These
+ // are used in different contexts and are named differently to keep things
+ // clear, but they both expand to the "set of input files" for a build rule.
+ "in", // SUBSTITUTION_LINKER_INPUTS
"ldflags", // SUBSTITUTION_LDFLAGS
"libs", // SUBSTITUTION_LIBS
"output_extension", // SUBSTITUTION_OUTPUT_EXTENSION
"solibs", // SUBSTITUTION_SOLIBS
};
+SubstitutionBits::SubstitutionBits() : used() {
+}
+
+void SubstitutionBits::MergeFrom(const SubstitutionBits& other) {
+ for (size_t i = 0; i < SUBSTITUTION_NUM_TYPES; i++)
+ used[i] |= other.used[i];
+}
+
+void SubstitutionBits::FillVector(std::vector<SubstitutionType>* vect) const {
+ for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES; i++) {
+ if (used[i])
+ vect->push_back(static_cast<SubstitutionType>(i));
+ }
+}
+
bool SubstitutionIsInOutputDir(SubstitutionType type) {
return type == SUBSTITUTION_SOURCE_GEN_DIR ||
- type == SUBSTITUTION_SOURCE_OUT_DIR;
+ type == SUBSTITUTION_SOURCE_OUT_DIR ||
+ type == SUBSTITUTION_ROOT_GEN_DIR ||
+ type == SUBSTITUTION_ROOT_OUT_DIR ||
+ type == SUBSTITUTION_TARGET_GEN_DIR ||
+ type == SUBSTITUTION_TARGET_OUT_DIR;
}
bool IsValidSourceSubstitution(SubstitutionType type) {
- return type == SUBSTITUTION_SOURCE ||
+ return type == SUBSTITUTION_LITERAL ||
+ type == SUBSTITUTION_SOURCE ||
type == SUBSTITUTION_SOURCE_NAME_PART ||
type == SUBSTITUTION_SOURCE_FILE_PART ||
type == SUBSTITUTION_SOURCE_DIR ||
@@ -92,7 +117,9 @@ bool IsValidSourceSubstitution(SubstitutionType type) {
}
bool IsValidToolSubstutition(SubstitutionType type) {
- return type == SUBSTITUTION_OUTPUT ||
+ return type == SUBSTITUTION_LITERAL ||
+ type == SUBSTITUTION_OUTPUT ||
+ type == SUBSTITUTION_LABEL ||
type == SUBSTITUTION_ROOT_GEN_DIR ||
type == SUBSTITUTION_ROOT_OUT_DIR ||
type == SUBSTITUTION_TARGET_GEN_DIR ||
@@ -102,6 +129,8 @@ bool IsValidToolSubstutition(SubstitutionType type) {
bool IsValidCompilerSubstitution(SubstitutionType type) {
return IsValidToolSubstutition(type) ||
+ IsValidSourceSubstitution(type) ||
+ type == SUBSTITUTION_SOURCE ||
type == SUBSTITUTION_CFLAGS ||
type == SUBSTITUTION_CFLAGS_C ||
type == SUBSTITUTION_CFLAGS_CC ||
@@ -113,8 +142,8 @@ bool IsValidCompilerSubstitution(SubstitutionType type) {
bool IsValidCompilerOutputsSubstitution(SubstitutionType type) {
// All tool types except "output" (which would be infinitely recursive).
- return IsValidToolSubstutition(type) &&
- type != SUBSTITUTION_OUTPUT;
+ return (IsValidToolSubstutition(type) && type != SUBSTITUTION_OUTPUT) ||
+ IsValidSourceSubstitution(type);
}
bool IsValidLinkerSubstitution(SubstitutionType type) {
@@ -132,6 +161,11 @@ bool IsValidLinkerOutputsSubstitution(SubstitutionType type) {
type == SUBSTITUTION_OUTPUT_EXTENSION;
}
+bool IsValidCopySubstitution(SubstitutionType type) {
+ return IsValidToolSubstutition(type) ||
+ type == SUBSTITUTION_SOURCE;
+}
+
bool EnsureValidSourcesSubstitutions(
const std::vector<SubstitutionType>& types,
const ParseNode* origin,
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index fd7c406..7212157 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -19,7 +19,11 @@ enum SubstitutionType {
// until NUM_TYPES.
SUBSTITUTION_FIRST_PATTERN,
+ // These map to Ninja's {in} and {out} variables.
SUBSTITUTION_SOURCE = SUBSTITUTION_FIRST_PATTERN, // {{source}}
+ SUBSTITUTION_OUTPUT, // {{output}}
+
+ // Valid for all compiler tools.
SUBSTITUTION_SOURCE_NAME_PART, // {{source_name_part}}
SUBSTITUTION_SOURCE_FILE_PART, // {{source_file_part}}
SUBSTITUTION_SOURCE_DIR, // {{source_dir}}
@@ -27,8 +31,9 @@ enum SubstitutionType {
SUBSTITUTION_SOURCE_GEN_DIR, // {{source_gen_dir}}
SUBSTITUTION_SOURCE_OUT_DIR, // {{source_out_dir}}
- // Valid for all compiler and linker tools (depends on target).
- SUBSTITUTION_OUTPUT, // {{output}}
+ // Valid for all compiler and linker tools. These depend on the target and
+ // no not vary on a per-file basis.
+ SUBSTITUTION_LABEL, // {{label}}
SUBSTITUTION_ROOT_GEN_DIR, // {{root_gen_dir}}
SUBSTITUTION_ROOT_OUT_DIR, // {{root_out_dir}}
SUBSTITUTION_TARGET_GEN_DIR, // {{target_gen_dir}}
@@ -63,6 +68,23 @@ extern const char* kSubstitutionNames[SUBSTITUTION_NUM_TYPES];
// the dollar sign.
extern const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES];
+// A wrapper around an array if flags indicating whether a give substitution
+// type is required in some context. By convention, the LITERAL type bit is
+// not set.
+struct SubstitutionBits {
+ SubstitutionBits();
+
+ // Merges any bits set in the given "other" to this one. This object will
+ // then be the union of all bits in the two lists.
+ void MergeFrom(const SubstitutionBits& other);
+
+ // Converts the substitution type bitfield (with a true set for each required
+ // item) to a vector of the types listed. Does not include LITERAL.
+ void FillVector(std::vector<SubstitutionType>* vect) const;
+
+ bool used[SUBSTITUTION_NUM_TYPES];
+};
+
// Returns true if the given substitution pattern references the output
// directory. This is used to check strings that begin with a substitution to
// verify that the produce a file in the output directory.
@@ -76,6 +98,7 @@ bool IsValidCompilerSubstitution(SubstitutionType type);
bool IsValidCompilerOutputsSubstitution(SubstitutionType type);
bool IsValidLinkerSubstitution(SubstitutionType type);
bool IsValidLinkerOutputsSubstitution(SubstitutionType type);
+bool IsValidCopySubstitution(SubstitutionType type);
// Like the "IsValid..." version above but checks a list of types and sets a
// an error blaming the given source if the test fails.
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index d474381..3a41b38 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -10,8 +10,75 @@
#include "tools/gn/output_file.h"
#include "tools/gn/settings.h"
#include "tools/gn/source_file.h"
+#include "tools/gn/string_utils.h"
#include "tools/gn/substitution_list.h"
#include "tools/gn/substitution_pattern.h"
+#include "tools/gn/target.h"
+
+namespace {
+
+// This happens when the output of a substitution looks like
+// <some_output_dir>/<other_stuff>. and we're computing a file in the output
+// directory. If <some_output_dir> resolves to the empty string because it
+// refers to the root build directory, the result will start with a slash which
+// is wrong.
+//
+// There are several possible solutions:
+//
+// - Could convert empty directories to a ".". However, this looks weird in the
+// Ninja file and Ninja doesn't canonicalize this away.
+//
+// - Make all substitutions compute SourceFiles and then convert to
+// OutputFiles. The root_build_dir will never be empty in this case, and the
+// Rebase function will properly strip the slash away when it is rebased to
+// be relative to the output directory. However, we never need compiler and
+// linker outputs as SourceFiles, and we do a lot of these conversions which
+// requires a lot of unnecessary path rebasing.
+//
+// - Detect this as a special case and delete the slash.
+//
+// This function implements the special case solution. This problem only arises
+// in the very specific case where we're appending a literal beginning in a
+// slash, the result string is empty, and the preceeding pattern identifies
+// an output directory.
+//
+// If we find too many problems with this implementation, it would probably be
+// cleanest to implement the "round trip through SourceFile" solution for
+// simplicity and guaranteed correctness, rather than adding even more special
+// cases.
+//
+// This function only needs to be called when computing substitutions as
+// OutputFiles (which are relative to the build dir) and not round-tripping
+// through SourceFiles.
+void AppendLiteralWithPossibleSlashEliding(
+ const std::vector<SubstitutionPattern::Subrange>& ranges,
+ size_t literal_index,
+ std::string* result) {
+ const std::string& literal = ranges[literal_index].literal;
+
+ if (// When the literal's index is 0 and it begins with a slash the user
+ // must have wanted it to start with a slash. Likewise, if it's 2 or
+ // more, it's impossible to have a length > 1 sequence of substitutions
+ // that both make sense as a path and resolve to the build directory.
+ literal_index != 1 ||
+ // When the result is nonempty, appending the slash as a separator is
+ // always OK.
+ !result->empty() ||
+ // If the literal doesn't begin in a slash, appending directly is fine.
+ literal.empty() || literal[0] != '/') {
+ result->append(literal);
+ return;
+ }
+
+ // If we get here, we need to collapse the slash. Assert that the first
+ // substitution should have ended up in the output directory. This should
+ // have already been checked since linker and compiler outputs (which is
+ // what this is used for) should always bein the output directory.
+ DCHECK(SubstitutionIsInOutputDir(ranges[0].type));
+ result->append(&literal[1], literal.size() - 1);
+}
+
+} // namespace
const char kSourceExpansion_Help[] =
"How Source Expansion Works\n"
@@ -118,15 +185,38 @@ const char kSourceExpansion_Help[] =
" //out/Debug/obj/mydirectory/input2.h\n"
" //out/Debug/obj/mydirectory/input2.cc\n";
-SubstitutionWriter::SubstitutionWriter() {
-}
+// static
+void SubstitutionWriter::WriteWithNinjaVariables(
+ const SubstitutionPattern& pattern,
+ const EscapeOptions& escape_options,
+ std::ostream& out) {
+ // The result needs to be quoted as if it was one string, but the $ for
+ // the inserted Ninja variables can't be escaped. So write to a buffer with
+ // no quoting, and then quote the whole thing if necessary.
+ EscapeOptions no_quoting(escape_options);
+ no_quoting.inhibit_quoting = true;
-SubstitutionWriter::~SubstitutionWriter() {
+ bool needs_quotes = false;
+ std::string result;
+ for (size_t i = 0; i < pattern.ranges().size(); i++) {
+ const SubstitutionPattern::Subrange range = pattern.ranges()[i];
+ if (range.type == SUBSTITUTION_LITERAL) {
+ result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
+ } else {
+ result.append("${");
+ result.append(kSubstitutionNinjaNames[range.type]);
+ result.append("}");
+ }
+ }
+
+ if (needs_quotes && !escape_options.inhibit_quoting)
+ out << "\"" << result << "\"";
+ else
+ out << result;
}
// static
void SubstitutionWriter::GetListAsSourceFiles(
- const Settings* settings,
const SubstitutionList& list,
std::vector<SourceFile>* output) {
for (size_t i = 0; i < list.list().size(); i++) {
@@ -145,16 +235,16 @@ void SubstitutionWriter::GetListAsSourceFiles(
}
}
+// static
void SubstitutionWriter::GetListAsOutputFiles(
const Settings* settings,
const SubstitutionList& list,
std::vector<OutputFile>* output) {
std::vector<SourceFile> output_as_sources;
- GetListAsSourceFiles(settings, list, &output_as_sources);
+ GetListAsSourceFiles(list, &output_as_sources);
for (size_t i = 0; i < output_as_sources.size(); i++) {
- output->push_back(OutputFile(
- RebaseSourceAbsolutePath(output_as_sources[i].value(),
- settings->build_settings()->build_dir())));
+ output->push_back(OutputFile(settings->build_settings(),
+ output_as_sources[i]));
}
}
@@ -201,9 +291,7 @@ OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
<< "The result of the pattern \""
<< pattern.AsString()
<< "\" was not an absolute path beginning in \"//\".";
- return OutputFile(
- RebaseSourceAbsolutePath(result_as_source.value(),
- settings->build_settings()->build_dir()));
+ return OutputFile(settings->build_settings(), result_as_source);
}
// static
@@ -298,36 +386,6 @@ void SubstitutionWriter::WriteNinjaVariablesForSource(
}
// static
-void SubstitutionWriter::WriteWithNinjaVariables(
- const SubstitutionPattern& pattern,
- const EscapeOptions& escape_options,
- std::ostream& out) {
- // The result needs to be quoted as if it was one string, but the $ for
- // the inserted Ninja variables can't be escaped. So write to a buffer with
- // no quoting, and then quote the whole thing if necessary.
- EscapeOptions no_quoting(escape_options);
- no_quoting.inhibit_quoting = true;
-
- bool needs_quotes = false;
- std::string result;
- for (size_t i = 0; i < pattern.ranges().size(); i++) {
- const SubstitutionPattern::Subrange range = pattern.ranges()[i];
- if (range.type == SUBSTITUTION_LITERAL) {
- result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
- } else {
- result.append("${");
- result.append(kSubstitutionNinjaNames[range.type]);
- result.append("}");
- }
- }
-
- if (needs_quotes && !escape_options.inhibit_quoting)
- out << "\"" << result << "\"";
- else
- out << result;
-}
-
-// static
std::string SubstitutionWriter::GetSourceSubstitution(
const Settings* settings,
const SourceFile& source,
@@ -371,7 +429,9 @@ std::string SubstitutionWriter::GetSourceSubstitution(
break;
default:
- NOTREACHED();
+ NOTREACHED()
+ << "Unsupported substitution for this function: "
+ << kSubstitutionNames[type];
return std::string();
}
@@ -382,3 +442,182 @@ std::string SubstitutionWriter::GetSourceSubstitution(
return to_rebase;
return RebaseSourceAbsolutePath(to_rebase, relative_to);
}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionPattern& pattern) {
+ std::string result_value;
+ for (size_t i = 0; i < pattern.ranges().size(); i++) {
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
+ if (subrange.type == SUBSTITUTION_LITERAL) {
+ result_value.append(subrange.literal);
+ } else {
+ std::string subst;
+ CHECK(GetTargetSubstitution(target, subrange.type, &subst));
+ result_value.append(subst);
+ }
+ }
+ return OutputFile(result_value);
+}
+
+// static
+void SubstitutionWriter::ApplyListToTargetAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output) {
+ for (size_t i = 0; i < list.list().size(); i++) {
+ output->push_back(ApplyPatternToTargetAsOutputFile(
+ target, tool, list.list()[i]));
+ }
+}
+
+// static
+bool SubstitutionWriter::GetTargetSubstitution(
+ const Target* target,
+ SubstitutionType type,
+ std::string* result) {
+ switch (type) {
+ case SUBSTITUTION_LABEL:
+ // Only include the toolchain for non-default toolchains.
+ *result = target->label().GetUserVisibleName(
+ !target->settings()->is_default());
+ break;
+ case SUBSTITUTION_ROOT_GEN_DIR:
+ *result = GetToolchainGenDirAsOutputFile(target->settings()).value();
+ TrimTrailingSlash(result);
+ break;
+ case SUBSTITUTION_ROOT_OUT_DIR:
+ *result = target->settings()->toolchain_output_subdir().value();
+ TrimTrailingSlash(result);
+ break;
+ case SUBSTITUTION_TARGET_GEN_DIR:
+ *result = GetTargetGenDirAsOutputFile(target).value();
+ TrimTrailingSlash(result);
+ break;
+ case SUBSTITUTION_TARGET_OUT_DIR:
+ *result = GetTargetOutputDirAsOutputFile(target).value();
+ TrimTrailingSlash(result);
+ break;
+ case SUBSTITUTION_TARGET_OUTPUT_NAME:
+ *result = target->GetComputedOutputName(true);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+// static
+std::string SubstitutionWriter::GetTargetSubstitution(
+ const Target* target,
+ SubstitutionType type) {
+ std::string result;
+ GetTargetSubstitution(target, type, &result);
+ return result;
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
+ const Target* target,
+ const SourceFile& source,
+ const SubstitutionPattern& pattern) {
+ OutputFile result;
+ for (size_t i = 0; i < pattern.ranges().size(); i++) {
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
+ if (subrange.type == SUBSTITUTION_LITERAL) {
+ AppendLiteralWithPossibleSlashEliding(
+ pattern.ranges(), i, &result.value());
+ } else {
+ result.value().append(
+ GetCompilerSubstitution(target, source, subrange.type));
+ }
+ }
+ return result;
+}
+
+// static
+void SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+ const Target* target,
+ const SourceFile& source,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output) {
+ for (size_t i = 0; i < list.list().size(); i++) {
+ output->push_back(ApplyPatternToCompilerAsOutputFile(
+ target, source, list.list()[i]));
+ }
+}
+
+// static
+std::string SubstitutionWriter::GetCompilerSubstitution(
+ const Target* target,
+ const SourceFile& source,
+ SubstitutionType type) {
+ // First try the common tool ones.
+ std::string result;
+ if (GetTargetSubstitution(target, type, &result))
+ return result;
+
+ // Fall-through to the source ones.
+ return GetSourceSubstitution(
+ target->settings(), source, type, OUTPUT_RELATIVE,
+ target->settings()->build_settings()->build_dir());
+}
+
+// static
+OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionPattern& pattern) {
+ OutputFile result;
+ for (size_t i = 0; i < pattern.ranges().size(); i++) {
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
+ if (subrange.type == SUBSTITUTION_LITERAL) {
+ AppendLiteralWithPossibleSlashEliding(
+ pattern.ranges(), i, &result.value());
+ } else {
+ result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
+ }
+ }
+ return result;
+}
+
+// static
+void SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output) {
+ for (size_t i = 0; i < list.list().size(); i++) {
+ output->push_back(ApplyPatternToLinkerAsOutputFile(
+ target, tool, list.list()[i]));
+ }
+}
+
+// static
+std::string SubstitutionWriter::GetLinkerSubstitution(
+ const Target* target,
+ const Tool* tool,
+ SubstitutionType type) {
+ // First try the common tool ones.
+ std::string result;
+ if (GetTargetSubstitution(target, type, &result))
+ return result;
+
+ // Fall-through to the linker-specific ones.
+ switch (type) {
+ case SUBSTITUTION_OUTPUT_EXTENSION:
+ // Use the extension provided on the target if nonempty, 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())
+ return tool->default_output_extension();
+ return std::string(".") + target->output_extension();
+
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
diff --git a/tools/gn/substitution_writer.h b/tools/gn/substitution_writer.h
index cd45566..219f135 100644
--- a/tools/gn/substitution_writer.h
+++ b/tools/gn/substitution_writer.h
@@ -18,11 +18,35 @@ class SourceDir;
class SourceFile;
class SubstitutionList;
class SubstitutionPattern;
+class Target;
+class Tool;
// Help text for script source expansion.
extern const char kSourceExpansion_Help[];
// This class handles writing or applying substitution patterns to strings.
+//
+// There are several different uses:
+//
+// - Source substitutions: These are used to compute action_foreach
+// outputs and arguments. Functions are provided to expand these in terms
+// of both OutputFiles (for writing Ninja files) as well as SourceFiles
+// (for computing lists used by code).
+//
+// - Target substitutions: These are specific to the target+tool combination
+// and are shared between the compiler and linker ones. It includes things
+// like the target_gen_dir.
+//
+// - Compiler substitutions: These are used to compute compiler outputs.
+// It includes all source substitutions (since they depend on the various
+// parts of the source file) as well as the target substitutions.
+//
+// - Linker substitutions: These are used to compute linker outputs. It
+// includes the target substitutions.
+//
+// The compiler and linker specific substitutions do NOT include the various
+// cflags, ldflags, libraries, etc. These are written by the ninja target
+// writer since they depend on traversing the dependency tree.
class SubstitutionWriter {
public:
enum OutputStyle {
@@ -30,15 +54,20 @@ class SubstitutionWriter {
OUTPUT_RELATIVE, // Dirs will be relative to a given directory.
};
- SubstitutionWriter();
- ~SubstitutionWriter();
+ // Writes the pattern to the given stream with no special handling, and with
+ // Ninja variables replacing the patterns.
+ static void WriteWithNinjaVariables(
+ const SubstitutionPattern& pattern,
+ const EscapeOptions& escape_options,
+ std::ostream& out);
+
+ // NOP substitutions ---------------------------------------------------------
// Converts the given SubstitutionList to OutputFiles assuming there are
// no substitutions (it will assert if there are). This is used for cases
// like actions where the outputs are explicit, but the list is stored as
// a SubstitutionList.
static void GetListAsSourceFiles(
- const Settings* settings,
const SubstitutionList& list,
std::vector<SourceFile>* output);
static void GetListAsOutputFiles(
@@ -46,6 +75,8 @@ class SubstitutionWriter {
const SubstitutionList& list,
std::vector<OutputFile>* output);
+ // Source substitutions -----------------------------------------------------
+
// Applies the substitution pattern to a source file, returning the result
// as either a string, a SourceFile or an OutputFile. If the result is
// expected to be a SourceFile or an OutputFile, this will CHECK if the
@@ -115,13 +146,6 @@ class SubstitutionWriter {
const EscapeOptions& escape_options,
std::ostream& out);
- // Writes the pattern to the given stream with no special handling, and with
- // Ninja variables replacing the patterns.
- static void WriteWithNinjaVariables(
- const SubstitutionPattern& pattern,
- const EscapeOptions& escape_options,
- std::ostream& out);
-
// Extracts the given type of substitution related to a source file from the
// given source file. If output_style is OUTPUT_RELATIVE, relative_to
// indicates the directory that the relative directories should be relative
@@ -132,6 +156,75 @@ class SubstitutionWriter {
SubstitutionType type,
OutputStyle output_style,
const SourceDir& relative_to);
+
+ // Target substitutions ------------------------------------------------------
+ //
+ // Handles the target substitutions that apply to both compiler and linker
+ // tools.
+ static OutputFile ApplyPatternToTargetAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionPattern& pattern);
+ static void ApplyListToTargetAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output);
+
+ // This function is slightly different than the other substitution getters
+ // since it can handle failure (since it is designed to be used by the
+ // compiler and linker ones which will fall through if it's not a common tool
+ // one).
+ static bool GetTargetSubstitution(
+ const Target* target,
+ SubstitutionType type,
+ std::string* result);
+ static std::string GetTargetSubstitution(
+ const Target* target,
+ SubstitutionType type);
+
+ // Compiler substitutions ----------------------------------------------------
+ //
+ // A compiler substitution allows both source and tool substitutions. These
+ // are used to compute output names for compiler tools.
+
+ static OutputFile ApplyPatternToCompilerAsOutputFile(
+ const Target* target,
+ const SourceFile& source,
+ const SubstitutionPattern& pattern);
+ static void ApplyListToCompilerAsOutputFile(
+ const Target* target,
+ const SourceFile& source,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output);
+
+ // Like GetSourceSubstitution but for strings based on the target or
+ // toolchain. This type of result will always be relative to the build
+ // directory.
+ static std::string GetCompilerSubstitution(
+ const Target* target,
+ const SourceFile& source,
+ SubstitutionType type);
+
+ // Linker substitutions ------------------------------------------------------
+
+ static OutputFile ApplyPatternToLinkerAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionPattern& pattern);
+ static void ApplyListToLinkerAsOutputFile(
+ const Target* target,
+ const Tool* tool,
+ const SubstitutionList& list,
+ std::vector<OutputFile>* output);
+
+ // Like GetSourceSubstitution but for strings based on the target or
+ // toolchain. This type of result will always be relative to the build
+ // directory.
+ static std::string GetLinkerSubstitution(
+ const Target* target,
+ const Tool* tool,
+ SubstitutionType type);
};
#endif // TOOLS_GN_SUBSTITUTION_WRITER_H_
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index 12c96d5..f17e31d 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -7,10 +7,32 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/err.h"
#include "tools/gn/escape.h"
+#include "tools/gn/substitution_list.h"
#include "tools/gn/substitution_pattern.h"
#include "tools/gn/substitution_writer.h"
+#include "tools/gn/target.h"
#include "tools/gn/test_with_scope.h"
+TEST(SubstitutionWriter, GetListAs) {
+ TestWithScope setup;
+
+ SubstitutionList list = SubstitutionList::MakeForTest(
+ "//foo/bar/a.cc",
+ "//foo/bar/b.cc");
+
+ std::vector<SourceFile> sources;
+ SubstitutionWriter::GetListAsSourceFiles(list, &sources);
+ ASSERT_EQ(2u, sources.size());
+ EXPECT_EQ("//foo/bar/a.cc", sources[0].value());
+ EXPECT_EQ("//foo/bar/b.cc", sources[1].value());
+
+ std::vector<OutputFile> outputs;
+ SubstitutionWriter::GetListAsOutputFiles(setup.settings(), list, &outputs);
+ ASSERT_EQ(2u, outputs.size());
+ EXPECT_EQ("../../foo/bar/a.cc", outputs[0].value());
+ EXPECT_EQ("../../foo/bar/b.cc", outputs[1].value());
+}
+
TEST(SubstitutionWriter, ApplyPatternToSource) {
TestWithScope setup;
@@ -79,9 +101,7 @@ TEST(SubstitutionWriter, WriteWithNinjaVariables) {
out.str());
}
-// Tests in isolation different types of substitutions and that the right
-// things are generated.
-TEST(SubstutitionWriter, Substitutions) {
+TEST(SubstutitionWriter, SourceSubstitutions) {
TestWithScope setup;
// Call to get substitutions relative to the build dir.
@@ -150,3 +170,88 @@ TEST(SubstutitionWriter, Substitutions) {
#undef GetAbsSubst
#undef GetRelSubst
}
+
+TEST(SubstitutionWriter, TargetSubstitutions) {
+ TestWithScope setup;
+
+ Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+ target.set_output_type(Target::STATIC_LIBRARY);
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
+ std::string result;
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_LABEL, &result));
+ EXPECT_EQ("//foo/bar:baz", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_ROOT_GEN_DIR, &result));
+ EXPECT_EQ("gen", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_ROOT_OUT_DIR, &result));
+ EXPECT_EQ("", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_TARGET_GEN_DIR, &result));
+ EXPECT_EQ("gen/foo/bar", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_TARGET_OUT_DIR, &result));
+ EXPECT_EQ("obj/foo/bar", result);
+
+ EXPECT_TRUE(SubstitutionWriter::GetTargetSubstitution(
+ &target, SUBSTITUTION_TARGET_OUTPUT_NAME, &result));
+ EXPECT_EQ("libbaz", result);
+}
+
+TEST(SubstitutionWriter, CompilerSubstitutions) {
+ TestWithScope setup;
+
+ Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+ target.set_output_type(Target::STATIC_LIBRARY);
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
+ // The compiler substitution is just source + target combined. So test one
+ // of each of those classes of things to make sure this is hooked up.
+ EXPECT_EQ("file",
+ SubstitutionWriter::GetCompilerSubstitution(
+ &target, SourceFile("//foo/bar/file.txt"),
+ SUBSTITUTION_SOURCE_NAME_PART));
+ EXPECT_EQ("gen/foo/bar",
+ SubstitutionWriter::GetCompilerSubstitution(
+ &target, SourceFile("//foo/bar/file.txt"),
+ SUBSTITUTION_TARGET_GEN_DIR));
+}
+
+TEST(SubstitutionWriter, LinkerSubstitutions) {
+ TestWithScope setup;
+
+ Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+ target.set_output_type(Target::SHARED_LIBRARY);
+ target.SetToolchain(setup.toolchain());
+ target.OnResolved();
+
+ const Tool* tool = setup.toolchain()->GetToolForTargetFinalOutput(&target);
+
+ // The compiler substitution is just target + OUTPUT_EXTENSION combined. So
+ // test one target one plus the output extension.
+ EXPECT_EQ(".so",
+ SubstitutionWriter::GetLinkerSubstitution(
+ &target, tool, SUBSTITUTION_OUTPUT_EXTENSION));
+ EXPECT_EQ("gen/foo/bar",
+ SubstitutionWriter::GetLinkerSubstitution(
+ &target, tool, SUBSTITUTION_TARGET_GEN_DIR));
+
+ // Test that we handle paths that end up in the root build dir properly
+ // (no leading "./" or "/").
+ Err err;
+ SubstitutionPattern pattern;
+ ASSERT_TRUE(
+ pattern.Parse("{{root_out_dir}}/{{target_output_name}}.so", NULL, &err));
+
+ OutputFile output = SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ &target, tool, pattern);
+ EXPECT_EQ("libbaz.so", output.value());
+}
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 1983374..f5bdd58 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -5,8 +5,12 @@
#include "tools/gn/target.h"
#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "tools/gn/config_values_extractors.h"
+#include "tools/gn/filesystem_utils.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/substitution_writer.h"
namespace {
@@ -41,7 +45,8 @@ Target::Target(const Settings* settings, const Label& label)
: Item(settings, label),
output_type_(UNKNOWN),
all_headers_public_(true),
- hard_dep_(false) {
+ hard_dep_(false),
+ toolchain_(NULL) {
}
Target::~Target() {
@@ -83,6 +88,7 @@ const Target* Target::AsTarget() const {
void Target::OnResolved() {
DCHECK(output_type_ != UNKNOWN);
+ DCHECK(toolchain_) << "Toolchain should have been set before resolving.";
// Convert any groups we depend on to just direct dependencies on that
// group's deps. We insert the new deps immediately after the group so that
@@ -91,6 +97,7 @@ void Target::OnResolved() {
for (size_t i = 0; i < deps_.size(); i++) {
const Target* dep = deps_[i].ptr;
if (dep->output_type_ == GROUP) {
+ // TODO(brettw) bug 403488 this should also handle datadeps.
deps_.insert(deps_.begin() + i + 1, dep->deps_.begin(), dep->deps_.end());
i += dep->deps_.size();
}
@@ -120,12 +127,61 @@ void Target::OnResolved() {
}
PullForwardedDependentConfigs();
PullRecursiveHardDeps();
+
+ FillOutputFiles();
}
bool Target::IsLinkable() const {
return output_type_ == STATIC_LIBRARY || output_type_ == SHARED_LIBRARY;
}
+std::string Target::GetComputedOutputName(bool include_prefix) const {
+ DCHECK(toolchain_)
+ << "Toolchain must be specified before getting the computed output name.";
+
+ const std::string& name = output_name_.empty() ? label().name()
+ : output_name_;
+
+ std::string result;
+ if (include_prefix) {
+ const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+ const std::string& prefix = tool->output_prefix();
+ // Only add the prefix if the name doesn't already have it.
+ if (!StartsWithASCII(name, prefix, true))
+ result = prefix;
+ }
+
+ result.append(name);
+ return result;
+}
+
+bool Target::SetToolchain(const Toolchain* toolchain, Err* err) {
+ DCHECK(!toolchain_);
+ DCHECK_NE(UNKNOWN, output_type_);
+ toolchain_ = toolchain;
+
+ const Tool* tool = toolchain->GetToolForTargetFinalOutput(this);
+ if (tool)
+ return true;
+
+ // Tool not specified for this target type.
+ if (err) {
+ *err = Err(defined_from(), "This target uses an undefined tool.",
+ base::StringPrintf(
+ "The target %s\n"
+ "of type \"%s\"\n"
+ "uses toolchain %s\n"
+ "which doesn't have the tool \"%s\" defined.\n\n"
+ "Alas, I can not continue.",
+ label().GetUserVisibleName(false).c_str(),
+ GetStringForOutputType(output_type_),
+ label().GetToolchainLabel().GetUserVisibleName(false).c_str(),
+ Toolchain::ToolTypeToName(
+ toolchain->GetToolTypeForTargetFinalOutput(this)).c_str()));
+ }
+ return false;
+}
+
void Target::PullDependentTargetInfo() {
// Gather info from our dependents we need.
for (size_t dep_i = 0; dep_i < deps_.size(); dep_i++) {
@@ -190,3 +246,62 @@ void Target::PullRecursiveHardDeps() {
recursive_hard_deps_.insert(*cur);
}
}
+
+void Target::FillOutputFiles() {
+ const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
+ switch (output_type_) {
+ case GROUP:
+ case SOURCE_SET:
+ case COPY_FILES:
+ case ACTION:
+ case ACTION_FOREACH: {
+ // These don't get linked to and use stamps which should be the first
+ // 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(".stamp");
+ break;
+ }
+ case EXECUTABLE:
+ // Executables don't get linked to, but the first output is used for
+ // dependency management.
+ CHECK_GE(tool->outputs().list().size(), 1u);
+ dependency_output_file_ =
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ this, tool, tool->outputs().list()[0]);
+ break;
+ case STATIC_LIBRARY:
+ // Static libraries both have dependencies and linking going off of the
+ // first output.
+ CHECK(tool->outputs().list().size() >= 1);
+ link_output_file_ = dependency_output_file_ =
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ this, tool, tool->outputs().list()[0]);
+ break;
+ case SHARED_LIBRARY:
+ CHECK(tool->outputs().list().size() >= 1);
+ if (tool->link_output().empty() && tool->depend_output().empty()) {
+ // Default behavior, use the first output file for both.
+ link_output_file_ = dependency_output_file_ =
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ this, tool, tool->outputs().list()[0]);
+ } else {
+ // Use the tool-specified ones.
+ if (!tool->link_output().empty()) {
+ link_output_file_ =
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ this, tool, tool->link_output());
+ }
+ if (!tool->depend_output().empty()) {
+ dependency_output_file_ =
+ SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+ this, tool, tool->link_output());
+ }
+ }
+ break;
+ case UNKNOWN:
+ default:
+ NOTREACHED();
+ }
+}
diff --git a/tools/gn/target.h b/tools/gn/target.h
index f0301e9..9b7ff6d 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -19,12 +19,14 @@
#include "tools/gn/item.h"
#include "tools/gn/label_ptr.h"
#include "tools/gn/ordered_set.h"
+#include "tools/gn/output_file.h"
#include "tools/gn/source_file.h"
#include "tools/gn/unique_vector.h"
class InputFile;
class Settings;
class Token;
+class Toolchain;
class Target : public Item {
public:
@@ -59,9 +61,18 @@ class Target : public Item {
bool IsLinkable() const;
// Will be the empty string to use the target label as the output name.
+ // See GetComputedOutputName().
const std::string& output_name() const { return output_name_; }
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).
+ //
+ // Because this depends on the tool for this target, the toolchain must
+ // have been set before calling.
+ std::string GetComputedOutputName(bool include_prefix) const;
+
const std::string& output_extension() const { return output_extension_; }
void set_output_extension(const std::string& extension) {
output_extension_ = extension;
@@ -155,6 +166,37 @@ class Target : public Item {
return recursive_hard_deps_;
}
+ // The toolchain is only known once this target is resolved (all if its
+ // dependencies are known). They will be null until then. Generally, this can
+ // only be used during target writing.
+ const Toolchain* toolchain() const { return toolchain_; }
+
+ // Sets the toolchain. The toolchain must include a tool for this target
+ // or the error will be set and the function will return false. Unusually,
+ // this function's "err" output is optional since this is commonly used
+ // frequently by unit tests which become needlessly verbose.
+ bool SetToolchain(const Toolchain* toolchain, Err* err = NULL);
+
+ // Returns outputs from this target. The link output file is the one that
+ // other targets link to when they depend on this target. This will only be
+ // valid for libraries and will be empty for all other target types.
+ //
+ // The dependency output file is the file that should be used to express
+ // a dependency on this one. It could be the same as the link output file
+ // (this will be the case for static libraries). For shared libraries it
+ // could be the same or different than the link output file, depending on the
+ // system. For actions this will be the stamp file.
+ //
+ // These are only known once the target is resolved and will be empty before
+ // that. This is a cache of the files to prevent every target that depends on
+ // a given library from recomputing the same pattern.
+ const OutputFile& link_output_file() const {
+ return link_output_file_;
+ }
+ const OutputFile& dependency_output_file() const {
+ return dependency_output_file_;
+ }
+
private:
// Pulls necessary information from dependencies to this one when all
// dependencies have been resolved.
@@ -165,6 +207,9 @@ class Target : public Item {
void PullForwardedDependentConfigs();
void PullRecursiveHardDeps();
+ // Fills the link and dependency output files when a target is resolved.
+ void FillOutputFiles();
+
OutputType output_type_;
std::string output_name_;
std::string output_extension_;
@@ -217,6 +262,13 @@ class Target : public Item {
ConfigValues config_values_; // Used for all binary targets.
ActionValues action_values_; // Used for action[_foreach] targets.
+ // Toolchain used by this target. Null until target is resolved.
+ const Toolchain* toolchain_;
+
+ // Output files. Null until the target is resolved.
+ OutputFile link_output_file_;
+ OutputFile dependency_output_file_;
+
DISALLOW_COPY_AND_ASSIGN(Target);
};
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index bdccf43..657581a 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -264,7 +264,7 @@ bool TargetGenerator::EnsureSubstitutionIsInOutputDir(
// If the first thing is a literal, it must start with the output dir.
if (!EnsureStringIsInOutputDir(
GetBuildSettings()->build_dir(),
- pattern.ranges()[0].literal, original_value, err_))
+ pattern.ranges()[0].literal, original_value.origin(), err_))
return false;
} else {
// Otherwise, the first subrange must be a pattern that expands to
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 9dcaf08..9bf0c30 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -7,51 +7,43 @@
#include "tools/gn/config.h"
#include "tools/gn/settings.h"
#include "tools/gn/target.h"
+#include "tools/gn/test_with_scope.h"
#include "tools/gn/toolchain.h"
-namespace {
-
-class TargetTest : public testing::Test {
- public:
- TargetTest()
- : build_settings_(),
- settings_(&build_settings_, std::string()),
- toolchain_(&settings_, Label(SourceDir("//tc/"), "tc")) {
- settings_.set_toolchain_label(toolchain_.label());
- }
- virtual ~TargetTest() {
- }
-
- protected:
- BuildSettings build_settings_;
- Settings settings_;
- Toolchain toolchain_;
-};
-
-} // namespace
-
// Tests that depending on a group is like depending directly on the group's
// deps.
-TEST_F(TargetTest, GroupDeps) {
+TEST(Target, GroupDeps) {
+ TestWithScope setup;
+
// Two low-level targets.
- Target x(&settings_, Label(SourceDir("//component/"), "x"));
- Target y(&settings_, Label(SourceDir("//component/"), "y"));
+ Target x(setup.settings(), Label(SourceDir("//component/"), "x"));
+ x.set_output_type(Target::STATIC_LIBRARY);
+ x.SetToolchain(setup.toolchain());
+ x.OnResolved();
+ Target y(setup.settings(), Label(SourceDir("//component/"), "y"));
+ y.set_output_type(Target::STATIC_LIBRARY);
+ y.SetToolchain(setup.toolchain());
+ y.OnResolved();
// Make a group for both x and y.
- Target g(&settings_, Label(SourceDir("//group/"), "g"));
+ Target g(setup.settings(), Label(SourceDir("//group/"), "g"));
g.set_output_type(Target::GROUP);
g.deps().push_back(LabelTargetPair(&x));
g.deps().push_back(LabelTargetPair(&y));
// Random placeholder target so we can see the group's deps get inserted at
// the right place.
- Target b(&settings_, Label(SourceDir("//app/"), "b"));
+ Target b(setup.settings(), Label(SourceDir("//app/"), "b"));
+ b.set_output_type(Target::STATIC_LIBRARY);
+ b.SetToolchain(setup.toolchain());
+ b.OnResolved();
// Make a target depending on the group and "b". OnResolved will expand.
- Target a(&settings_, Label(SourceDir("//app/"), "a"));
+ Target a(setup.settings(), Label(SourceDir("//app/"), "a"));
a.set_output_type(Target::EXECUTABLE);
a.deps().push_back(LabelTargetPair(&g));
a.deps().push_back(LabelTargetPair(&b));
+ a.SetToolchain(setup.toolchain());
a.OnResolved();
// The group's deps should be inserted after the group itself in the deps
@@ -65,15 +57,18 @@ TEST_F(TargetTest, GroupDeps) {
// Tests that lib[_dir]s are inherited across deps boundaries for static
// libraries but not executables.
-TEST_F(TargetTest, LibInheritance) {
+TEST(Target, LibInheritance) {
+ TestWithScope setup;
+
const std::string lib("foo");
const SourceDir libdir("/foo_dir/");
// Leaf target with ldflags set.
- Target z(&settings_, Label(SourceDir("//foo/"), "z"));
+ Target z(setup.settings(), Label(SourceDir("//foo/"), "z"));
z.set_output_type(Target::STATIC_LIBRARY);
z.config_values().libs().push_back(lib);
z.config_values().lib_dirs().push_back(libdir);
+ z.SetToolchain(setup.toolchain());
z.OnResolved();
// All lib[_dir]s should be set when target is resolved.
@@ -86,11 +81,12 @@ TEST_F(TargetTest, LibInheritance) {
// and its own. Its own flag should be before the inherited one.
const std::string second_lib("bar");
const SourceDir second_libdir("/bar_dir/");
- Target shared(&settings_, Label(SourceDir("//foo/"), "shared"));
+ Target shared(setup.settings(), Label(SourceDir("//foo/"), "shared"));
shared.set_output_type(Target::SHARED_LIBRARY);
shared.config_values().libs().push_back(second_lib);
shared.config_values().lib_dirs().push_back(second_libdir);
shared.deps().push_back(LabelTargetPair(&z));
+ shared.SetToolchain(setup.toolchain());
shared.OnResolved();
ASSERT_EQ(2u, shared.all_libs().size());
@@ -101,9 +97,10 @@ TEST_F(TargetTest, LibInheritance) {
EXPECT_EQ(libdir, shared.all_lib_dirs()[1]);
// Executable target shouldn't get either by depending on shared.
- Target exec(&settings_, Label(SourceDir("//foo/"), "exec"));
+ Target exec(setup.settings(), Label(SourceDir("//foo/"), "exec"));
exec.set_output_type(Target::EXECUTABLE);
exec.deps().push_back(LabelTargetPair(&shared));
+ exec.SetToolchain(setup.toolchain());
exec.OnResolved();
EXPECT_EQ(0u, exec.all_libs().size());
EXPECT_EQ(0u, exec.all_lib_dirs().size());
@@ -111,27 +108,32 @@ TEST_F(TargetTest, LibInheritance) {
// Test all/direct_dependent_configs inheritance, and
// forward_dependent_configs_from
-TEST_F(TargetTest, DependentConfigs) {
+TEST(Target, DependentConfigs) {
+ TestWithScope setup;
+
// Set up a dependency chain of a -> b -> c
- Target a(&settings_, Label(SourceDir("//foo/"), "a"));
+ Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
a.set_output_type(Target::EXECUTABLE);
- Target b(&settings_, Label(SourceDir("//foo/"), "b"));
+ a.SetToolchain(setup.toolchain());
+ Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
b.set_output_type(Target::STATIC_LIBRARY);
- Target c(&settings_, Label(SourceDir("//foo/"), "c"));
+ b.SetToolchain(setup.toolchain());
+ Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
c.set_output_type(Target::STATIC_LIBRARY);
+ c.SetToolchain(setup.toolchain());
a.deps().push_back(LabelTargetPair(&b));
b.deps().push_back(LabelTargetPair(&c));
// Normal non-inherited config.
- Config config(&settings_, Label(SourceDir("//foo/"), "config"));
+ Config config(setup.settings(), Label(SourceDir("//foo/"), "config"));
c.configs().push_back(LabelConfigPair(&config));
// All dependent config.
- Config all(&settings_, Label(SourceDir("//foo/"), "all"));
+ Config all(setup.settings(), Label(SourceDir("//foo/"), "all"));
c.all_dependent_configs().push_back(LabelConfigPair(&all));
// Direct dependent config.
- Config direct(&settings_, Label(SourceDir("//foo/"), "direct"));
+ Config direct(setup.settings(), Label(SourceDir("//foo/"), "direct"));
c.direct_dependent_configs().push_back(LabelConfigPair(&direct));
c.OnResolved();
@@ -151,10 +153,12 @@ TEST_F(TargetTest, DependentConfigs) {
EXPECT_EQ(&all, a.all_dependent_configs()[0].ptr);
// Making an an alternate A and B with B forwarding the direct dependents.
- Target a_fwd(&settings_, Label(SourceDir("//foo/"), "a_fwd"));
+ Target a_fwd(setup.settings(), Label(SourceDir("//foo/"), "a_fwd"));
a_fwd.set_output_type(Target::EXECUTABLE);
- Target b_fwd(&settings_, Label(SourceDir("//foo/"), "b_fwd"));
+ a_fwd.SetToolchain(setup.toolchain());
+ Target b_fwd(setup.settings(), Label(SourceDir("//foo/"), "b_fwd"));
b_fwd.set_output_type(Target::STATIC_LIBRARY);
+ b_fwd.SetToolchain(setup.toolchain());
a_fwd.deps().push_back(LabelTargetPair(&b_fwd));
b_fwd.deps().push_back(LabelTargetPair(&c));
b_fwd.forward_dependent_configs().push_back(LabelTargetPair(&c));
@@ -172,18 +176,23 @@ TEST_F(TargetTest, DependentConfigs) {
// Tests that forward_dependent_configs_from works for groups, forwarding the
// group's deps' dependent configs.
-TEST_F(TargetTest, ForwardDependentConfigsFromGroups) {
- Target a(&settings_, Label(SourceDir("//foo/"), "a"));
+TEST(Target, ForwardDependentConfigsFromGroups) {
+ TestWithScope setup;
+
+ Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
a.set_output_type(Target::EXECUTABLE);
- Target b(&settings_, Label(SourceDir("//foo/"), "b"));
+ a.SetToolchain(setup.toolchain());
+ Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
b.set_output_type(Target::GROUP);
- Target c(&settings_, Label(SourceDir("//foo/"), "c"));
+ b.SetToolchain(setup.toolchain());
+ Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
c.set_output_type(Target::STATIC_LIBRARY);
+ c.SetToolchain(setup.toolchain());
a.deps().push_back(LabelTargetPair(&b));
b.deps().push_back(LabelTargetPair(&c));
// Direct dependent config on C.
- Config direct(&settings_, Label(SourceDir("//foo/"), "direct"));
+ Config direct(setup.settings(), Label(SourceDir("//foo/"), "direct"));
c.direct_dependent_configs().push_back(LabelConfigPair(&direct));
// A forwards the dependent configs from B.
@@ -200,17 +209,23 @@ TEST_F(TargetTest, ForwardDependentConfigsFromGroups) {
ASSERT_EQ(&direct, a.direct_dependent_configs()[0].ptr);
}
-TEST_F(TargetTest, InheritLibs) {
+TEST(Target, InheritLibs) {
+ TestWithScope setup;
+
// Create a dependency chain:
// A (executable) -> B (shared lib) -> C (static lib) -> D (source set)
- Target a(&settings_, Label(SourceDir("//foo/"), "a"));
+ Target a(setup.settings(), Label(SourceDir("//foo/"), "a"));
a.set_output_type(Target::EXECUTABLE);
- Target b(&settings_, Label(SourceDir("//foo/"), "b"));
+ a.SetToolchain(setup.toolchain());
+ Target b(setup.settings(), Label(SourceDir("//foo/"), "b"));
b.set_output_type(Target::SHARED_LIBRARY);
- Target c(&settings_, Label(SourceDir("//foo/"), "c"));
+ b.SetToolchain(setup.toolchain());
+ Target c(setup.settings(), Label(SourceDir("//foo/"), "c"));
c.set_output_type(Target::STATIC_LIBRARY);
- Target d(&settings_, Label(SourceDir("//foo/"), "d"));
+ c.SetToolchain(setup.toolchain());
+ Target d(setup.settings(), Label(SourceDir("//foo/"), "d"));
d.set_output_type(Target::SOURCE_SET);
+ d.SetToolchain(setup.toolchain());
a.deps().push_back(LabelTargetPair(&b));
b.deps().push_back(LabelTargetPair(&c));
c.deps().push_back(LabelTargetPair(&d));
@@ -237,3 +252,44 @@ TEST_F(TargetTest, InheritLibs) {
EXPECT_EQ(1u, a_inherited.size());
EXPECT_TRUE(a_inherited.IndexOf(&b) != static_cast<size_t>(-1));
}
+
+TEST(Target, GetComputedOutputName) {
+ TestWithScope setup;
+
+ // Basic target with no prefix (executable type tool in the TestWithScope has
+ // no prefix) or output name.
+ Target basic(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ basic.set_output_type(Target::EXECUTABLE);
+ basic.SetToolchain(setup.toolchain());
+ basic.OnResolved();
+ EXPECT_EQ("bar", basic.GetComputedOutputName(false));
+ EXPECT_EQ("bar", basic.GetComputedOutputName(true));
+
+ // Target with no prefix but an output name.
+ Target with_name(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ with_name.set_output_type(Target::EXECUTABLE);
+ with_name.set_output_name("myoutput");
+ with_name.SetToolchain(setup.toolchain());
+ with_name.OnResolved();
+ EXPECT_EQ("myoutput", with_name.GetComputedOutputName(false));
+ EXPECT_EQ("myoutput", with_name.GetComputedOutputName(true));
+
+ // Target with a "lib" prefix (the static library tool in the TestWithScope
+ // should specify a "lib" output prefix).
+ Target with_prefix(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ with_prefix.set_output_type(Target::STATIC_LIBRARY);
+ with_prefix.SetToolchain(setup.toolchain());
+ with_prefix.OnResolved();
+ EXPECT_EQ("bar", with_prefix.GetComputedOutputName(false));
+ EXPECT_EQ("libbar", with_prefix.GetComputedOutputName(true));
+
+ // Target with a "lib" prefix that already has it applied. The prefix should
+ // not duplicate something already in the target name.
+ Target dup_prefix(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ dup_prefix.set_output_type(Target::STATIC_LIBRARY);
+ dup_prefix.set_output_name("libbar");
+ dup_prefix.SetToolchain(setup.toolchain());
+ dup_prefix.OnResolved();
+ EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(false));
+ EXPECT_EQ("libbar", dup_prefix.GetComputedOutputName(true));
+}
diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc
index 55fbc2e..06f4d04 100644
--- a/tools/gn/test_with_scope.cc
+++ b/tools/gn/test_with_scope.cc
@@ -8,6 +8,19 @@
#include "tools/gn/parser.h"
#include "tools/gn/tokenizer.h"
+namespace {
+
+void SetCommandForTool(const std::string& cmd, Tool* tool) {
+ Err err;
+ SubstitutionPattern command;
+ command.Parse(cmd, NULL, &err);
+ CHECK(!err.has_error())
+ << "Couldn't parse \"" << cmd << "\", " << "got " << err.message();
+ tool->set_command(command);
+}
+
+} // namespace
+
TestWithScope::TestWithScope()
: build_settings_(),
settings_(&build_settings_, std::string()),
@@ -19,11 +32,98 @@ TestWithScope::TestWithScope()
settings_.set_toolchain_label(toolchain_.label());
settings_.set_default_toolchain_label(toolchain_.label());
+
+ SetupToolchain(&toolchain_);
}
TestWithScope::~TestWithScope() {
}
+// static
+void TestWithScope::SetupToolchain(Toolchain* toolchain) {
+ Err err;
+
+ // CC
+ scoped_ptr<Tool> cc_tool(new Tool);
+ SetCommandForTool(
+ "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cc_tool.get());
+ cc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ toolchain->SetTool(Toolchain::TYPE_CC, cc_tool.Pass());
+
+ // CXX
+ scoped_ptr<Tool> cxx_tool(new Tool);
+ SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cxx_tool.get());
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ toolchain->SetTool(Toolchain::TYPE_CXX, cxx_tool.Pass());
+
+ // OBJC
+ scoped_ptr<Tool> objc_tool(new Tool);
+ SetCommandForTool(
+ "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} "
+ "{{include_dirs}} -o {{output}}",
+ objc_tool.get());
+ objc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ toolchain->SetTool(Toolchain::TYPE_OBJC, objc_tool.Pass());
+
+ // OBJC
+ scoped_ptr<Tool> objcxx_tool(new Tool);
+ SetCommandForTool(
+ "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} "
+ "{{include_dirs}} -o {{output}}",
+ objcxx_tool.get());
+ objcxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ toolchain->SetTool(Toolchain::TYPE_OBJCXX, objcxx_tool.Pass());
+
+ // Don't use RC and ASM tools in unit tests yet. Add here if needed.
+
+ // ALINK
+ scoped_ptr<Tool> alink_tool(new Tool);
+ SetCommandForTool("ar {{output}} {{source}}", alink_tool.get());
+ alink_tool->set_output_prefix("lib");
+ alink_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{target_out_dir}}/{{target_output_name}}.a"));
+ toolchain->SetTool(Toolchain::TYPE_ALINK, alink_tool.Pass());
+
+ // SOLINK
+ scoped_ptr<Tool> solink_tool(new Tool);
+ SetCommandForTool("ld -shared -o {{target_output_name}}.so {{inputs}} "
+ "{{ldflags}} {{libs}}", solink_tool.get());
+ solink_tool->set_output_prefix("lib");
+ solink_tool->set_default_output_extension(".so");
+ solink_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"));
+ toolchain->SetTool(Toolchain::TYPE_SOLINK, solink_tool.Pass());
+
+ // LINK
+ scoped_ptr<Tool> link_tool(new Tool);
+ SetCommandForTool("ld -o {{target_output_name}} {{source}} "
+ "{{ldflags}} {{libs}}", link_tool.get());
+ link_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{root_out_dir}}/{{target_output_name}}"));
+ toolchain->SetTool(Toolchain::TYPE_LINK, link_tool.Pass());
+
+ // STAMP
+ scoped_ptr<Tool> stamp_tool(new Tool);
+ SetCommandForTool("touch {{output}}", stamp_tool.get());
+ toolchain->SetTool(Toolchain::TYPE_STAMP, stamp_tool.Pass());
+
+ // COPY
+ scoped_ptr<Tool> copy_tool(new Tool);
+ SetCommandForTool("cp {{source}} {{output}}", copy_tool.get());
+ toolchain->SetTool(Toolchain::TYPE_COPY, copy_tool.Pass());
+
+ toolchain->ToolchainSetupComplete();
+}
+
void TestWithScope::AppendPrintOutput(const std::string& str) {
print_output_.append(str);
}
diff --git a/tools/gn/test_with_scope.h b/tools/gn/test_with_scope.h
index ce7d95c..449a504 100644
--- a/tools/gn/test_with_scope.h
+++ b/tools/gn/test_with_scope.h
@@ -35,6 +35,12 @@ class TestWithScope {
// threadsafe so don't write tests that call print from multiple threads.
std::string& print_output() { return print_output_; }
+ // Fills in the tools for the given toolchain with reasonable default values.
+ // The toolchain in this object will be automatically set up with this
+ // function, it is exposed to allow tests to get the same functionality for
+ // other toolchains they make
+ static void SetupToolchain(Toolchain* toolchain);
+
private:
void AppendPrintOutput(const std::string& str);
diff --git a/tools/gn/tool.cc b/tools/gn/tool.cc
new file mode 100644
index 0000000..b549283
--- /dev/null
+++ b/tools/gn/tool.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/tool.h"
+
+Tool::Tool()
+ : depsformat_(DEPS_GCC),
+ restat_(false),
+ complete_(false) {
+}
+
+Tool::~Tool() {
+}
+
+void Tool::SetComplete() {
+ DCHECK(!complete_);
+ complete_ = true;
+
+ command_.FillRequiredTypes(&substitution_bits_);
+ depfile_.FillRequiredTypes(&substitution_bits_);
+ description_.FillRequiredTypes(&substitution_bits_);
+ outputs_.FillRequiredTypes(&substitution_bits_);
+ link_output_.FillRequiredTypes(&substitution_bits_);
+ depend_output_.FillRequiredTypes(&substitution_bits_);
+ rspfile_.FillRequiredTypes(&substitution_bits_);
+ rspfile_content_.FillRequiredTypes(&substitution_bits_);
+}
diff --git a/tools/gn/tool.h b/tools/gn/tool.h
new file mode 100644
index 0000000..dae8089
--- /dev/null
+++ b/tools/gn/tool.h
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_TOOL_H_
+#define TOOLS_GN_TOOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "tools/gn/substitution_list.h"
+#include "tools/gn/substitution_pattern.h"
+
+class Tool {
+ public:
+ enum DepsFormat {
+ DEPS_GCC = 0,
+ DEPS_MSVC = 1
+ };
+
+ Tool();
+ ~Tool();
+
+ // Getters/setters ----------------------------------------------------------
+ //
+ // After the tool has had its attributes set, the caller must call
+ // SetComplete(), at which point no other changes can be made.
+
+ // Command to run.
+ const SubstitutionPattern& command() const {
+ return command_;
+ }
+ void set_command(const SubstitutionPattern& cmd) {
+ DCHECK(!complete_);
+ command_ = cmd;
+ }
+
+ // Should include a leading "." if nonempty.
+ const std::string& default_output_extension() const {
+ return default_output_extension_;
+ }
+ void set_default_output_extension(const std::string& ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ default_output_extension_ = ext;
+ }
+
+ // Dependency file (if supported).
+ const SubstitutionPattern& depfile() const {
+ return depfile_;
+ }
+ void set_depfile(const SubstitutionPattern& df) {
+ DCHECK(!complete_);
+ depfile_ = df;
+ }
+
+ DepsFormat depsformat() const {
+ return depsformat_;
+ }
+ void set_depsformat(DepsFormat f) {
+ DCHECK(!complete_);
+ depsformat_ = f;
+ }
+
+ const SubstitutionPattern& description() const {
+ return description_;
+ }
+ void set_description(const SubstitutionPattern& desc) {
+ DCHECK(!complete_);
+ description_ = desc;
+ }
+
+ const std::string& lib_switch() const {
+ return lib_switch_;
+ }
+ void set_lib_switch(const std::string& s) {
+ DCHECK(!complete_);
+ lib_switch_ = s;
+ }
+
+ const std::string& lib_dir_switch() const {
+ return lib_dir_switch_;
+ }
+ void set_lib_dir_switch(const std::string& s) {
+ DCHECK(!complete_);
+ lib_dir_switch_ = s;
+ }
+
+ const SubstitutionList& outputs() const {
+ return outputs_;
+ }
+ void set_outputs(const SubstitutionList& out) {
+ DCHECK(!complete_);
+ outputs_ = out;
+ }
+
+ // Should match files in the outputs() if nonempty.
+ const SubstitutionPattern& link_output() const {
+ return link_output_;
+ }
+ void set_link_output(const SubstitutionPattern& link_out) {
+ DCHECK(!complete_);
+ link_output_ = link_out;
+ }
+
+ const SubstitutionPattern& depend_output() const {
+ return depend_output_;
+ }
+ void set_depend_output(const SubstitutionPattern& dep_out) {
+ DCHECK(!complete_);
+ depend_output_ = dep_out;
+ }
+
+ const std::string& output_prefix() const {
+ return output_prefix_;
+ }
+ void set_output_prefix(const std::string& s) {
+ DCHECK(!complete_);
+ output_prefix_ = s;
+ }
+
+ const std::string& pool() const {
+ return pool_;
+ }
+ void set_pool(const std::string& s) {
+ DCHECK(!complete_);
+ pool_ = s;
+ }
+
+ bool restat() const {
+ return restat_;
+ }
+ void set_restat(bool r) {
+ DCHECK(!complete_);
+ restat_ = r;
+ }
+
+ const SubstitutionPattern& rspfile() const {
+ return rspfile_;
+ }
+ void set_rspfile(const SubstitutionPattern& rsp) {
+ DCHECK(!complete_);
+ rspfile_ = rsp;
+ }
+
+ const SubstitutionPattern& rspfile_content() const {
+ return rspfile_content_;
+ }
+ void set_rspfile_content(const SubstitutionPattern& content) {
+ DCHECK(!complete_);
+ rspfile_content_ = content;
+ }
+
+ // Other functions ----------------------------------------------------------
+
+ // Called when the toolchain is saving this tool, after everything is filled
+ // in.
+ void SetComplete();
+
+ // Returns true if this tool has separate outputs for dependency tracking
+ // and linking.
+ bool has_separate_solink_files() const {
+ return !link_output_.empty() || !depend_output_.empty();
+ }
+
+ // Substitutions required by this tool.
+ const SubstitutionBits& substitution_bits() const {
+ DCHECK(complete_);
+ return substitution_bits_;
+ }
+
+ public:
+ SubstitutionPattern command_;
+ std::string default_output_extension_;
+ SubstitutionPattern depfile_;
+ DepsFormat depsformat_;
+ SubstitutionPattern description_;
+ std::string lib_switch_;
+ std::string lib_dir_switch_;
+ SubstitutionList outputs_;
+ SubstitutionPattern link_output_;
+ SubstitutionPattern depend_output_;
+ std::string output_prefix_;
+ std::string pool_;
+ bool restat_;
+ SubstitutionPattern rspfile_;
+ SubstitutionPattern rspfile_content_;
+
+ bool complete_;
+
+ SubstitutionBits substitution_bits_;
+
+ DISALLOW_COPY_AND_ASSIGN(Tool);
+};
+
+#endif // TOOLS_GN_TOOL_H_
diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc
index eee458a..1171c59 100644
--- a/tools/gn/toolchain.cc
+++ b/tools/gn/toolchain.cc
@@ -4,7 +4,10 @@
#include "tools/gn/toolchain.h"
+#include <string.h>
+
#include "base/logging.h"
+#include "tools/gn/target.h"
#include "tools/gn/value.h"
const char* Toolchain::kToolCc = "cc";
@@ -19,14 +22,9 @@ const char* Toolchain::kToolLink = "link";
const char* Toolchain::kToolStamp = "stamp";
const char* Toolchain::kToolCopy = "copy";
-Toolchain::Tool::Tool() {
-}
-
-Toolchain::Tool::~Tool() {
-}
-
Toolchain::Toolchain(const Settings* settings, const Label& label)
- : Item(settings, label) {
+ : Item(settings, label),
+ setup_complete_(false) {
}
Toolchain::~Toolchain() {
@@ -76,12 +74,84 @@ std::string Toolchain::ToolTypeToName(ToolType type) {
}
}
-const Toolchain::Tool& Toolchain::GetTool(ToolType type) const {
+const Tool* Toolchain::GetTool(ToolType type) const {
DCHECK(type != TYPE_NONE);
- return tools_[static_cast<size_t>(type)];
+ return tools_[static_cast<size_t>(type)].get();
}
-void Toolchain::SetTool(ToolType type, const Tool& t) {
+void Toolchain::SetTool(ToolType type, scoped_ptr<Tool> t) {
DCHECK(type != TYPE_NONE);
- tools_[static_cast<size_t>(type)] = t;
+ DCHECK(!tools_[type].get());
+ t->SetComplete();
+ tools_[type] = t.Pass();
+}
+
+void Toolchain::ToolchainSetupComplete() {
+ // Collect required bits from all tools.
+ for (size_t i = 0; i < TYPE_NUMTYPES; i++) {
+ if (tools_[i])
+ substitution_bits_.MergeFrom(tools_[i]->substitution_bits());
+ }
+
+ setup_complete_ = true;
+}
+
+// static
+Toolchain::ToolType Toolchain::GetToolTypeForSourceType(SourceFileType type) {
+ switch (type) {
+ case SOURCE_C:
+ return TYPE_CC;
+ case SOURCE_CC:
+ return TYPE_CXX;
+ case SOURCE_M:
+ return TYPE_OBJC;
+ case SOURCE_MM:
+ return TYPE_OBJCXX;
+ case SOURCE_ASM:
+ case SOURCE_S:
+ return TYPE_ASM;
+ case SOURCE_RC:
+ return TYPE_RC;
+ case SOURCE_UNKNOWN:
+ case SOURCE_H:
+ case SOURCE_O:
+ return TYPE_NONE;
+ default:
+ NOTREACHED();
+ return TYPE_NONE;
+ }
+}
+
+const Tool* Toolchain::GetToolForSourceType(SourceFileType type) {
+ return tools_[GetToolTypeForSourceType(type)].get();
+}
+
+// static
+Toolchain::ToolType Toolchain::GetToolTypeForTargetFinalOutput(
+ const Target* target) {
+ // The contents of this list might be suprising (i.e. stamp tool for copy
+ // rules). See the header for why.
+ switch (target->output_type()) {
+ case Target::GROUP:
+ return TYPE_STAMP;
+ case Target::EXECUTABLE:
+ return Toolchain::TYPE_LINK;
+ case Target::SHARED_LIBRARY:
+ return Toolchain::TYPE_SOLINK;
+ case Target::STATIC_LIBRARY:
+ return Toolchain::TYPE_ALINK;
+ case Target::SOURCE_SET:
+ return TYPE_STAMP;
+ case Target::COPY_FILES:
+ case Target::ACTION:
+ case Target::ACTION_FOREACH:
+ return TYPE_STAMP;
+ default:
+ NOTREACHED();
+ return Toolchain::TYPE_NONE;
+ }
+}
+
+const Tool* Toolchain::GetToolForTargetFinalOutput(const Target* target) const {
+ return tools_[GetToolTypeForTargetFinalOutput(target)].get();
}
diff --git a/tools/gn/toolchain.h b/tools/gn/toolchain.h
index 4ab5412..8264577 100644
--- a/tools/gn/toolchain.h
+++ b/tools/gn/toolchain.h
@@ -6,10 +6,15 @@
#define TOOLS_GN_TOOLCHAIN_H_
#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
#include "base/strings/string_piece.h"
#include "tools/gn/item.h"
#include "tools/gn/label_ptr.h"
#include "tools/gn/scope.h"
+#include "tools/gn/source_file_type.h"
+#include "tools/gn/substitution_type.h"
+#include "tools/gn/tool.h"
#include "tools/gn/value.h"
// Holds information on a specific toolchain. This data is filled in when we
@@ -21,7 +26,7 @@
// before generating the build for that target.
//
// Note on threadsafety: The label of the toolchain never changes so can
-// safetly be accessed from any thread at any time (we do this when asking for
+// safely be accessed from any thread at any time (we do this when asking for
// the toolchain name). But the values in the toolchain do, so these can't
// be accessed until this Item is resolved.
class Toolchain : public Item {
@@ -55,22 +60,6 @@ class Toolchain : public Item {
static const char* kToolStamp;
static const char* kToolCopy;
- struct Tool {
- Tool();
- ~Tool();
-
- std::string command;
- std::string depfile;
- std::string depsformat;
- std::string description;
- std::string lib_dir_prefix;
- std::string lib_prefix;
- std::string pool;
- std::string restat;
- std::string rspfile;
- std::string rspfile_content;
- };
-
Toolchain(const Settings* settings, const Label& label);
virtual ~Toolchain();
@@ -82,8 +71,15 @@ class Toolchain : public Item {
static ToolType ToolNameToType(const base::StringPiece& str);
static std::string ToolTypeToName(ToolType type);
- const Tool& GetTool(ToolType type) const;
- void SetTool(ToolType type, const Tool& t);
+ // Returns null if the tool hasn't been defined.
+ const Tool* GetTool(ToolType type) const;
+
+ // Set a tool. When all tools are configured, you should call
+ // ToolchainSetupComplete().
+ void SetTool(ToolType type, scoped_ptr<Tool> t);
+
+ // Does final setup on the toolchain once all tools are known.
+ void ToolchainSetupComplete();
// Targets that must be resolved before compiling any targets.
const LabelTargetVector& deps() const { return deps_; }
@@ -96,8 +92,29 @@ class Toolchain : public Item {
Scope::KeyValueMap& args() { return args_; }
const Scope::KeyValueMap& args() const { return args_; }
+ // Returns the tool for compiling the given source file type.
+ static ToolType GetToolTypeForSourceType(SourceFileType type);
+ const Tool* GetToolForSourceType(SourceFileType type);
+
+ // Returns the tool that produces the final output for the given target type.
+ // This isn't necessarily the tool you would expect. For copy target, this
+ // will return the stamp tool ionstead since the final output of a copy
+ // target is to stamp the set of copies done so there is one output.
+ static ToolType GetToolTypeForTargetFinalOutput(const Target* target);
+ const Tool* GetToolForTargetFinalOutput(const Target* target) const;
+
+ const SubstitutionBits& substitution_bits() const {
+ DCHECK(setup_complete_);
+ return substitution_bits_;
+ }
+
private:
- Tool tools_[TYPE_NUMTYPES];
+ scoped_ptr<Tool> tools_[TYPE_NUMTYPES];
+
+ bool setup_complete_;
+
+ // Substitutions used by the tools in this toolchain.
+ SubstitutionBits substitution_bits_;
LabelTargetVector deps_;
Scope::KeyValueMap args_;