diff options
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_; |