diff options
author | brettw <brettw@chromium.org> | 2015-06-29 16:00:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-29 23:01:02 +0000 |
commit | 3dab5fe87ddb0efe646f6ab393ab07533d75237d (patch) | |
tree | 81ca968a3e2cb0bd140cd1de88ad1af8cbab54bd | |
parent | 6cd7b1a4af16d8d5d81d450edc6ac06105ca8f7e (diff) | |
download | chromium_src-3dab5fe87ddb0efe646f6ab393ab07533d75237d.zip chromium_src-3dab5fe87ddb0efe646f6ab393ab07533d75237d.tar.gz chromium_src-3dab5fe87ddb0efe646f6ab393ab07533d75237d.tar.bz2 |
Windows precompiled header support in GN.
Introduces aprecompiled_header_type flag on a tool to say whether it supports precompiled headers, and flags on configs/targets that allow one to specify which header is precompiled.
This does not implement GCC precompiled headers, but the type flag will allow future expansion (the implementation will be mostly separate).
Renames SOURCE_CC to SOURCE_CPP to avoid confusion with Toolchain::TYPE_CC (which is actually the C compiler).
BUG=297678
Review URL: https://codereview.chromium.org/1207903002
Cr-Commit-Position: refs/heads/master@{#336674}
27 files changed, 935 insertions, 195 deletions
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn index e92c3ae..ba165ed 100644 --- a/build/config/BUILD.gn +++ b/build/config/BUILD.gn @@ -396,3 +396,31 @@ config("default_libs") { libs = [ "dl" ] } } + +# Add this config to your target to enable precompiled headers. +# +# On Windows, precompiled headers are done on a per-target basis. If you have +# just a couple of files, the time it takes to precompile (~2 seconds) can +# actually be longer than the time saved. On a Z620, a 100 file target compiles +# about 2 seconds faster with precompiled headers, with greater savings for +# larger targets. +# +# Recommend precompiled headers for targets with more than 50 .cc files. +config("precompiled_headers") { + # TODO(brettw) enable this when GN support in the binary has been rolled. + #if (is_win) { + if (false) { + # This is a string rather than a file GN knows about. It has to match + # exactly what's in the /FI flag below, and what might appear in the source + # code in quotes for an #include directive. + precompiled_header = "build/precompile.h" + + # This is a file that GN will compile with the above header. It will be + # implicitly added to the sources (potentially multiple times, with one + # variant for each language used in the target). + precompiled_source = "//build/precompile.cc" + + # Force include the header. + cflags = [ "/FI$precompiled_header" ] + } +} diff --git a/build/toolchain/win/BUILD.gn b/build/toolchain/win/BUILD.gn index 4936c3d..0236c8e 100644 --- a/build/toolchain/win/BUILD.gn +++ b/build/toolchain/win/BUILD.gn @@ -80,6 +80,9 @@ template("msvc_toolchain") { tool("cc") { rspfile = "{{output}}.rsp" + + # TODO(brettw) enable this when GN support in the binary has been rolled. + #precompiled_header_type = "msvc" pdbname = "{{target_out_dir}}/{{target_output_name}}_c.pdb" command = "ninja -t msvc -e $env -- $cl /nologo /showIncludes /FC @$rspfile /c {{source}} /Fo{{output}} /Fd$pdbname" depsformat = "msvc" @@ -93,6 +96,9 @@ template("msvc_toolchain") { tool("cxx") { rspfile = "{{output}}.rsp" + # TODO(brettw) enable this when GN support in the binary has been rolled. + #precompiled_header_type = "msvc" + # The PDB name needs to be different between C and C++ compiled files. pdbname = "{{target_out_dir}}/{{target_output_name}}_cc.pdb" command = "ninja -t msvc -e $env -- $cl /nologo /showIncludes /FC @$rspfile /c {{source}} /Fo{{output}} /Fd$pdbname" diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn index 036d471..8f9f012 100644 --- a/tools/gn/BUILD.gn +++ b/tools/gn/BUILD.gn @@ -7,6 +7,8 @@ import("//testing/test.gni") defines = [ "GN_BUILD" ] static_library("gn_lib") { + configs += [ "//build/config:precompiled_headers" ] + sources = [ "action_target_generator.cc", "action_target_generator.h", diff --git a/tools/gn/config_values.h b/tools/gn/config_values.h index dec43c6..64c0acc 100644 --- a/tools/gn/config_values.h +++ b/tools/gn/config_values.h @@ -10,6 +10,7 @@ #include "base/basictypes.h" #include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" // Holds the values (include_dirs, defines, compiler flags, etc.) for a given // config or target. @@ -39,6 +40,22 @@ class ConfigValues { #undef STRING_VALUES_ACCESSOR #undef DIR_VALUES_ACCESSOR + bool has_precompiled_headers() const { + return !precompiled_header_.empty() || !precompiled_source_.is_null(); + } + const std::string& precompiled_header() const { + return precompiled_header_; + } + void set_precompiled_header(const std::string& f) { + precompiled_header_ = f; + } + const SourceFile& precompiled_source() const { + return precompiled_source_; + } + void set_precompiled_source(const SourceFile& f) { + precompiled_source_ = f; + } + private: std::vector<std::string> cflags_; std::vector<std::string> cflags_c_; @@ -51,6 +68,8 @@ class ConfigValues { std::vector<SourceDir> lib_dirs_; std::vector<std::string> libs_; + std::string precompiled_header_; + SourceFile precompiled_source_; DISALLOW_COPY_AND_ASSIGN(ConfigValues); }; diff --git a/tools/gn/config_values_extractors.h b/tools/gn/config_values_extractors.h index 4ac2840..5d248fb 100644 --- a/tools/gn/config_values_extractors.h +++ b/tools/gn/config_values_extractors.h @@ -25,7 +25,7 @@ struct EscapeOptions; // // Example: // for (ConfigValueIterator iter(target); !iter.done(); iter.Next()) -// DoSomething(iter->cur()); +// DoSomething(iter.cur()); class ConfigValuesIterator { public: explicit ConfigValuesIterator(const Target* target) diff --git a/tools/gn/config_values_generator.cc b/tools/gn/config_values_generator.cc index f66ddfd..7eb7bc5 100644 --- a/tools/gn/config_values_generator.cc +++ b/tools/gn/config_values_generator.cc @@ -4,6 +4,7 @@ #include "tools/gn/config_values_generator.h" +#include "base/strings/string_util.h" #include "tools/gn/config_values.h" #include "tools/gn/scope.h" #include "tools/gn/settings.h" @@ -81,4 +82,34 @@ void ConfigValuesGenerator::Run() { #undef FILL_STRING_CONFIG_VALUE #undef FILL_DIR_CONFIG_VALUE + + // Precompiled headers. + const Value* precompiled_header_value = + scope_->GetValue(variables::kPrecompiledHeader, true); + if (precompiled_header_value) { + if (!precompiled_header_value->VerifyTypeIs(Value::STRING, err_)) + return; + + // Check for common errors. This is a string and not a file. + const std::string& pch_string = precompiled_header_value->string_value(); + if (base::StartsWith(pch_string, "//", base::CompareCase::SENSITIVE)) { + *err_ = Err(*precompiled_header_value, + "This precompiled_header value is wrong.", + "You need to specify a string that the compiler will match against\n" + "the #include lines rather than a GN-style file name.\n"); + return; + } + config_values_->set_precompiled_header(pch_string); + } + + const Value* precompiled_source_value = + scope_->GetValue(variables::kPrecompiledSource, true); + if (precompiled_source_value) { + config_values_->set_precompiled_source( + input_dir_.ResolveRelativeFile( + *precompiled_source_value, err_, + scope_->settings()->build_settings()->root_path_utf8())); + if (err_->has_error()) + return; + } } diff --git a/tools/gn/config_values_generator.h b/tools/gn/config_values_generator.h index eb8a2e6..4e58010 100644 --- a/tools/gn/config_values_generator.h +++ b/tools/gn/config_values_generator.h @@ -43,6 +43,7 @@ class ConfigValuesGenerator { // For using in documentation for functions which use this. #define CONFIG_VALUES_VARS_HELP \ " Flags: cflags, cflags_c, cflags_cc, cflags_objc, cflags_objcc,\n" \ - " defines, include_dirs, ldflags, lib_dirs, libs\n" + " defines, include_dirs, ldflags, lib_dirs, libs\n" \ + " precompiled_header, precompiled_source\n" #endif // TOOLS_GN_CONFIG_VALUES_GENERATOR_H_ diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc index 6e1c01d..c98db86 100644 --- a/tools/gn/filesystem_utils.cc +++ b/tools/gn/filesystem_utils.cc @@ -744,7 +744,7 @@ OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings, result.value().append(&source_dir.value()[2], source_dir.value().size() - 2); } else { - // system-absolute + // System-absolute. const std::string& build_dir = settings->build_settings()->build_dir().value(); diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc index c4a7037..2fe5f53 100644 --- a/tools/gn/function_toolchain.cc +++ b/tools/gn/function_toolchain.cc @@ -114,6 +114,25 @@ bool ReadOutputExtension(Scope* scope, Tool* tool, Err* err) { return true; } +bool ReadPrecompiledHeaderType(Scope* scope, Tool* tool, Err* err) { + const Value* value = scope->GetValue("precompiled_header_type", 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, do nothing (default is "no PCH"). + + if (value->string_value() == "msvc") { + tool->set_precompiled_header_type(Tool::PCH_MSVC); + return true; + } + *err = Err(*value, "Invalid precompiled_header_type", + "Must either be empty or \"msvc\"."); + return false; +} + bool ReadDepsFormat(Scope* scope, Tool* tool, Err* err) { const Value* value = scope->GetValue("depsformat", true); if (!value) @@ -503,6 +522,20 @@ const char kTool_Help[] = " Posix systems:\n" " output_prefix = \"lib\"\n" "\n" + " precompiled_header_type [string]\n" + " Valid for: \"cc\", \"cxx\", \"objc\", \"objcxx\"\n" + "\n" + " Type of precompiled headers. If undefined or the empty string,\n" + " precompiled headers will not be used for this tool. Otherwise\n" + " use \"msvc\" which is the only currently supported value.\n" + "\n" + " For precompiled headers to be used for a given target, the\n" + " target (or a config applied to it) must also specify a\n" + " \"precompiled_header\" and, for \"msvc\"-style headers, a\n" + " \"precompiled_source\" value.\n" + "\n" + " See \"gn help precompiled_header\" for more.\n" + "\n" " restat [boolean]\n" " Valid for: all tools (optional, defaults to false)\n" "\n" @@ -540,7 +573,7 @@ const char kTool_Help[] = " rspfile_content = \"{{inputs}} {{solibs}} {{libs}}\"\n" " }\n" "\n" - "Expansions for tool variables" + "Expansions for tool variables\n" "\n" " All paths are relative to the root build directory, which is the\n" " current directory for running all tools. These expansions are\n" @@ -784,6 +817,7 @@ Value RunTool(Scope* scope, &Tool::set_depend_output, err) || !ReadString(&block_scope, "output_prefix", tool.get(), &Tool::set_output_prefix, err) || + !ReadPrecompiledHeaderType(&block_scope, tool.get(), err) || !ReadBool(&block_scope, "restat", tool.get(), &Tool::set_restat, err) || !ReadPattern(&block_scope, "rspfile", subst_validator, tool.get(), &Tool::set_rspfile, err) || diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc index d055243..1141e6e 100644 --- a/tools/gn/header_checker.cc +++ b/tools/gn/header_checker.cc @@ -158,7 +158,7 @@ void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) { for (const auto& file : files) { // Only check C-like source files (RC files also have includes). SourceFileType type = GetSourceFileType(file.first); - if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C && + if (type != SOURCE_CPP && type != SOURCE_H && type != SOURCE_C && type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC) continue; diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc index 680c0d9..7b81bfa5 100644 --- a/tools/gn/ninja_binary_target_writer.cc +++ b/tools/gn/ninja_binary_target_writer.cc @@ -4,6 +4,7 @@ #include "tools/gn/ninja_binary_target_writer.h" +#include <cstring> #include <set> #include <sstream> @@ -12,12 +13,33 @@ #include "tools/gn/deps_iterator.h" #include "tools/gn/err.h" #include "tools/gn/escape.h" +#include "tools/gn/filesystem_utils.h" #include "tools/gn/ninja_utils.h" #include "tools/gn/settings.h" +#include "tools/gn/source_file_type.h" #include "tools/gn/string_utils.h" #include "tools/gn/substitution_writer.h" #include "tools/gn/target.h" +// Represents a set of tool types. Must be first since it is also shared by +// some helper functions in the anonymous namespace below. +class NinjaBinaryTargetWriter::SourceFileTypeSet { + public: + SourceFileTypeSet() { + memset(flags_, 0, sizeof(bool) * static_cast<int>(SOURCE_NUMTYPES)); + } + + void Set(SourceFileType type) { + flags_[static_cast<int>(type)] = true; + } + bool Get(SourceFileType type) const { + return flags_[static_cast<int>(type)]; + } + + private: + bool flags_[static_cast<int>(SOURCE_NUMTYPES)]; +}; + namespace { // Returns the proper escape options for writing compiler and linker flags. @@ -65,31 +87,231 @@ struct IncludeWriter { PathOutput& path_output_; }; +// 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) { + 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(target->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(); +} + +// Returns the language-specific prefix/suffix for precomiled header files. +const char* GetPCHLangForToolType(Toolchain::ToolType type) { + switch (type) { + case Toolchain::TYPE_CC: + return "c"; + case Toolchain::TYPE_CXX: + return "cc"; + case Toolchain::TYPE_OBJC: + return "m"; + case Toolchain::TYPE_OBJCXX: + return "mm"; + default: + NOTREACHED() << "Not a valid PCH tool type type"; + return ""; + } +} + +// Returns the object files for the precompiled header of the given type (flag +// type and tool type must match). +void GetWindowsPCHObjectFiles(const Target* target, + Toolchain::ToolType tool_type, + std::vector<OutputFile>* outputs) { + outputs->clear(); + + // Compute the tool. This must use the tool type passed in rather than the + // detected file type of the precompiled source file since the same + // precompiled source file will be used for separate C/C++ compiles. + const Tool* tool = target->toolchain()->GetTool(tool_type); + if (!tool) + return; + SubstitutionWriter::ApplyListToCompilerAsOutputFile( + target, target->config_values().precompiled_source(), + tool->outputs(), outputs); + + if (outputs->empty()) + return; + if (outputs->size() > 1) + outputs->resize(1); // Only link the first output from the compiler tool. + + // Need to annotate the obj files with the language type. For example: + // obj/foo/target_name.precompile.obj -> + // obj/foo/target_name.precompile.cc.obj + const char* lang_suffix = GetPCHLangForToolType(tool_type); + std::string& output_value = (*outputs)[0].value(); + size_t extension_offset = FindExtensionOffset(output_value); + if (extension_offset == std::string::npos) { + NOTREACHED() << "No extension found"; + } else { + DCHECK(extension_offset >= 1); + DCHECK(output_value[extension_offset - 1] == '.'); + output_value.insert(extension_offset - 1, "."); + output_value.insert(extension_offset, lang_suffix); + } +} + +// Appends the object files generated by the given source set to the given +// output vector. +void AddSourceSetObjectFiles(const Target* source_set, + UniqueVector<OutputFile>* obj_files) { + std::vector<OutputFile> tool_outputs; // Prevent allocation in loop. + NinjaBinaryTargetWriter::SourceFileTypeSet used_types; + + // Compute object files for all sources. Only link the first output from + // the tool if there are more than one. + for (const auto& source : source_set->sources()) { + Toolchain::ToolType tool_type = Toolchain::TYPE_NONE; + if (GetOutputFilesForSource(source_set, source, &tool_type, &tool_outputs)) + obj_files->push_back(tool_outputs[0]); + + used_types.Set(GetSourceFileType(source)); + } + + // Precompiled header object files. + if (source_set->config_values().has_precompiled_headers()) { + if (used_types.Get(SOURCE_C)) { + GetWindowsPCHObjectFiles(source_set, Toolchain::TYPE_CC, &tool_outputs); + obj_files->Append(tool_outputs.begin(), tool_outputs.end()); + } + if (used_types.Get(SOURCE_CPP)) { + GetWindowsPCHObjectFiles(source_set, Toolchain::TYPE_CXX, &tool_outputs); + obj_files->Append(tool_outputs.begin(), tool_outputs.end()); + } + if (used_types.Get(SOURCE_M)) { + GetWindowsPCHObjectFiles(source_set, Toolchain::TYPE_OBJC, &tool_outputs); + obj_files->Append(tool_outputs.begin(), tool_outputs.end()); + } + if (used_types.Get(SOURCE_MM)) { + GetWindowsPCHObjectFiles(source_set, Toolchain::TYPE_OBJCXX, + &tool_outputs); + obj_files->Append(tool_outputs.begin(), tool_outputs.end()); + } + } +} + } // namespace NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target, std::ostream& out) : NinjaTargetWriter(target, out), - tool_(target->toolchain()->GetToolForTargetFinalOutput(target)) { + tool_(target->toolchain()->GetToolForTargetFinalOutput(target)), + rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) { } NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() { } void NinjaBinaryTargetWriter::Run() { - WriteCompilerVars(); + // Figure out what source types are needed. + SourceFileTypeSet used_types; + for (const auto& source : target_->sources()) + used_types.Set(GetSourceFileType(source)); + + WriteCompilerVars(used_types); + + // The input dependencies will be an order-only dependency. This will cause + // Ninja to make sure the inputs are up-to-date before compiling this source, + // but changes in the inputs deps won't cause the file to be recompiled. + // + // This is important to prevent changes in unrelated actions that are + // upstream of this target from causing everything to be recompiled + // + // Why can we get away with this rather than using implicit deps ("|", which + // will force rebuilds when the inputs change)? For source code, the + // computed dependencies of all headers will be computed by the compiler, + // which will cause source rebuilds if any "real" upstream dependencies + // change. + // + // If a .cc file is generated by an input dependency, Ninja will see the + // input to the build rule doesn't exist, and that it is an output from a + // previous step, and build the previous step first. This is a "real" + // dependency and doesn't need | or || to express. + // + // The only case where this rule matters is for the first build where no .d + // files exist, and Ninja doesn't know what that source file depends on. In + // this case it's sufficient to ensure that the upstream dependencies are + // built first. This is exactly what Ninja's order-only dependencies + // expresses. + OutputFile order_only_dep = + WriteInputDepsStampAndGetDep(std::vector<const Target*>()); + + std::vector<OutputFile> pch_obj_files; + WritePrecompiledHeaderCommands(used_types, order_only_dep, &pch_obj_files); + // Treat all precompiled object files as explicit dependencies of all + // compiles. Some notes: + // + // - Technically only the language-specific one is required for any specific + // compile, but that's more difficult to express and the additional logic + // doesn't buy much reduced parallelism. Just list them all (there's + // usually only one anyway). + // + // - Technically the .pch file is the input to the compile, not the + // precompiled header's corresponding object file that we're using here. + // But Ninja's depslog doesn't support multiple outputs from the + // precompiled header compile step (it outputs both the .pch file and a + // corresponding .obj file). So we consistently list the .obj file and the + // .pch file we really need comes along with it. std::vector<OutputFile> obj_files; std::vector<SourceFile> other_files; - WriteSources(&obj_files, &other_files); + WriteSources(pch_obj_files, order_only_dep, &obj_files, &other_files); - if (target_->output_type() == Target::SOURCE_SET) + // Also link all pch object files. + obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end()); + + if (target_->output_type() == Target::SOURCE_SET) { WriteSourceSetStamp(obj_files); - else +#ifndef NDEBUG + // Verify that the function that separately computes a source set's object + // files match the object files just computed. + UniqueVector<OutputFile> computed_obj; + AddSourceSetObjectFiles(target_, &computed_obj); + DCHECK_EQ(obj_files.size(), computed_obj.size()); + for (const auto& obj : obj_files) + DCHECK_NE(static_cast<size_t>(-1), computed_obj.IndexOf(obj)); +#endif + } else { WriteLinkerStuff(obj_files, other_files); + } } -void NinjaBinaryTargetWriter::WriteCompilerVars() { +void NinjaBinaryTargetWriter::WriteCompilerVars( + const SourceFileTypeSet& used_types) { const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); // Defines. @@ -113,36 +335,134 @@ void NinjaBinaryTargetWriter::WriteCompilerVars() { out_ << std::endl; } - // C flags and friends. - EscapeOptions flag_escape_options = GetFlagOptions(); -#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; \ + bool has_precompiled_headers = + target_->config_values().has_precompiled_headers(); + + // Some toolchains pass cflags to the assembler since it's the same command, + // and cflags_c might also be sent to the objective C compiler. + // + // TODO(brettw) remove the SOURCE_M from the CFLAGS_C writing once the Chrome + // Mac build is updated not to pass cflags_c to .m files. + EscapeOptions opts = GetFlagOptions(); + if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) || + used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM) || + used_types.Get(SOURCE_ASM)) { + WriteOneFlag(SUBSTITUTION_CFLAGS, false, Toolchain::TYPE_NONE, + &ConfigValues::cflags, opts); + } + if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_M) || + used_types.Get(SOURCE_ASM)) { + WriteOneFlag(SUBSTITUTION_CFLAGS_C, has_precompiled_headers, + Toolchain::TYPE_CC, &ConfigValues::cflags_c, opts); + } + if (used_types.Get(SOURCE_CPP)) { + WriteOneFlag(SUBSTITUTION_CFLAGS_CC, has_precompiled_headers, + Toolchain::TYPE_CXX, &ConfigValues::cflags_cc, opts); + } + if (used_types.Get(SOURCE_M)) { + WriteOneFlag(SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers, + Toolchain::TYPE_OBJC, &ConfigValues::cflags_objc, opts); + } + if (used_types.Get(SOURCE_MM)) { + WriteOneFlag(SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers, + Toolchain::TYPE_OBJCXX, &ConfigValues::cflags_objcc, opts); + } + + WriteSharedVars(subst); +} + +void NinjaBinaryTargetWriter::WriteOneFlag( + SubstitutionType subst_enum, + bool has_precompiled_headers, + Toolchain::ToolType tool_type, + const std::vector<std::string>& (ConfigValues::* getter)() const, + EscapeOptions flag_escape_options) { + if (!target_->toolchain()->substitution_bits().used[subst_enum]) + return; + + out_ << kSubstitutionNinjaNames[subst_enum] << " ="; + + if (has_precompiled_headers) { + const Tool* tool = target_->toolchain()->GetTool(tool_type); + if (tool && tool->precompiled_header_type() == Tool::PCH_MSVC) { + // Name the .pch file. + out_ << " /Fp"; + path_output_.WriteFile(out_, GetWindowsPCHFile(tool_type)); + + // Enables precompiled headers and names the .h file. It's a string + // rather than a file name (so no need to rebase or use path_output_). + out_ << " /Yu" << target_->config_values().precompiled_header(); } + } + + RecursiveTargetConfigStringsToStream(target_, getter, + flag_escape_options, out_); + out_ << std::endl; +} - 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) +void NinjaBinaryTargetWriter::WritePrecompiledHeaderCommands( + const SourceFileTypeSet& used_types, + const OutputFile& order_only_dep, + std::vector<OutputFile>* object_files) { + if (!target_->config_values().has_precompiled_headers()) + return; -#undef WRITE_FLAGS + const Tool* tool_c = target_->toolchain()->GetTool(Toolchain::TYPE_CC); + if (tool_c && + tool_c->precompiled_header_type() == Tool::PCH_MSVC && + used_types.Get(SOURCE_C)) { + WriteWindowsPCHCommand(SUBSTITUTION_CFLAGS_C, + Toolchain::TYPE_CC, + order_only_dep, object_files); + } + const Tool* tool_cxx = target_->toolchain()->GetTool(Toolchain::TYPE_CXX); + if (tool_cxx && + tool_cxx->precompiled_header_type() == Tool::PCH_MSVC && + used_types.Get(SOURCE_CPP)) { + WriteWindowsPCHCommand(SUBSTITUTION_CFLAGS_CC, + Toolchain::TYPE_CXX, + order_only_dep, object_files); + } +} - WriteSharedVars(subst); +void NinjaBinaryTargetWriter::WriteWindowsPCHCommand( + SubstitutionType flag_type, + Toolchain::ToolType tool_type, + const OutputFile& order_only_dep, + std::vector<OutputFile>* object_files) { + // Compute the object file (it will be language-specific). + std::vector<OutputFile> outputs; + GetWindowsPCHObjectFiles(target_, tool_type, &outputs); + if (outputs.empty()) + return; + object_files->insert(object_files->end(), outputs.begin(), outputs.end()); + + // Build line to compile the file. + WriteCompilerBuildLine(target_->config_values().precompiled_source(), + std::vector<OutputFile>(), order_only_dep, tool_type, + outputs); + + // This build line needs a custom language-specific flags value. It needs to + // include the switch to generate the .pch file in addition to the normal + // ones. Rule-specific variables are just indented underneath the rule line, + // and this defines the new one in terms of the old value. + out_ << " " << kSubstitutionNinjaNames[flag_type] << " ="; + out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}"; + + // Append the command to generate the .pch file. + out_ << " /Yc" << target_->config_values().precompiled_header(); + + // Write two blank lines to help separate the PCH build lines from the + // regular source build lines. + out_ << std::endl << std::endl; } void NinjaBinaryTargetWriter::WriteSources( + const std::vector<OutputFile>& extra_deps, + const OutputFile& order_only_dep, std::vector<OutputFile>* object_files, std::vector<SourceFile>* other_files) { - object_files->reserve(target_->sources().size()); - - OutputFile input_dep = - WriteInputDepsStampAndGetDep(std::vector<const Target*>()); - - std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_); + object_files->reserve(object_files->size() + target_->sources().size()); std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop. for (const auto& source : target_->sources()) { @@ -154,41 +474,8 @@ void NinjaBinaryTargetWriter::WriteSources( } 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_, source); - if (!input_dep.value().empty()) { - // Write out the input dependencies as an order-only dependency. This - // will cause Ninja to make sure the inputs are up-to-date before - // compiling this source, but changes in the inputs deps won't cause - // the file to be recompiled. - // - // This is important to prevent changes in unrelated actions that - // are upstream of this target from causing everything to be recompiled. - // - // Why can we get away with this rather than using implicit deps ("|", - // which will force rebuilds when the inputs change)? For source code, - // the computed dependencies of all headers will be computed by the - // compiler, which will cause source rebuilds if any "real" upstream - // dependencies change. - // - // If a .cc file is generated by an input dependency, Ninja will see - // the input to the build rule doesn't exist, and that it is an output - // from a previous step, and build the previous step first. This is a - // "real" dependency and doesn't need | or || to express. - // - // The only case where this rule matters is for the first build where - // no .d files exist, and Ninja doesn't know what that source file - // depends on. In this case it's sufficient to ensure that the upstream - // dependencies are built first. This is exactly what Ninja's order- - // only dependencies expresses. - out_ << " || "; - path_output_.WriteFile(out_, input_dep); - } - out_ << std::endl; + WriteCompilerBuildLine(source, extra_deps, order_only_dep, tool_type, + tool_outputs); } // It's theoretically possible for a compiler to produce more than one @@ -198,6 +485,34 @@ void NinjaBinaryTargetWriter::WriteSources( out_ << std::endl; } +void NinjaBinaryTargetWriter::WriteCompilerBuildLine( + const SourceFile& source, + const std::vector<OutputFile>& extra_deps, + const OutputFile& order_only_dep, + Toolchain::ToolType tool_type, + const std::vector<OutputFile>& outputs) { + out_ << "build"; + path_output_.WriteFiles(out_, outputs); + + out_ << ": " << rule_prefix_ << Toolchain::ToolTypeToName(tool_type); + out_ << " "; + path_output_.WriteFile(out_, source); + + if (!extra_deps.empty()) { + out_ << " |"; + for (const OutputFile& dep : extra_deps) { + out_ << " "; + path_output_.WriteFile(out_, dep); + } + } + + if (!order_only_dep.value().empty()) { + out_ << " || "; + path_output_.WriteFile(out_, order_only_dep); + } + out_ << std::endl; +} + void NinjaBinaryTargetWriter::WriteLinkerStuff( const std::vector<OutputFile>& object_files, const std::vector<SourceFile>& other_files) { @@ -208,8 +523,7 @@ void NinjaBinaryTargetWriter::WriteLinkerStuff( out_ << "build"; path_output_.WriteFiles(out_, output_files); - out_ << ": " - << GetNinjaRulePrefixForToolchain(settings_) + out_ << ": " << rule_prefix_ << Toolchain::ToolTypeToName( target_->toolchain()->GetToolTypeForTargetFinalOutput(target_)); @@ -219,18 +533,12 @@ void NinjaBinaryTargetWriter::WriteLinkerStuff( GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps); // Object files. - for (const auto& obj : object_files) { - out_ << " "; - path_output_.WriteFile(out_, obj); - } - for (const auto& obj : extra_object_files) { - out_ << " "; - path_output_.WriteFile(out_, obj); - } + path_output_.WriteFiles(out_, object_files); + path_output_.WriteFiles(out_, extra_object_files); + // Dependencies. std::vector<OutputFile> implicit_deps; std::vector<OutputFile> solibs; - for (const Target* cur : linkable_deps) { // All linkable deps should have a link output file. DCHECK(!cur->link_output_file().value().empty()) @@ -442,18 +750,8 @@ void NinjaBinaryTargetWriter::ClassifyDependency( // just forward the dependency, otherwise the files in the source // set can easily get linked more than once which will cause // multiple definition errors. - if (can_link_libs) { - // Linking in a source set to an executable, shared library, or - // complete static library, so copy its object files. - std::vector<OutputFile> tool_outputs; // Prevent allocation in loop. - for (const auto& source : dep->sources()) { - Toolchain::ToolType tool_type = Toolchain::TYPE_NONE; - if (GetOutputFilesForSource(dep, source, &tool_type, &tool_outputs)) { - // Only link the first output if there are more than one. - extra_object_files->push_back(tool_outputs[0]); - } - } - } + if (can_link_libs) + AddSourceSetObjectFiles(dep, extra_object_files); // Add the source set itself as a non-linkable dependency on the current // target. This will make sure that anything the source set's stamp file @@ -481,33 +779,15 @@ void NinjaBinaryTargetWriter::WriteOrderOnlyDependencies( } } -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(); +OutputFile NinjaBinaryTargetWriter::GetWindowsPCHFile( + Toolchain::ToolType tool_type) const { + // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up + // looking like "obj/chrome/browser/browser.cc.pch" + OutputFile ret = GetTargetOutputDirAsOutputFile(target_); + ret.value().append(target_->label().name()); + ret.value().push_back('_'); + ret.value().append(GetPCHLangForToolType(tool_type)); + ret.value().append(".pch"); + + return ret; } diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h index 977e982..fe7a132 100644 --- a/tools/gn/ninja_binary_target_writer.h +++ b/tools/gn/ninja_binary_target_writer.h @@ -12,11 +12,14 @@ #include "tools/gn/unique_vector.h" struct EscapeOptions; +class SourceFileTypeSet; // Writes a .ninja file for a binary target type (an executable, a shared // library, or a static library). class NinjaBinaryTargetWriter : public NinjaTargetWriter { public: + class SourceFileTypeSet; + NinjaBinaryTargetWriter(const Target* target, std::ostream& out); ~NinjaBinaryTargetWriter() override; @@ -25,9 +28,57 @@ class NinjaBinaryTargetWriter : public NinjaTargetWriter { private: typedef std::set<OutputFile> OutputFileSet; - void WriteCompilerVars(); - void WriteSources(std::vector<OutputFile>* object_files, + // Writes all flags for the compiler: includes, defines, cflags, etc. + void WriteCompilerVars(const SourceFileTypeSet& used_types); + + // has_precompiled_headers is set when this substitution matches a tool type + // that supports precompiled headers, and this target supports precompiled + // headers. It doesn't indicate if the tool has precompiled headers (this + // will be looked up by this function). + // + // The tool_type indicates the corresponding tool for flags that are + // tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g. + // "defines") tool_type should be TYPE_NONE. + void WriteOneFlag( + SubstitutionType subst_enum, + bool has_precompiled_headers, + Toolchain::ToolType tool_type, + const std::vector<std::string>& (ConfigValues::* getter)() const, + EscapeOptions flag_escape_options); + + // Writes build lines required for precompiled headers. Any generated + // object files will be appended to the given vector. + // + // input_dep is the stamp file collecting the dependencies required before + // compiling this target. It will be empty if there are no input deps. + void WritePrecompiledHeaderCommands(const SourceFileTypeSet& used_types, + const OutputFile& input_dep, + std::vector<OutputFile>* object_files); + + // Writes a Windows .pch compile build line for a language type. + void WriteWindowsPCHCommand(SubstitutionType flag_type, + Toolchain::ToolType tool_type, + const OutputFile& input_dep, + std::vector<OutputFile>* object_files); + + // extra_deps are additional dependencies to run before the rule. + // + // iorder_only_dep is the name of the stamp file that covers the dependencies + // that must be run before doing any compiles. + // + // The files produced by the compiler will be added to two output vectors. + void WriteSources(const std::vector<OutputFile>& extra_deps, + const OutputFile& order_only_dep, + std::vector<OutputFile>* object_files, std::vector<SourceFile>* other_files); + + // Writes a build line. + void WriteCompilerBuildLine(const SourceFile& source, + const std::vector<OutputFile>& extra_deps, + const OutputFile& order_only_dep, + Toolchain::ToolType tool_type, + const std::vector<OutputFile>& outputs); + void WriteLinkerStuff(const std::vector<OutputFile>& object_files, const std::vector<SourceFile>& other_files); void WriteLinkerFlags(const SourceFile* optional_def_file); @@ -61,25 +112,15 @@ class NinjaBinaryTargetWriter : public NinjaTargetWriter { void WriteOrderOnlyDependencies( const UniqueVector<const Target*>& non_linkable_deps); - // 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; + // Returns the computed name of the Windows .pch file for the given + // tool type. The tool must support precompiled headers. + OutputFile GetWindowsPCHFile(Toolchain::ToolType tool_type) const; const Tool* tool_; + // Cached version of the prefix used for rule types for this toolchain. + std::string rule_prefix_; + 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 f4e4065..2ad816a 100644 --- a/tools/gn/ninja_binary_target_writer_unittest.cc +++ b/tools/gn/ninja_binary_target_writer_unittest.cc @@ -38,10 +38,7 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = bar\n" @@ -70,11 +67,6 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { const char expected[] = "defines =\n" "include_dirs =\n" - "cflags =\n" - "cflags_c =\n" - "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libshlib\n" @@ -108,11 +100,6 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { const char expected[] = "defines =\n" "include_dirs =\n" - "cflags =\n" - "cflags_c =\n" - "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libstlib\n" @@ -138,11 +125,6 @@ TEST(NinjaBinaryTargetWriter, SourceSet) { const char expected[] = "defines =\n" "include_dirs =\n" - "cflags =\n" - "cflags_c =\n" - "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libstlib\n" @@ -196,10 +178,7 @@ TEST(NinjaBinaryTargetWriter, ProductExtensionAndInputDeps) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libshlib\n" @@ -249,10 +228,7 @@ TEST(NinjaBinaryTargetWriter, EmptyProductExtension) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libshlib\n" @@ -305,10 +281,7 @@ TEST(NinjaBinaryTargetWriter, SourceSetDataDeps) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = inter\n" @@ -340,10 +313,7 @@ TEST(NinjaBinaryTargetWriter, SourceSetDataDeps) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = exe\n" @@ -380,10 +350,7 @@ TEST(NinjaBinaryTargetWriter, SharedLibraryModuleDefinitionFile) { "defines =\n" "include_dirs =\n" "cflags =\n" - "cflags_c =\n" "cflags_cc =\n" - "cflags_objc =\n" - "cflags_objcc =\n" "root_out_dir = .\n" "target_out_dir = obj/foo\n" "target_output_name = libbar\n" @@ -396,3 +363,99 @@ TEST(NinjaBinaryTargetWriter, SharedLibraryModuleDefinitionFile) { " output_extension = .so\n"; EXPECT_EQ(expected, out.str()); } + +TEST(NinjaBinaryTargetWriter, WinPrecompiledHeaders) { + Err err; + + // This setup's toolchain does not have precompiled headers defined. + TestWithScope setup; + + // A precompiled header toolchain. + Settings pch_settings(setup.build_settings(), "withpch/"); + Toolchain pch_toolchain(&pch_settings, + Label(SourceDir("//toolchain/"), "withpch")); + + // Declare a C++ compiler that supports PCH. + scoped_ptr<Tool> cxx_tool(new Tool); + TestWithScope::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")); + cxx_tool->set_precompiled_header_type(Tool::PCH_MSVC); + pch_toolchain.SetTool(Toolchain::TYPE_CXX, cxx_tool.Pass()); + pch_toolchain.ToolchainSetupComplete(); + + // This target doesn't specify precompiled headers. + { + Target no_pch_target(&pch_settings, + Label(SourceDir("//foo/"), "no_pch_target")); + no_pch_target.set_output_type(Target::SOURCE_SET); + no_pch_target.visibility().SetPublic(); + no_pch_target.sources().push_back(SourceFile("//foo/input1.cc")); + no_pch_target.SetToolchain(&pch_toolchain); + ASSERT_TRUE(no_pch_target.OnResolved(&err)); + + std::ostringstream out; + NinjaBinaryTargetWriter writer(&no_pch_target, out); + writer.Run(); + + const char no_pch_expected[] = + "defines =\n" + "include_dirs =\n" + "cflags =\n" + "cflags_cc =\n" + "target_output_name = no_pch_target\n" + "\n" + "build withpch/obj/foo/no_pch_target.input1.o: " + "cxx ../../foo/input1.cc\n" + "\n" + "build withpch/obj/foo/no_pch_target.stamp: " + "stamp withpch/obj/foo/no_pch_target.input1.o\n"; + EXPECT_EQ(no_pch_expected, out.str()); + } + + // This target specifies PCH. + { + Target pch_target(&pch_settings, + Label(SourceDir("//foo/"), "pch_target")); + pch_target.config_values().set_precompiled_header("build/precompile.h"); + pch_target.config_values().set_precompiled_source( + SourceFile("//build/precompile.cc")); + pch_target.set_output_type(Target::SOURCE_SET); + pch_target.visibility().SetPublic(); + pch_target.sources().push_back(SourceFile("//foo/input1.cc")); + pch_target.SetToolchain(&pch_toolchain); + ASSERT_TRUE(pch_target.OnResolved(&err)); + + std::ostringstream out; + NinjaBinaryTargetWriter writer(&pch_target, out); + writer.Run(); + + const char pch_win_expected[] = + "defines =\n" + "include_dirs =\n" + "cflags =\n" + // There should only be one .pch file created, for C++ files. + "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch " + "/Yubuild/precompile.h\n" + "target_output_name = pch_target\n" + "\n" + // Compile the precompiled source file with /Yc. + "build withpch/obj/build/pch_target.precompile.cc.o: " + "cxx ../../build/precompile.cc\n" + " cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n" + "\n" + "build withpch/obj/foo/pch_target.input1.o: " + "cxx ../../foo/input1.cc | " + // Explicit dependency on the PCH build step. + "withpch/obj/build/pch_target.precompile.cc.o\n" + "\n" + "build withpch/obj/foo/pch_target.stamp: " + "stamp withpch/obj/foo/pch_target.input1.o " + // The precompiled object file was added to the outputs. + "withpch/obj/build/pch_target.precompile.cc.o\n"; + EXPECT_EQ(pch_win_expected, out.str()); + } +} diff --git a/tools/gn/path_output.cc b/tools/gn/path_output.cc index 5b333d8..711341f 100644 --- a/tools/gn/path_output.cc +++ b/tools/gn/path_output.cc @@ -83,6 +83,14 @@ void PathOutput::WriteFiles(std::ostream& out, } } +void PathOutput::WriteFiles(std::ostream& out, + const UniqueVector<OutputFile>& files) const { + for (const auto& file : files) { + out << " "; + WriteFile(out, file); + } +} + 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 f7beb42..46c1aa9 100644 --- a/tools/gn/path_output.h +++ b/tools/gn/path_output.h @@ -12,6 +12,7 @@ #include "base/strings/string_piece.h" #include "tools/gn/escape.h" #include "tools/gn/source_dir.h" +#include "tools/gn/unique_vector.h" class OutputFile; class SourceFile; @@ -55,6 +56,8 @@ class PathOutput { // write an initial space before the first item. void WriteFiles(std::ostream& out, const std::vector<OutputFile>& files) const; + void WriteFiles(std::ostream& out, + const UniqueVector<OutputFile>& files) const; // This variant assumes the dir ends in a trailing slash or is empty. void WriteDir(std::ostream& out, diff --git a/tools/gn/source_file_type.cc b/tools/gn/source_file_type.cc index 2b34125..72b872c 100644 --- a/tools/gn/source_file_type.cc +++ b/tools/gn/source_file_type.cc @@ -10,7 +10,7 @@ SourceFileType GetSourceFileType(const SourceFile& file) { base::StringPiece extension = FindExtension(&file.value()); if (extension == "cc" || extension == "cpp" || extension == "cxx") - return SOURCE_CC; + return SOURCE_CPP; if (extension == "h") return SOURCE_H; if (extension == "c") diff --git a/tools/gn/source_file_type.h b/tools/gn/source_file_type.h index f738daa..c43b432 100644 --- a/tools/gn/source_file_type.h +++ b/tools/gn/source_file_type.h @@ -7,11 +7,13 @@ class SourceFile; +// This should be sequential integers starting from 0 so they can be used as +// array indices. enum SourceFileType { - SOURCE_UNKNOWN, + SOURCE_UNKNOWN = 0, SOURCE_ASM, SOURCE_C, - SOURCE_CC, + SOURCE_CPP, SOURCE_H, SOURCE_M, SOURCE_MM, @@ -19,6 +21,9 @@ enum SourceFileType { SOURCE_RC, SOURCE_O, // Object files can be inputs, too. Also counts .obj. SOURCE_DEF, + + // Must be last. + SOURCE_NUMTYPES, }; SourceFileType GetSourceFileType(const SourceFile& file); diff --git a/tools/gn/target.cc b/tools/gn/target.cc index 0b189e6..a7a3d07 100644 --- a/tools/gn/target.cc +++ b/tools/gn/target.cc @@ -170,6 +170,8 @@ bool Target::OnResolved(Err* err) { PullDependentTargets(); PullForwardedDependentConfigs(); PullRecursiveHardDeps(); + if (!ResolvePrecompiledHeaders(err)) + return false; FillOutputFiles(); @@ -215,12 +217,12 @@ std::string Target::GetComputedOutputName(bool include_prefix) const { 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 (!base::StartsWithASCII(name, prefix, true)) - result = prefix; + if (tool) { + // Only add the prefix if the name doesn't already have it. + if (!base::StartsWithASCII(name, tool->output_prefix(), true)) + result = tool->output_prefix(); + } } - result.append(name); return result; } @@ -429,6 +431,60 @@ void Target::FillOutputFiles() { computed_outputs_.push_back(OutputFile(settings()->build_settings(), out)); } +bool Target::ResolvePrecompiledHeaders(Err* err) { + // Precompiled headers are stored on a ConfigValues struct. This way, the + // build can set all the precompiled header settings in a config and apply + // it to many targets. Likewise, the precompiled header values may be + // specified directly on a target. + // + // Unlike other values on configs which are lists that just get concatenated, + // the precompiled header settings are unique values. We allow them to be + // specified anywhere, but if they are specified in more than one place all + // places must match. + + // Track where the current settings came from for issuing errors. + const Label* pch_header_settings_from = NULL; + if (config_values_.has_precompiled_headers()) + pch_header_settings_from = &label(); + + for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) { + if (!iter.GetCurrentConfig()) + continue; // Skip the one on the target itself. + + const Config* config = iter.GetCurrentConfig(); + const ConfigValues& cur = config->config_values(); + if (!cur.has_precompiled_headers()) + continue; // This one has no precompiled header info, skip. + + if (config_values_.has_precompiled_headers()) { + // Already have a precompiled header values, the settings must match. + if (config_values_.precompiled_header() != cur.precompiled_header() || + config_values_.precompiled_source() != cur.precompiled_source()) { + *err = Err(defined_from(), + "Precompiled header setting conflict.", + "The target " + label().GetUserVisibleName(false) + "\n" + "has conflicting precompiled header settings.\n" + "\n" + "From " + pch_header_settings_from->GetUserVisibleName(false) + + "\n header: " + config_values_.precompiled_header() + + "\n source: " + config_values_.precompiled_source().value() + + "\n\n" + "From " + config->label().GetUserVisibleName(false) + + "\n header: " + cur.precompiled_header() + + "\n source: " + cur.precompiled_source().value()); + return false; + } + } else { + // Have settings from a config, apply them to ourselves. + pch_header_settings_from = &config->label(); + config_values_.set_precompiled_header(cur.precompiled_header()); + config_values_.set_precompiled_source(cur.precompiled_source()); + } + } + + return true; +} + bool Target::CheckVisibility(Err* err) const { for (const auto& pair : GetDeps(DEPS_ALL)) { if (!Visibility::CheckItemVisibility(this, pair.ptr, err)) diff --git a/tools/gn/target.h b/tools/gn/target.h index 58319f1..e8758b7 100644 --- a/tools/gn/target.h +++ b/tools/gn/target.h @@ -10,6 +10,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/gtest_prod_util.h" #include "base/logging.h" #include "base/strings/string_piece.h" #include "base/synchronization/lock.h" @@ -252,6 +253,8 @@ class Target : public Item { } private: + FRIEND_TEST_ALL_PREFIXES(Target, ResolvePrecompiledHeaders); + // Pulls necessary information from dependencies to this one when all // dependencies have been resolved. void PullDependentTarget(const Target* dep, bool is_public); @@ -266,6 +269,10 @@ class Target : public Item { // Fills the link and dependency output files when a target is resolved. void FillOutputFiles(); + // Checks precompiled headers from configs and makes sure the resulting + // values are in config_values_. + bool ResolvePrecompiledHeaders(Err* err); + // Validates the given thing when a target is resolved. bool CheckVisibility(Err* err) const; bool CheckTestonly(Err* err) const; @@ -310,8 +317,13 @@ class Target : public Item { // target is marked resolved. This will not include the current target. std::set<const Target*> recursive_hard_deps_; - ConfigValues config_values_; // Used for all binary targets. - ActionValues action_values_; // Used for action[_foreach] targets. + // Used for all binary targets. The precompiled header values in this struct + // will be resolved to the ones to use for this target, if precompiled + // headers are used. + ConfigValues config_values_; + + // Used for action[_foreach] targets. + ActionValues action_values_; // Toolchain used by this target. Null until target is resolved. const Toolchain* toolchain_; diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc index 48fb84c..c9e0b1e 100644 --- a/tools/gn/target_unittest.cc +++ b/tools/gn/target_unittest.cc @@ -558,3 +558,58 @@ TEST(Target, WriteFileGeneratedInputs) { // Should be OK. EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty()); } + +TEST(Target, ResolvePrecompiledHeaders) { + TestWithScope setup; + Err err; + + Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); + + // Target with no settings, no configs, should be a no-op. + EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err)); + + // Config with PCH values. + Config config_1(setup.settings(), Label(SourceDir("//foo/"), "c1")); + std::string pch_1("pch.h"); + SourceFile pcs_1("//pcs.cc"); + config_1.config_values().set_precompiled_header(pch_1); + config_1.config_values().set_precompiled_source(pcs_1); + target.configs().push_back(LabelConfigPair(&config_1)); + + // No PCH info specified on target, but the config specifies one, the + // values should get copied to the target. + EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err)); + EXPECT_EQ(pch_1, target.config_values().precompiled_header()); + EXPECT_TRUE(target.config_values().precompiled_source() == pcs_1); + + // Now both target and config have matching PCH values. Resolving again + // should be a no-op since they all match. + EXPECT_TRUE(target.ResolvePrecompiledHeaders(&err)); + EXPECT_TRUE(target.config_values().precompiled_header() == pch_1); + EXPECT_TRUE(target.config_values().precompiled_source() == pcs_1); + + // Second config with different PCH values. + Config config_2(setup.settings(), Label(SourceDir("//foo/"), "c2")); + std::string pch_2("pch2.h"); + SourceFile pcs_2("//pcs2.cc"); + config_2.config_values().set_precompiled_header(pch_2); + config_2.config_values().set_precompiled_source(pcs_2); + target.configs().push_back(LabelConfigPair(&config_2)); + + // This should be an error since they don't match. + EXPECT_FALSE(target.ResolvePrecompiledHeaders(&err)); + + // Make sure the proper labels are blamed. + EXPECT_EQ( + "The target //foo:bar\n" + "has conflicting precompiled header settings.\n" + "\n" + "From //foo:bar\n" + " header: pch.h\n" + " source: //pcs.cc\n" + "\n" + "From //foo:c2\n" + " header: pch2.h\n" + " source: //pcs2.cc", + err.help_text()); +} diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc index 847af62..d37b722 100644 --- a/tools/gn/test_with_scope.cc +++ b/tools/gn/test_with_scope.cc @@ -8,19 +8,6 @@ #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, nullptr, &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()), @@ -132,6 +119,16 @@ void TestWithScope::SetupToolchain(Toolchain* toolchain) { toolchain->ToolchainSetupComplete(); } +// static +void TestWithScope::SetCommandForTool(const std::string& cmd, Tool* tool) { + Err err; + SubstitutionPattern command; + command.Parse(cmd, nullptr, &err); + CHECK(!err.has_error()) + << "Couldn't parse \"" << cmd << "\", " << "got " << err.message(); + tool->set_command(command); +} + 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 79c6a08..e390df8 100644 --- a/tools/gn/test_with_scope.h +++ b/tools/gn/test_with_scope.h @@ -43,9 +43,14 @@ class TestWithScope { // 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 + // other toolchains they make. static void SetupToolchain(Toolchain* toolchain); + // Sets the given text command on the given tool, parsing it as a + // substitution pattern. This will assert if the input is malformed. This is + // designed to help setting up Tools for tests. + static void SetCommandForTool(const std::string& cmd, Tool* tool); + private: void AppendPrintOutput(const std::string& str); diff --git a/tools/gn/tool.cc b/tools/gn/tool.cc index b549283..139c032 100644 --- a/tools/gn/tool.cc +++ b/tools/gn/tool.cc @@ -6,6 +6,7 @@ Tool::Tool() : depsformat_(DEPS_GCC), + precompiled_header_type_(PCH_NONE), restat_(false), complete_(false) { } diff --git a/tools/gn/tool.h b/tools/gn/tool.h index 14a64c1..b9575bc 100644 --- a/tools/gn/tool.h +++ b/tools/gn/tool.h @@ -19,6 +19,11 @@ class Tool { DEPS_MSVC = 1 }; + enum PrecompiledHeaderType { + PCH_NONE = 0, + PCH_MSVC = 1 + }; + Tool(); ~Tool(); @@ -63,6 +68,13 @@ class Tool { depsformat_ = f; } + PrecompiledHeaderType precompiled_header_type() const { + return precompiled_header_type_; + } + void set_precompiled_header_type(PrecompiledHeaderType pch_type) { + precompiled_header_type_ = pch_type; + } + const SubstitutionPattern& description() const { return description_; } @@ -167,6 +179,7 @@ class Tool { std::string default_output_extension_; SubstitutionPattern depfile_; DepsFormat depsformat_; + PrecompiledHeaderType precompiled_header_type_; SubstitutionPattern description_; std::string lib_switch_; std::string lib_dir_switch_; diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc index 8ec2c2d..cdd70c0 100644 --- a/tools/gn/toolchain.cc +++ b/tools/gn/toolchain.cc @@ -102,7 +102,7 @@ Toolchain::ToolType Toolchain::GetToolTypeForSourceType(SourceFileType type) { switch (type) { case SOURCE_C: return TYPE_CC; - case SOURCE_CC: + case SOURCE_CPP: return TYPE_CXX; case SOURCE_M: return TYPE_OBJC; diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc index 51a68d5..472554a 100644 --- a/tools/gn/variables.cc +++ b/tools/gn/variables.cc @@ -854,6 +854,76 @@ const char kOutputs_Help[] = " file(s). For actions, the outputs should be the list of files\n" " generated by the script.\n"; +const char kPrecompiledHeader[] = "precompiled_header"; +const char kPrecompiledHeader_HelpShort[] = + "precompiled_header: [string] Header file to precompile."; +const char kPrecompiledHeader_Help[] = + "precompiled_header: [string] Header file to precompile.\n" + "\n" + " Precompiled headers will be used when a target specifies this\n" + " value, or a config applying to this target specifies this value.\n" + " In addition, the tool corresponding to the source files must also\n" + " specify precompiled headers (see \"gn help tool\"). The tool\n" + " will also specify what type of precompiled headers to use.\n" + "\n" + " The precompiled header/source variables can be specified on a target\n" + " or a config, but must be the same for all configs applying to a given\n" + " target since a target can only have one precompiled header.\n" + "\n" + "MSVC precompiled headers\n" + "\n" + " When using MSVC-style precompiled headers, the \"precompiled_header\"\n" + " value is a string corresponding to the header. This is NOT a path\n" + " to a file that GN recognises, but rather the exact string that appears\n" + " in quotes after an #include line in source code. The compiler will\n" + " match this string against includes or forced includes (/FI).\n" + "\n" + " MSVC also requires a source file to compile the header with. This must\n" + " be specified by the \"precompiled_source\" value. In contrast to the\n" + " header value, this IS a GN-style file name, and tells GN which source\n" + " file to compile to make the .pch file used for subsequent compiles.\n" + "\n" + " If you use both C and C++ sources, the precompiled header and source\n" + " file will be compiled using both tools. You will want to make sure\n" + " to wrap C++ includes in __cplusplus #ifdefs so the file will compile\n" + " in C mode.\n" + "\n" + " For example, if the toolchain specifies MSVC headers:\n" + "\n" + " toolchain(\"vc_x64\") {\n" + " ...\n" + " tool(\"cxx\") {\n" + " precompiled_header_type = \"msvc\"\n" + " ...\n" + "\n" + " You might make a config like this:\n" + "\n" + " config(\"use_precompiled_headers\") {\n" + " precompiled_header = \"build/precompile.h\"\n" + " precompiled_source = \"//build/precompile.cc\"\n" + "\n" + " # Either your source files should #include \"build/precompile.h\"\n" + " # first, or you can do this to force-include the header.\n" + " cflags = [ \"/FI$precompiled_header\" ]\n" + " }\n" + "\n" + " And then define a target that uses the config:\n" + "\n" + " executable(\"doom_melon\") {\n" + " configs += [ \":use_precompiled_headers\" ]\n" + " ...\n" + "\n"; + +const char kPrecompiledSource[] = "precompiled_source"; +const char kPrecompiledSource_HelpShort[] = + "precompiled_source: [file name] Source file to precompile."; +const char kPrecompiledSource_Help[] = + "precompiled_source: [file name] Source file to precompile.\n" + "\n" + " The source file that goes along with the precompiled_header when\n" + " using \"msvc\"-style precompiled headers. It will be implicitly added\n" + " to the sources of the target. See \"gn help precompiled_header\".\n"; + const char kPublic[] = "public"; const char kPublic_HelpShort[] = "public: [file list] Declare public header files for a target."; @@ -1121,6 +1191,8 @@ const VariableInfoMap& GetTargetVariables() { INSERT_VARIABLE(OutputExtension) INSERT_VARIABLE(OutputName) INSERT_VARIABLE(Outputs) + INSERT_VARIABLE(PrecompiledHeader) + INSERT_VARIABLE(PrecompiledSource) INSERT_VARIABLE(Public) INSERT_VARIABLE(PublicConfigs) INSERT_VARIABLE(PublicDeps) diff --git a/tools/gn/variables.h b/tools/gn/variables.h index 57d6a98..4d8c4b4 100644 --- a/tools/gn/variables.h +++ b/tools/gn/variables.h @@ -171,6 +171,14 @@ extern const char kOutputs[]; extern const char kOutputs_HelpShort[]; extern const char kOutputs_Help[]; +extern const char kPrecompiledHeader[]; +extern const char kPrecompiledHeader_HelpShort[]; +extern const char kPrecompiledHeader_Help[]; + +extern const char kPrecompiledSource[]; +extern const char kPrecompiledSource_HelpShort[]; +extern const char kPrecompiledSource_Help[]; + extern const char kPublic[]; extern const char kPublic_HelpShort[]; extern const char kPublic_Help[]; |