diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-19 22:52:16 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-19 22:54:04 +0000 |
commit | 0dfcae783a9285e3a3099f7098f076ef7b093f6e (patch) | |
tree | 543118667a7393e30934ae7c14050d4686219657 | |
parent | c9a47cccedaa0556b52c1ceeb6c6a0d0b2f018f9 (diff) | |
download | chromium_src-0dfcae783a9285e3a3099f7098f076ef7b093f6e.zip chromium_src-0dfcae783a9285e3a3099f7098f076ef7b093f6e.tar.gz chromium_src-0dfcae783a9285e3a3099f7098f076ef7b093f6e.tar.bz2 |
Support more configurability in GN toolchains
This uses substitution patterns in toolchains to allow the toolchain to specify more flexibly how files are to be named and generated at each step. The toolchain now has control over the naming of object and executable files, for example, where before these were hardcoded.
This removes most of the OS-specific logic hardcoded into the GN tool. There is still a bunch in action invocation; this will be done in a followup.
R=jamesr@chromium.org
Review URL: https://codereview.chromium.org/440333002
Cr-Commit-Position: refs/heads/master@{#290685}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@290685 0039d316-1c4b-4281-b951-d872f2087c98
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_; |