Move C logic to child class of NinjaBinaryTargetWriter
Change-Id: I0faf5027c790189993ab3bf4561c757c298674e1
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4463
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/build/gen.py b/build/gen.py
index 75210d7..eeb7cb5 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -491,6 +491,7 @@
'tools/gn/ninja_binary_target_writer.cc',
'tools/gn/ninja_build_writer.cc',
'tools/gn/ninja_bundle_data_target_writer.cc',
+ 'tools/gn/ninja_c_binary_target_writer.cc',
'tools/gn/ninja_copy_target_writer.cc',
'tools/gn/ninja_create_bundle_target_writer.cc',
'tools/gn/ninja_generated_file_target_writer.cc',
@@ -591,6 +592,7 @@
'tools/gn/metadata_walk_unittest.cc',
'tools/gn/ninja_action_target_writer_unittest.cc',
'tools/gn/ninja_binary_target_writer_unittest.cc',
+ 'tools/gn/ninja_c_binary_target_writer_unittest.cc',
'tools/gn/ninja_build_writer_unittest.cc',
'tools/gn/ninja_bundle_data_target_writer_unittest.cc',
'tools/gn/ninja_copy_target_writer_unittest.cc',
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 9f0e949..4cda834 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -18,7 +18,7 @@
#include "tools/gn/err.h"
#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
-#include "tools/gn/general_tool.h"
+#include "tools/gn/ninja_c_binary_target_writer.h"
#include "tools/gn/ninja_target_command_util.h"
#include "tools/gn/ninja_utils.h"
#include "tools/gn/scheduler.h"
@@ -28,865 +28,25 @@
#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.
-EscapeOptions GetFlagOptions() {
- EscapeOptions opts;
- opts.mode = ESCAPE_NINJA_COMMAND;
- return opts;
+bool NinjaBinaryTargetWriter::SourceFileTypeSet::CSourceUsed() {
+ return Get(SOURCE_CPP) || Get(SOURCE_H) || Get(SOURCE_C) || Get(SOURCE_M) ||
+ Get(SOURCE_MM) || Get(SOURCE_RC) || Get(SOURCE_S);
}
-// Returns the language-specific lang recognized by gcc’s -x flag for
-// precompiled header files.
-const char* GetPCHLangForToolType(const char* name) {
- if (name == CTool::kCToolCc)
- return "c-header";
- if (name == CTool::kCToolCxx)
- return "c++-header";
- if (name == CTool::kCToolObjC)
- return "objective-c-header";
- if (name == CTool::kCToolObjCxx)
- return "objective-c++-header";
- NOTREACHED() << "Not a valid PCH tool type: " << name;
- return "";
-}
-
-// 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()) {
- const char* tool_name = Tool::kToolNone;
- if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
- obj_files->push_back(tool_outputs[0]);
-
- used_types.Set(GetSourceFileType(source));
- }
-
- // Add MSVC precompiled header object files. GCC .gch files are not object
- // files so they are omitted.
- if (source_set->config_values().has_precompiled_headers()) {
- if (used_types.Get(SOURCE_C)) {
- const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
- if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
- GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
- obj_files->Append(tool_outputs.begin(), tool_outputs.end());
- }
- }
- if (used_types.Get(SOURCE_CPP)) {
- const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
- if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
- GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
- obj_files->Append(tool_outputs.begin(), tool_outputs.end());
- }
- }
- if (used_types.Get(SOURCE_M)) {
- const CTool* tool =
- source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
- if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
- GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
- obj_files->Append(tool_outputs.begin(), tool_outputs.end());
- }
- }
- if (used_types.Get(SOURCE_MM)) {
- const CTool* tool =
- source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
- if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
- GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &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()->GetToolForTargetFinalOutputAsC(target)),
rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) {}
NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default;
void NinjaBinaryTargetWriter::Run() {
- // Figure out what source types are needed.
SourceFileTypeSet used_types;
for (const auto& source : target_->sources())
used_types.Set(GetSourceFileType(source));
- WriteCompilerVars(used_types);
-
- OutputFile input_dep = WriteInputsStampAndGetDep();
-
- // 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.
- //
- // The order only deps are referenced by each source file compile,
- // but also by PCH compiles. The latter are annoying to count, so omit
- // them here. This means that binary targets with a single source file
- // that also use PCH files won't have a stamp file even though having
- // one would make output ninja file size a bit lower. That's ok, binary
- // targets with a single source are rare.
- size_t num_stamp_uses = target_->sources().size();
- std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
- std::vector<const Target*>(), num_stamp_uses);
-
- // For GCC builds, the .gch files are not object files, but still need to be
- // added as explicit dependencies below. The .gch output files are placed in
- // |pch_other_files|. This is to prevent linking against them.
- std::vector<OutputFile> pch_obj_files;
- std::vector<OutputFile> pch_other_files;
- WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
- &pch_other_files);
- std::vector<OutputFile>* pch_files =
- !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
-
- // Treat all pch output files as explicit dependencies of all
- // compiles that support them. Some notes:
- //
- // - On Windows, 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.
- //
- // - GCC .gch files are not object files, therefore they are not added to the
- // object file list.
- std::vector<OutputFile> obj_files;
- std::vector<SourceFile> other_files;
- WriteSources(*pch_files, input_dep, order_only_deps, &obj_files,
- &other_files);
-
- // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
- obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
- if (!CheckForDuplicateObjectFiles(obj_files))
- return;
-
- if (target_->output_type() == Target::SOURCE_SET) {
- WriteSourceSetStamp(obj_files);
-#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, input_dep);
+ if (used_types.CSourceUsed()) {
+ NinjaCBinaryTargetWriter writer(target_, out_);
+ writer.Run();
}
}
-
-void NinjaBinaryTargetWriter::WriteCompilerVars(
- const SourceFileTypeSet& used_types) {
- const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
-
- // Defines.
- if (subst.used[SUBSTITUTION_DEFINES]) {
- out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
- RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
- DefineWriter(), out_);
- out_ << std::endl;
- }
-
- // Include directories.
- if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
- out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
- PathOutput include_path_output(
- path_output_.current_dir(),
- settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
- RecursiveTargetConfigToStream<SourceDir>(
- target_, &ConfigValues::include_dirs,
- IncludeWriter(include_path_output), out_);
- out_ << std::endl;
- }
-
- bool has_precompiled_headers =
- target_->config_values().has_precompiled_headers();
-
- EscapeOptions opts = GetFlagOptions();
- if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) {
- WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Tool::kToolNone,
- &ConfigValues::asmflags, opts, path_output_, out_);
- }
- if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) ||
- used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) {
- WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
- &ConfigValues::cflags, opts, path_output_, out_);
- }
- if (used_types.Get(SOURCE_C)) {
- WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
- CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
- out_);
- }
- if (used_types.Get(SOURCE_CPP)) {
- WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
- CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
- out_);
- }
- if (used_types.Get(SOURCE_M)) {
- WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
- CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
- path_output_, out_);
- }
- if (used_types.Get(SOURCE_MM)) {
- WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
- CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
- path_output_, out_);
- }
-
- WriteSharedVars(subst);
-}
-
-OutputFile NinjaBinaryTargetWriter::WriteInputsStampAndGetDep() const {
- CHECK(target_->toolchain()) << "Toolchain not set on target "
- << target_->label().GetUserVisibleName(true);
-
- std::vector<const SourceFile*> inputs;
- for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
- for (const auto& input : iter.cur().inputs()) {
- inputs.push_back(&input);
- }
- }
-
- if (inputs.size() == 0)
- return OutputFile(); // No inputs
-
- // If we only have one input, return it directly instead of writing a stamp
- // file for it.
- if (inputs.size() == 1)
- return OutputFile(settings_->build_settings(), *inputs[0]);
-
- // Make a stamp file.
- OutputFile input_stamp_file =
- GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
- input_stamp_file.value().append(target_->label().name());
- input_stamp_file.value().append(".inputs.stamp");
-
- out_ << "build ";
- path_output_.WriteFile(out_, input_stamp_file);
- out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
- << GeneralTool::kGeneralToolStamp;
-
- // File inputs.
- for (const auto* input : inputs) {
- out_ << " ";
- path_output_.WriteFile(out_, *input);
- }
-
- out_ << "\n";
- return input_stamp_file;
-}
-
-void NinjaBinaryTargetWriter::WritePCHCommands(
- const SourceFileTypeSet& used_types,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files,
- std::vector<OutputFile>* other_files) {
- if (!target_->config_values().has_precompiled_headers())
- return;
-
- const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
- if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
- used_types.Get(SOURCE_C)) {
- WritePCHCommand(SUBSTITUTION_CFLAGS_C, CTool::kCToolCc,
- tool_c->precompiled_header_type(), input_dep,
- order_only_deps, object_files, other_files);
- }
- const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
- if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
- used_types.Get(SOURCE_CPP)) {
- WritePCHCommand(SUBSTITUTION_CFLAGS_CC, CTool::kCToolCxx,
- tool_cxx->precompiled_header_type(), input_dep,
- order_only_deps, object_files, other_files);
- }
-
- const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
- if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
- used_types.Get(SOURCE_M)) {
- WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, CTool::kCToolObjC,
- tool_objc->precompiled_header_type(), input_dep,
- order_only_deps, object_files, other_files);
- }
-
- const CTool* tool_objcxx =
- target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
- if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
- used_types.Get(SOURCE_MM)) {
- WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, CTool::kCToolObjCxx,
- tool_objcxx->precompiled_header_type(), input_dep,
- order_only_deps, object_files, other_files);
- }
-}
-
-void NinjaBinaryTargetWriter::WritePCHCommand(
- SubstitutionType flag_type,
- const char* tool_name,
- CTool::PrecompiledHeaderType header_type,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files,
- std::vector<OutputFile>* other_files) {
- switch (header_type) {
- case CTool::PCH_MSVC:
- WriteWindowsPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
- object_files);
- break;
- case CTool::PCH_GCC:
- WriteGCCPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
- other_files);
- break;
- case CTool::PCH_NONE:
- NOTREACHED() << "Cannot write a PCH command with no PCH header type";
- break;
- }
-}
-
-void NinjaBinaryTargetWriter::WriteGCCPCHCommand(
- SubstitutionType flag_type,
- const char* tool_name,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* gch_files) {
- // Compute the pch output file (it will be language-specific).
- std::vector<OutputFile> outputs;
- GetPCHOutputFiles(target_, tool_name, &outputs);
- if (outputs.empty())
- return;
-
- gch_files->insert(gch_files->end(), outputs.begin(), outputs.end());
-
- std::vector<OutputFile> extra_deps;
- if (!input_dep.value().empty())
- extra_deps.push_back(input_dep);
-
- // Build line to compile the file.
- WriteCompilerBuildLine(target_->config_values().precompiled_source(),
- extra_deps, order_only_deps, tool_name, outputs);
-
- // This build line needs a custom language-specific flags value. Rule-specific
- // variables are just indented underneath the rule line.
- out_ << " " << kSubstitutionNinjaNames[flag_type] << " =";
-
- // Each substitution flag is overwritten in the target rule to replace the
- // implicitly generated -include flag with the -x <header lang> flag required
- // for .gch targets.
- EscapeOptions opts = GetFlagOptions();
- if (tool_name == CTool::kCToolCc) {
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
- out_);
- } else if (tool_name == CTool::kCToolCxx) {
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
- opts, out_);
- } else if (tool_name == CTool::kCToolObjC) {
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
- opts, out_);
- } else if (tool_name == CTool::kCToolObjCxx) {
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
- opts, out_);
- }
-
- // Append the command to specify the language of the .gch file.
- out_ << " -x " << GetPCHLangForToolType(tool_name);
-
- // Write two blank lines to help separate the PCH build lines from the
- // regular source build lines.
- out_ << std::endl << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteWindowsPCHCommand(
- SubstitutionType flag_type,
- const char* tool_name,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files) {
- // Compute the pch output file (it will be language-specific).
- std::vector<OutputFile> outputs;
- GetPCHOutputFiles(target_, tool_name, &outputs);
- if (outputs.empty())
- return;
-
- object_files->insert(object_files->end(), outputs.begin(), outputs.end());
-
- std::vector<OutputFile> extra_deps;
- if (!input_dep.value().empty())
- extra_deps.push_back(input_dep);
-
- // Build line to compile the file.
- WriteCompilerBuildLine(target_->config_values().precompiled_source(),
- extra_deps, order_only_deps, tool_name, outputs);
-
- // This build line needs a custom language-specific flags value. Rule-specific
- // variables are just indented underneath the rule line.
- out_ << " " << kSubstitutionNinjaNames[flag_type] << " =";
-
- // Append the command to generate the .pch file.
- // This adds the value to the existing flag instead of overwriting it.
- out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}";
- 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>& pch_deps,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files,
- std::vector<SourceFile>* other_files) {
- object_files->reserve(object_files->size() + target_->sources().size());
-
- std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
- std::vector<OutputFile> deps;
- for (const auto& source : target_->sources()) {
- // Clear the vector but maintain the max capacity to prevent reallocations.
- deps.resize(0);
- const char* tool_name = Tool::kToolNone;
- if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
- if (GetSourceFileType(source) == SOURCE_DEF)
- other_files->push_back(source);
- continue; // No output for this source.
- }
-
- if (!input_dep.value().empty())
- deps.push_back(input_dep);
-
- if (tool_name != Tool::kToolNone) {
- // Only include PCH deps that correspond to the tool type, for instance,
- // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep
- // for the output of a C tool type.
- //
- // This makes the assumption that pch_deps only contains pch output files
- // with the naming scheme specified in GetWindowsPCHObjectExtension or
- // GetGCCPCHOutputExtension.
- const CTool* tool = target_->toolchain()->GetToolAsC(tool_name);
- if (tool->precompiled_header_type() != CTool::PCH_NONE) {
- for (const auto& dep : pch_deps) {
- const std::string& output_value = dep.value();
- size_t extension_offset = FindExtensionOffset(output_value);
- if (extension_offset == std::string::npos)
- continue;
- std::string output_extension;
- if (tool->precompiled_header_type() == CTool::PCH_MSVC) {
- output_extension = GetWindowsPCHObjectExtension(
- tool_name, output_value.substr(extension_offset - 1));
- } else if (tool->precompiled_header_type() == CTool::PCH_GCC) {
- output_extension = GetGCCPCHOutputExtension(tool_name);
- }
- if (output_value.compare(
- output_value.size() - output_extension.size(),
- output_extension.size(), output_extension) == 0) {
- deps.push_back(dep);
- }
- }
- }
- WriteCompilerBuildLine(source, deps, order_only_deps, tool_name,
- tool_outputs);
- }
-
- // 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::WriteCompilerBuildLine(
- const SourceFile& source,
- const std::vector<OutputFile>& extra_deps,
- const std::vector<OutputFile>& order_only_deps,
- const char* tool_name,
- const std::vector<OutputFile>& outputs) {
- out_ << "build";
- path_output_.WriteFiles(out_, outputs);
-
- out_ << ": " << rule_prefix_ << tool_name;
- out_ << " ";
- path_output_.WriteFile(out_, source);
-
- if (!extra_deps.empty()) {
- out_ << " |";
- path_output_.WriteFiles(out_, extra_deps);
- }
-
- if (!order_only_deps.empty()) {
- out_ << " ||";
- path_output_.WriteFiles(out_, order_only_deps);
- }
- out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteLinkerStuff(
- const std::vector<OutputFile>& object_files,
- const std::vector<SourceFile>& other_files,
- const OutputFile& input_dep) {
- std::vector<OutputFile> output_files;
- SubstitutionWriter::ApplyListToLinkerAsOutputFile(
- target_, tool_, tool_->outputs(), &output_files);
-
- out_ << "build";
- path_output_.WriteFiles(out_, output_files);
-
- out_ << ": " << rule_prefix_
- << Tool::GetToolTypeForTargetFinalOutput(target_);
-
- 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.
- 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())
- << "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());
- }
- }
-
- const SourceFile* optional_def_file = nullptr;
- if (!other_files.empty()) {
- for (const SourceFile& src_file : other_files) {
- if (GetSourceFileType(src_file) == SOURCE_DEF) {
- optional_def_file = &src_file;
- implicit_deps.push_back(
- OutputFile(settings_->build_settings(), src_file));
- break; // Only one def file is allowed.
- }
- }
- }
-
- // Libraries specified by paths.
- const OrderedSet<LibFile>& libs = target_->all_libs();
- for (size_t i = 0; i < libs.size(); i++) {
- if (libs[i].is_source_file()) {
- implicit_deps.push_back(
- OutputFile(settings_->build_settings(), libs[i].source_file()));
- }
- }
-
- // The input dependency is only needed if there are no object files, as the
- // dependency is normally provided transitively by the source files.
- if (!input_dep.value().empty() && object_files.empty())
- implicit_deps.push_back(input_dep);
-
- // Append implicit dependencies collected above.
- if (!implicit_deps.empty()) {
- out_ << " |";
- path_output_.WriteFiles(out_, implicit_deps);
- }
-
- // Append data dependencies as order-only dependencies.
- //
- // This will include data dependencies and input dependencies (like when
- // this target depends on an action). Having the data dependencies in this
- // list ensures that the data is available at runtime when the user builds
- // this target.
- //
- // The action dependencies are not strictly necessary in this case. They
- // should also have been collected via the input deps stamp that each source
- // file has for an order-only dependency, and since this target depends on
- // the sources, there is already an implicit order-only dependency. However,
- // it's extra work to separate these out and there's no disadvantage to
- // listing them again.
- WriteOrderOnlyDependencies(non_linkable_deps);
-
- // End of the link "build" line.
- out_ << std::endl;
-
- // The remaining things go in the inner scope of the link line.
- if (target_->output_type() == Target::EXECUTABLE ||
- target_->output_type() == Target::SHARED_LIBRARY ||
- target_->output_type() == Target::LOADABLE_MODULE) {
- WriteLinkerFlags(optional_def_file);
- WriteLibs();
- } else if (target_->output_type() == Target::STATIC_LIBRARY) {
- out_ << " arflags =";
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
- GetFlagOptions(), out_);
- out_ << std::endl;
- }
- WriteOutputSubstitutions();
- WriteSolibs(solibs);
-}
-
-void NinjaBinaryTargetWriter::WriteLinkerFlags(
- const SourceFile* optional_def_file) {
- out_ << " ldflags =";
-
- // First the ldflags from the target and its config.
- RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
- GetFlagOptions(), out_);
-
- // Followed by library search paths that have been recursively pushed
- // through the dependency tree.
- const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
- if (!all_lib_dirs.empty()) {
- // Since we're passing these on the command line to the linker and not
- // to Ninja, we need to do shell escaping.
- PathOutput lib_path_output(path_output_.current_dir(),
- settings_->build_settings()->root_path_utf8(),
- ESCAPE_NINJA_COMMAND);
- for (size_t i = 0; i < all_lib_dirs.size(); i++) {
- out_ << " " << tool_->lib_dir_switch();
- lib_path_output.WriteDir(out_, all_lib_dirs[i],
- PathOutput::DIR_NO_LAST_SLASH);
- }
- }
-
- if (optional_def_file) {
- out_ << " /DEF:";
- path_output_.WriteFile(out_, *optional_def_file);
- }
-
- out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteLibs() {
- out_ << " libs =";
-
- // Libraries that have been recursively pushed through the dependency tree.
- EscapeOptions lib_escape_opts;
- lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
- const OrderedSet<LibFile> all_libs = target_->all_libs();
- const std::string framework_ending(".framework");
- for (size_t i = 0; i < all_libs.size(); i++) {
- const LibFile& lib_file = all_libs[i];
- const std::string& lib_value = lib_file.value();
- if (lib_file.is_source_file()) {
- out_ << " ";
- path_output_.WriteFile(out_, lib_file.source_file());
- } else if (base::EndsWith(lib_value, framework_ending,
- base::CompareCase::INSENSITIVE_ASCII)) {
- // Special-case libraries ending in ".framework" to support Mac: Add the
- // -framework switch and don't add the extension to the output.
- out_ << " -framework ";
- EscapeStringToStream(
- out_, lib_value.substr(0, lib_value.size() - framework_ending.size()),
- lib_escape_opts);
- } else {
- out_ << " " << tool_->lib_switch();
- EscapeStringToStream(out_, lib_value, lib_escape_opts);
- }
- }
- out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteOutputSubstitutions() {
- out_ << " output_extension = "
- << SubstitutionWriter::GetLinkerSubstitution(
- target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
- out_ << std::endl;
- out_ << " output_dir = "
- << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
- SUBSTITUTION_OUTPUT_DIR);
- out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteSolibs(
- const std::vector<OutputFile>& solibs) {
- if (solibs.empty())
- return;
-
- out_ << " solibs =";
- path_output_.WriteFiles(out_, solibs);
- out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteSourceSetStamp(
- const std::vector<OutputFile>& object_files) {
- // The stamp rule for source sets is generally not used, since targets that
- // 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.
- 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);
-
- // The classifier should never put extra object files in a source set:
- // any source sets that we depend on should appear in our non-linkable
- // deps instead.
- DCHECK(extra_object_files.empty());
-
- std::vector<OutputFile> order_only_deps;
- for (auto* dep : non_linkable_deps)
- order_only_deps.push_back(dep->dependency_output_file());
-
- WriteStampForTarget(object_files, order_only_deps);
-}
-
-void NinjaBinaryTargetWriter::GetDeps(
- UniqueVector<OutputFile>* extra_object_files,
- UniqueVector<const Target*>* linkable_deps,
- UniqueVector<const Target*>* non_linkable_deps) const {
- // Normal public/private deps.
- for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
- ClassifyDependency(pair.ptr, extra_object_files, linkable_deps,
- non_linkable_deps);
- }
-
- // Inherited libraries.
- for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
- ClassifyDependency(inherited_target, extra_object_files, linkable_deps,
- non_linkable_deps);
- }
-
- // Data deps.
- for (const auto& data_dep_pair : target_->data_deps())
- non_linkable_deps->push_back(data_dep_pair.ptr);
-}
-
-void NinjaBinaryTargetWriter::ClassifyDependency(
- const Target* dep,
- UniqueVector<OutputFile>* extra_object_files,
- UniqueVector<const Target*>* linkable_deps,
- UniqueVector<const Target*>* non_linkable_deps) const {
- // Only the following types of outputs have libraries linked into them:
- // EXECUTABLE
- // SHARED_LIBRARY
- // _complete_ STATIC_LIBRARY
- //
- // Child deps of intermediate static libraries get pushed up the
- // dependency tree until one of these is reached, and source sets
- // don't link at all.
- bool can_link_libs = target_->IsFinal();
-
- if (dep->output_type() == Target::SOURCE_SET ||
- // If a complete static library depends on an incomplete static library,
- // manually link in the object files of the dependent library as if it
- // were a source set. This avoids problems with braindead tools such as
- // ar which don't properly link dependent static libraries.
- (target_->complete_static_lib() &&
- dep->output_type() == Target::STATIC_LIBRARY &&
- !dep->complete_static_lib())) {
- // Source sets have their object files linked into final targets
- // (shared libraries, executables, loadable modules, and complete static
- // libraries). Intermediate static libraries and other source sets
- // 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)
- 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
- // depends on (like data deps) are also built before the current target
- // can be complete. Otherwise, these will be skipped since this target
- // will depend only on the source set's object files.
- non_linkable_deps->push_back(dep);
- } else if (target_->complete_static_lib() && dep->IsFinal()) {
- non_linkable_deps->push_back(dep);
- } else if (can_link_libs && dep->IsLinkable()) {
- linkable_deps->push_back(dep);
- } else {
- non_linkable_deps->push_back(dep);
- }
-}
-
-void NinjaBinaryTargetWriter::WriteOrderOnlyDependencies(
- const UniqueVector<const Target*>& non_linkable_deps) {
- if (!non_linkable_deps.empty()) {
- out_ << " ||";
-
- // Non-linkable targets.
- for (auto* non_linkable_dep : non_linkable_deps) {
- out_ << " ";
- path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
- }
- }
-}
-
-bool NinjaBinaryTargetWriter::CheckForDuplicateObjectFiles(
- const std::vector<OutputFile>& files) const {
- std::unordered_set<std::string> set;
- for (const auto& file : files) {
- if (!set.insert(file.value()).second) {
- Err err(
- target_->defined_from(), "Duplicate object file",
- "The target " + target_->label().GetUserVisibleName(false) +
- "\ngenerates two object files with the same name:\n " +
- file.value() +
- "\n"
- "\n"
- "It could be you accidentally have a file listed twice in the\n"
- "sources. Or, depending on how your toolchain maps sources to\n"
- "object files, two source files with the same name in different\n"
- "directories could map to the same object file.\n"
- "\n"
- "In the latter case, either rename one of the files or move one "
- "of\n"
- "the sources to a separate source_set to avoid them both being "
- "in\n"
- "the same target.");
- g_scheduler->FailWithError(err);
- return false;
- }
- }
- return true;
-}
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index 8a31219..d450ce2 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -19,119 +19,32 @@
// library, or a static library).
class NinjaBinaryTargetWriter : public NinjaTargetWriter {
public:
- class SourceFileTypeSet;
+ // Represents a set of tool types.
+ class 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)];
+ }
+
+ bool CSourceUsed();
+
+ private:
+ bool flags_[static_cast<int>(SOURCE_NUMTYPES)];
+ };
NinjaBinaryTargetWriter(const Target* target, std::ostream& out);
~NinjaBinaryTargetWriter() override;
void Run() override;
- private:
+ protected:
typedef std::set<OutputFile> OutputFileSet;
- // Writes all flags for the compiler: includes, defines, cflags, etc.
- void WriteCompilerVars(const SourceFileTypeSet& used_types);
-
- // Writes to the output stream a stamp rule for inputs, and
- // returns the file to be appended to source rules that encodes the
- // implicit dependencies for the current target. The returned OutputFile
- // will be empty if there are no inputs.
- OutputFile WriteInputsStampAndGetDep() const;
-
- // Writes build lines required for precompiled headers. Any generated
- // object files will be appended to the |object_files|. Any generated
- // non-object files (for instance, .gch files from a GCC toolchain, are
- // appended to |other_files|).
- //
- // 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 WritePCHCommands(const SourceFileTypeSet& used_types,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files,
- std::vector<OutputFile>* other_files);
-
- // Writes a .pch compile build line for a language type.
- void WritePCHCommand(SubstitutionType flag_type,
- const char* tool_name,
- CTool::PrecompiledHeaderType header_type,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files,
- std::vector<OutputFile>* other_files);
-
- void WriteGCCPCHCommand(SubstitutionType flag_type,
- const char* tool_name,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* gch_files);
-
- void WriteWindowsPCHCommand(SubstitutionType flag_type,
- const char* tool_name,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- std::vector<OutputFile>* object_files);
-
- // pch_deps are additional dependencies to run before the rule. They are
- // expected to abide by the naming conventions specified by GetPCHOutputFiles.
- //
- // order_only_dep are 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>& pch_deps,
- const OutputFile& input_dep,
- const std::vector<OutputFile>& order_only_deps,
- 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 std::vector<OutputFile>& order_only_deps,
- const char* tool_name,
- const std::vector<OutputFile>& outputs);
-
- void WriteLinkerStuff(const std::vector<OutputFile>& object_files,
- const std::vector<SourceFile>& other_files,
- const OutputFile& input_dep);
- void WriteLinkerFlags(const SourceFile* optional_def_file);
- void WriteLibs();
- void WriteOutputSubstitutions();
- 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);
-
- // Gets all target dependencies and classifies them, as well as accumulates
- // object files from source sets we need to link.
- void GetDeps(UniqueVector<OutputFile>* extra_object_files,
- UniqueVector<const Target*>* linkable_deps,
- UniqueVector<const Target*>* non_linkable_deps) const;
-
- // Classifies the dependency as linkable or nonlinkable with the current
- // target, adding it to the appropriate vector. If the dependency is a source
- // set we should link in, the source set's object files will be appended to
- // |extra_object_files|.
- void ClassifyDependency(const Target* dep,
- UniqueVector<OutputFile>* extra_object_files,
- UniqueVector<const Target*>* linkable_deps,
- UniqueVector<const Target*>* non_linkable_deps) const;
-
- // Writes the implicit dependencies for the link or stamp line. This is
- // the "||" and everything following it on the ninja line.
- //
- // The order-only dependencies are the non-linkable deps passed in as an
- // argument, plus the data file depdencies in the target.
- void WriteOrderOnlyDependencies(
- const UniqueVector<const Target*>& non_linkable_deps);
-
- // Checks for duplicates in the given list of output files. If any duplicates
- // are found, throws an error and return false.
- bool CheckForDuplicateObjectFiles(const std::vector<OutputFile>& files) const;
-
- const CTool* tool_;
-
// Cached version of the prefix used for rule types for this toolchain.
std::string rule_prefix_;
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index f066536..092f637 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -4,22 +4,13 @@
#include "tools/gn/ninja_binary_target_writer.h"
-#include <memory>
-#include <sstream>
-#include <utility>
-
-#include "tools/gn/config.h"
-#include "tools/gn/ninja_target_command_util.h"
-#include "tools/gn/scheduler.h"
-#include "tools/gn/target.h"
#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
-#include "util/build_config.h"
#include "util/test/test.h"
using NinjaBinaryTargetWriterTest = TestWithScheduler;
-TEST_F(NinjaBinaryTargetWriterTest, SourceSet) {
+TEST_F(NinjaBinaryTargetWriterTest, CSources) {
Err err;
TestWithScope setup;
@@ -35,156 +26,6 @@
target.SetToolchain(setup.toolchain());
ASSERT_TRUE(target.OnResolved(&err));
- // Source set itself.
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = bar\n"
- "\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.o "
- "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
- std::string out_str = 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.public_deps().push_back(LabelTargetPair(&target));
- shlib_target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(shlib_target.OnResolved(&err));
-
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&shlib_target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libshlib\n"
- "\n"
- "\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 ./libshlib.so: solink obj/foo/bar.input1.o "
- "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
- "|| obj/foo/bar.stamp\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = .so\n"
- " output_dir = \n";
- std::string out_str = 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.public_deps().push_back(LabelTargetPair(&target));
- stlib_target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(stlib_target.OnResolved(&err));
-
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&stlib_target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libstlib\n"
- "\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 || obj/foo/bar.stamp\n"
- " arflags =\n"
- " output_extension = \n"
- " output_dir = \n";
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
- }
-
- // Make the static library 'complete', which means it should be linked.
- stlib_target.set_complete_static_lib(true);
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&stlib_target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libstlib\n"
- "\n"
- "\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 obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
- "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
- "|| obj/foo/bar.stamp\n"
- " arflags =\n"
- " output_extension = \n"
- " output_dir = \n";
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
- }
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, EscapeDefines) {
- TestWithScope setup;
- Err err;
-
- TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
- target.config_values().defines().push_back("BOOL_DEF");
- target.config_values().defines().push_back("INT_DEF=123");
- target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expectedSubstr[] =
-#if defined(OS_WIN)
- "defines = -DBOOL_DEF -DINT_DEF=123 \"-DSTR_DEF=\\\"ABCD-1\\\"\"";
-#else
- "defines = -DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\"ABCD-1\\\"";
-#endif
- std::string out_str = out.str();
- EXPECT_TRUE(out_str.find(out_str) != std::string::npos);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, StaticLibrary) {
- TestWithScope setup;
- Err err;
-
- TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.config_values().arflags().push_back("--asdf");
- ASSERT_TRUE(target.OnResolved(&err));
-
std::ostringstream out;
NinjaBinaryTargetWriter writer(&target, out);
writer.Run();
@@ -196,964 +37,13 @@
"cflags_cc =\n"
"root_out_dir = .\n"
"target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
+ "target_output_name = bar\n"
"\n"
- "build obj/foo/libbar.input1.o: cxx ../../foo/input1.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/libbar.a: alink obj/foo/libbar.input1.o\n"
- " arflags = --asdf\n"
- " output_extension = \n"
- " output_dir = \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();
EXPECT_EQ(expected, out_str);
}
-
-TEST_F(NinjaBinaryTargetWriterTest, CompleteStaticLibrary) {
- TestWithScope setup;
- Err err;
-
- TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.config_values().arflags().push_back("--asdf");
- target.set_complete_static_lib(true);
-
- TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
- baz.sources().push_back(SourceFile("//foo/input2.cc"));
-
- target.public_deps().push_back(LabelTargetPair(&baz));
-
- ASSERT_TRUE(target.OnResolved(&err));
- ASSERT_TRUE(baz.OnResolved(&err));
-
- // A complete static library that depends on an incomplete static library
- // should link in the dependent object files as if the dependent target
- // were a source set.
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
- "\n"
- "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
- "\n"
- "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
- "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
- " arflags = --asdf\n"
- " output_extension = \n"
- " output_dir = \n";
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
- }
-
- // Make the dependent static library complete.
- baz.set_complete_static_lib(true);
-
- // Dependent complete static libraries should not be linked directly.
- {
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
- "\n"
- "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
- "\n"
- "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
- "|| obj/foo/libbaz.a\n"
- " arflags = --asdf\n"
- " output_extension = \n"
- " output_dir = \n";
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
- }
-}
-
-// This tests that output extension and output dir overrides apply, and input
-// dependencies are applied.
-TEST_F(NinjaBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
- Err err;
- TestWithScope setup;
-
- // An action for our library to depend on.
- Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
- action.set_output_type(Target::ACTION_FOREACH);
- action.visibility().SetPublic();
- action.SetToolchain(setup.toolchain());
- ASSERT_TRUE(action.OnResolved(&err));
-
- // A shared library w/ the output_extension set to a custom value.
- Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
- target.set_output_type(Target::SHARED_LIBRARY);
- target.set_output_extension(std::string("so.6"));
- target.set_output_dir(SourceDir("//out/Debug/foo/"));
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.sources().push_back(SourceFile("//foo/input2.cc"));
- target.public_deps().push_back(LabelTargetPair(&action));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libshlib\n"
- "\n"
- "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
- " || obj/foo/action.stamp\n"
- "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
- " || obj/foo/action.stamp\n"
- "\n"
- "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
- // The order-only dependency here is stricly unnecessary since the
- // sources list this as an order-only dep. See discussion in the code
- // that writes this.
- "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = .so.6\n"
- " output_dir = foo\n";
-
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
- Err err;
- TestWithScope setup;
-
- SourceFile generated_file("//out/Debug/generated.cc");
-
- // An action does code generation.
- Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
- action.set_output_type(Target::ACTION);
- action.visibility().SetPublic();
- action.SetToolchain(setup.toolchain());
- action.set_output_dir(SourceDir("//out/Debug/foo/"));
- action.action_values().outputs() =
- SubstitutionList::MakeForTest("//out/Debug/generated.cc");
- ASSERT_TRUE(action.OnResolved(&err));
-
- // A source set compiling geneated code, this target does not publicize any
- // headers.
- Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
- gen_obj.set_output_type(Target::SOURCE_SET);
- gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
- gen_obj.sources().push_back(generated_file);
- gen_obj.visibility().SetPublic();
- gen_obj.private_deps().push_back(LabelTargetPair(&action));
- gen_obj.set_all_headers_public(false);
- gen_obj.SetToolchain(setup.toolchain());
- ASSERT_TRUE(gen_obj.OnResolved(&err));
-
- std::ostringstream obj_out;
- NinjaBinaryTargetWriter obj_writer(&gen_obj, obj_out);
- obj_writer.Run();
-
- const char obj_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = gen_obj\n"
- "\n"
- "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
- " || obj/foo/generate.stamp\n"
- "\n"
- "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
- // The order-only dependency here is strictly unnecessary since the
- // sources list this as an order-only dep.
- " || obj/foo/generate.stamp\n";
-
- std::string obj_str = obj_out.str();
- EXPECT_EQ(obj_expected, obj_str);
-
- // A shared library depends on gen_obj, having corresponding header for
- // generated obj.
- Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
- gen_lib.set_output_type(Target::SHARED_LIBRARY);
- gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
- gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
- gen_lib.visibility().SetPublic();
- gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
- gen_lib.SetToolchain(setup.toolchain());
- ASSERT_TRUE(gen_lib.OnResolved(&err));
-
- std::ostringstream lib_out;
- NinjaBinaryTargetWriter lib_writer(&gen_lib, lib_out);
- lib_writer.Run();
-
- const char lib_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libgen_lib\n"
- "\n"
- "\n"
- "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
- // The order-only dependency here is strictly unnecessary since
- // obj/out/Debug/gen_obj.generated.o has dependency to
- // obj/foo/gen_obj.stamp
- " || obj/foo/gen_obj.stamp\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = .so\n"
- " output_dir = foo\n";
-
- std::string lib_str = lib_out.str();
- EXPECT_EQ(lib_expected, lib_str);
-
- // An executable depends on gen_lib.
- Target executable(setup.settings(),
- Label(SourceDir("//foo/"), "final_target"));
- executable.set_output_type(Target::EXECUTABLE);
- executable.set_output_dir(SourceDir("//out/Debug/foo/"));
- executable.sources().push_back(SourceFile("//foo/main.cc"));
- executable.private_deps().push_back(LabelTargetPair(&gen_lib));
- executable.SetToolchain(setup.toolchain());
- ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
-
- std::ostringstream final_out;
- NinjaBinaryTargetWriter final_writer(&executable, final_out);
- final_writer.Run();
-
- // There is no order only dependency to action target.
- const char final_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = final_target\n"
- "\n"
- "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
- "\n"
- "build ./final_target: link obj/foo/final_target.main.o"
- " ./libgen_lib.so\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = \n"
- " output_dir = foo\n";
-
- std::string final_str = final_out.str();
- EXPECT_EQ(final_expected, final_str);
-}
-
-// Tests libs are applied.
-TEST_F(NinjaBinaryTargetWriterTest, LibsAndLibDirs) {
- Err err;
- TestWithScope setup;
-
- // A shared library w/ libs and lib_dirs.
- Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
- target.set_output_type(Target::SHARED_LIBRARY);
- target.config_values().libs().push_back(LibFile(SourceFile("//foo/lib1.a")));
- target.config_values().libs().push_back(LibFile("foo"));
- target.config_values().lib_dirs().push_back(SourceDir("//foo/bar/"));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libshlib\n"
- "\n"
- "\n"
- "build ./libshlib.so: solink | ../../foo/lib1.a\n"
- " ldflags = -L../../foo/bar\n"
- " libs = ../../foo/lib1.a -lfoo\n"
- " output_extension = .so\n"
- " output_dir = \n";
-
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, EmptyOutputExtension) {
- Err err;
- TestWithScope setup;
-
- // This test is the same as OutputExtensionAndInputDeps, except that we call
- // set_output_extension("") and ensure that we get an empty one and override
- // the output prefix so that the name matches the target exactly.
- Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
- target.set_output_type(Target::SHARED_LIBRARY);
- target.set_output_prefix_override(true);
- target.set_output_extension(std::string());
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.sources().push_back(SourceFile("//foo/input2.cc"));
-
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = shlib\n"
- "\n"
- "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
- "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
- "\n"
- "build ./shlib: solink obj/foo/shlib.input1.o "
- "obj/foo/shlib.input2.o\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = \n"
- " output_dir = \n";
-
- std::string out_str = out.str();
- EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, SourceSetDataDeps) {
- Err err;
- TestWithScope setup;
-
- // This target is a data (runtime) dependency of the intermediate target.
- Target data(setup.settings(), Label(SourceDir("//foo/"), "data_target"));
- data.set_output_type(Target::EXECUTABLE);
- data.visibility().SetPublic();
- data.SetToolchain(setup.toolchain());
- ASSERT_TRUE(data.OnResolved(&err));
-
- // Intermediate source set target.
- Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
- inter.set_output_type(Target::SOURCE_SET);
- inter.visibility().SetPublic();
- inter.data_deps().push_back(LabelTargetPair(&data));
- inter.SetToolchain(setup.toolchain());
- inter.sources().push_back(SourceFile("//foo/inter.cc"));
- ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
-
- // Write out the intermediate target.
- std::ostringstream inter_out;
- NinjaBinaryTargetWriter inter_writer(&inter, inter_out);
- inter_writer.Run();
-
- // The intermediate source set will be a stamp file that depends on the
- // object files, and will have an order-only dependency on its data dep and
- // data file.
- const char inter_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = inter\n"
- "\n"
- "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
- "\n"
- "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
- "./data_target\n";
- EXPECT_EQ(inter_expected, inter_out.str());
-
- // Final target.
- Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
- exe.set_output_type(Target::EXECUTABLE);
- exe.public_deps().push_back(LabelTargetPair(&inter));
- exe.SetToolchain(setup.toolchain());
- exe.sources().push_back(SourceFile("//foo/final.cc"));
- ASSERT_TRUE(exe.OnResolved(&err));
-
- std::ostringstream final_out;
- NinjaBinaryTargetWriter final_writer(&exe, final_out);
- final_writer.Run();
-
- // The final output depends on both object files (one from the final target,
- // one from the source set) and has an order-only dependency on the source
- // set's stamp file and the final target's data file. The source set stamp
- // dependency will create an implicit order-only dependency on the data
- // target.
- const char final_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = exe\n"
- "\n"
- "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
- "\n"
- "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
- "obj/foo/inter.stamp\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = \n"
- " output_dir = \n";
- EXPECT_EQ(final_expected, final_out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
- Err err;
- TestWithScope setup;
-
- Target shared_lib(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- shared_lib.set_output_type(Target::SHARED_LIBRARY);
- shared_lib.SetToolchain(setup.toolchain());
- shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
- shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
- ASSERT_TRUE(shared_lib.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&shared_lib, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
- "\n"
- "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
- "\n"
- "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
- " ldflags = /DEF:../../foo/bar.def\n"
- " libs =\n"
- " output_extension = .so\n"
- " output_dir = \n";
- EXPECT_EQ(expected, out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, LoadableModule) {
- Err err;
- TestWithScope setup;
-
- Target loadable_module(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- loadable_module.set_output_type(Target::LOADABLE_MODULE);
- loadable_module.visibility().SetPublic();
- loadable_module.SetToolchain(setup.toolchain());
- loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
- ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&loadable_module, out);
- writer.Run();
-
- const char loadable_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
- "\n"
- "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
- "\n"
- "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = .so\n"
- " output_dir = \n";
- EXPECT_EQ(loadable_expected, out.str());
-
- // Final target.
- Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
- exe.set_output_type(Target::EXECUTABLE);
- exe.public_deps().push_back(LabelTargetPair(&loadable_module));
- exe.SetToolchain(setup.toolchain());
- exe.sources().push_back(SourceFile("//foo/final.cc"));
- ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
-
- std::ostringstream final_out;
- NinjaBinaryTargetWriter final_writer(&exe, final_out);
- final_writer.Run();
-
- // The final output depends on the loadable module so should have an
- // order-only dependency on the loadable modules's output file.
- const char final_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = exe\n"
- "\n"
- "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
- "\n"
- "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = \n"
- " output_dir = \n";
- EXPECT_EQ(final_expected, final_out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, 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"));
- pch_settings.set_toolchain_label(pch_toolchain.label());
- pch_settings.set_default_toolchain_label(setup.toolchain()->label());
-
- // Declare a C++ compiler that supports PCH.
- std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
- CTool* cxx_tool = cxx->AsC();
- TestWithScope::SetCommandForTool(
- "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
- "-o {{output}}",
- cxx_tool);
- cxx_tool->set_outputs(SubstitutionList::MakeForTest(
- "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
- cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
- pch_toolchain.SetTool(std::move(cxx));
-
- // Add a C compiler as well.
- std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
- CTool* cc_tool = cc->AsC();
- TestWithScope::SetCommandForTool(
- "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
- "-o {{output}}",
- cc_tool);
- cc_tool->set_outputs(SubstitutionList::MakeForTest(
- "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
- cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
- pch_toolchain.SetTool(std::move(cc));
- 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.sources().push_back(SourceFile("//foo/input2.c"));
- no_pch_target.config_values().cflags_c().push_back("-std=c99");
- 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_c = -std=c99\n"
- "cflags_cc =\n"
- "target_output_name = no_pch_target\n"
- "\n"
- "build withpch/obj/foo/no_pch_target.input1.o: "
- "withpch_cxx ../../foo/input1.cc\n"
- "build withpch/obj/foo/no_pch_target.input2.o: "
- "withpch_cc ../../foo/input2.c\n"
- "\n"
- "build withpch/obj/foo/no_pch_target.stamp: "
- "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
- "withpch/obj/foo/no_pch_target.input2.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.sources().push_back(SourceFile("//foo/input2.c"));
- 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"
- // It should output language-specific pch files.
- "cflags_c = /Fpwithpch/obj/foo/pch_target_c.pch "
- "/Yubuild/precompile.h\n"
- "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch "
- "/Yubuild/precompile.h\n"
- "target_output_name = pch_target\n"
- "\n"
- // Compile the precompiled source files with /Yc.
- "build withpch/obj/build/pch_target.precompile.c.o: "
- "withpch_cc ../../build/precompile.cc\n"
- " cflags_c = ${cflags_c} /Ycbuild/precompile.h\n"
- "\n"
- "build withpch/obj/build/pch_target.precompile.cc.o: "
- "withpch_cxx ../../build/precompile.cc\n"
- " cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n"
- "\n"
- "build withpch/obj/foo/pch_target.input1.o: "
- "withpch_cxx ../../foo/input1.cc | "
- // Explicit dependency on the PCH build step.
- "withpch/obj/build/pch_target.precompile.cc.o\n"
- "build withpch/obj/foo/pch_target.input2.o: "
- "withpch_cc ../../foo/input2.c | "
- // Explicit dependency on the PCH build step.
- "withpch/obj/build/pch_target.precompile.c.o\n"
- "\n"
- "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
- "withpch/obj/foo/pch_target.input1.o "
- "withpch/obj/foo/pch_target.input2.o "
- // The precompiled object files were added to the outputs.
- "withpch/obj/build/pch_target.precompile.c.o "
- "withpch/obj/build/pch_target.precompile.cc.o\n";
- EXPECT_EQ(pch_win_expected, out.str()) << pch_win_expected << "--BREAK--" << out.str();
- }
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, GCCPrecompiledHeaders) {
- 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"));
- pch_settings.set_toolchain_label(pch_toolchain.label());
- pch_settings.set_default_toolchain_label(setup.toolchain()->label());
-
- // Declare a C++ compiler that supports PCH.
- std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
- CTool* cxx_tool = cxx->AsC();
- TestWithScope::SetCommandForTool(
- "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
- "-o {{output}}",
- cxx_tool);
- cxx_tool->set_outputs(SubstitutionList::MakeForTest(
- "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
- cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
- pch_toolchain.SetTool(std::move(cxx));
- pch_toolchain.ToolchainSetupComplete();
-
- // Add a C compiler as well.
- std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
- CTool* cc_tool = cc->AsC();
- TestWithScope::SetCommandForTool(
- "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
- "-o {{output}}",
- cc_tool);
- cc_tool->set_outputs(SubstitutionList::MakeForTest(
- "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
- cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
- pch_toolchain.SetTool(std::move(cc));
- 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.sources().push_back(SourceFile("//foo/input2.c"));
- no_pch_target.config_values().cflags_c().push_back("-std=c99");
- 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_c = -std=c99\n"
- "cflags_cc =\n"
- "target_output_name = no_pch_target\n"
- "\n"
- "build withpch/obj/foo/no_pch_target.input1.o: "
- "withpch_cxx ../../foo/input1.cc\n"
- "build withpch/obj/foo/no_pch_target.input2.o: "
- "withpch_cc ../../foo/input2.c\n"
- "\n"
- "build withpch/obj/foo/no_pch_target.stamp: "
- "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
- "withpch/obj/foo/no_pch_target.input2.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_source(
- SourceFile("//build/precompile.h"));
- pch_target.config_values().cflags_c().push_back("-std=c99");
- pch_target.set_output_type(Target::SOURCE_SET);
- pch_target.visibility().SetPublic();
- pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
- pch_target.sources().push_back(SourceFile("//foo/input2.c"));
- pch_target.SetToolchain(&pch_toolchain);
- ASSERT_TRUE(pch_target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&pch_target, out);
- writer.Run();
-
- const char pch_gcc_expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_c = -std=c99 "
- "-include withpch/obj/build/pch_target.precompile.h-c\n"
- "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n"
- "target_output_name = pch_target\n"
- "\n"
- // Compile the precompiled sources with -x <lang>.
- "build withpch/obj/build/pch_target.precompile.h-c.gch: "
- "withpch_cc ../../build/precompile.h\n"
- " cflags_c = -std=c99 -x c-header\n"
- "\n"
- "build withpch/obj/build/pch_target.precompile.h-cc.gch: "
- "withpch_cxx ../../build/precompile.h\n"
- " cflags_cc = -x c++-header\n"
- "\n"
- "build withpch/obj/foo/pch_target.input1.o: "
- "withpch_cxx ../../foo/input1.cc | "
- // Explicit dependency on the PCH build step.
- "withpch/obj/build/pch_target.precompile.h-cc.gch\n"
- "build withpch/obj/foo/pch_target.input2.o: "
- "withpch_cc ../../foo/input2.c | "
- // Explicit dependency on the PCH build step.
- "withpch/obj/build/pch_target.precompile.h-c.gch\n"
- "\n"
- "build withpch/obj/foo/pch_target.stamp: "
- "withpch_stamp withpch/obj/foo/pch_target.input1.o "
- "withpch/obj/foo/pch_target.input2.o\n";
- EXPECT_EQ(pch_gcc_expected, out.str());
- }
-}
-
-// Should throw an error with the scheduler if a duplicate object file exists.
-// This is dependent on the toolchain's object file mapping.
-TEST_F(NinjaBinaryTargetWriterTest, DupeObjFileError) {
- TestWithScope setup;
- TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
- target.sources().push_back(SourceFile("//a.cc"));
- target.sources().push_back(SourceFile("//a.cc"));
-
- EXPECT_FALSE(scheduler().is_failed());
-
- scheduler().SuppressOutputForTesting(true);
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- scheduler().SuppressOutputForTesting(false);
-
- // Should have issued an error.
- EXPECT_TRUE(scheduler().is_failed());
-}
-
-// This tests that output extension and output dir overrides apply, and input
-// dependencies are applied.
-TEST_F(NinjaBinaryTargetWriterTest, InputFiles) {
- Err err;
- TestWithScope setup;
-
- // This target has one input.
- {
- Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- target.set_output_type(Target::SOURCE_SET);
- target.visibility().SetPublic();
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.sources().push_back(SourceFile("//foo/input2.cc"));
- target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = bar\n"
- "\n"
- "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
- " | ../../foo/input.data\n"
- "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
- " | ../../foo/input.data\n"
- "\n"
- "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
- "obj/foo/bar.input2.o\n";
-
- EXPECT_EQ(expected, out.str());
- }
-
- // This target has one input but no source files.
- {
- Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- target.set_output_type(Target::SHARED_LIBRARY);
- target.visibility().SetPublic();
- target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = libbar\n"
- "\n"
- "\n"
- "build ./libbar.so: solink | ../../foo/input.data\n"
- " ldflags =\n"
- " libs =\n"
- " output_extension = .so\n"
- " output_dir = \n";
-
- EXPECT_EQ(expected, out.str());
- }
-
- // This target has multiple inputs.
- {
- Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- target.set_output_type(Target::SOURCE_SET);
- target.visibility().SetPublic();
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.sources().push_back(SourceFile("//foo/input2.cc"));
- target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
- target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = bar\n"
- "\n"
- "build obj/foo/bar.inputs.stamp: stamp"
- " ../../foo/input1.data ../../foo/input2.data\n"
- "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
- " | obj/foo/bar.inputs.stamp\n"
- "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
- " | obj/foo/bar.inputs.stamp\n"
- "\n"
- "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
- "obj/foo/bar.input2.o\n";
-
- EXPECT_EQ(expected, out.str());
- }
-
- // This target has one input itself, one from an immediate config, and one
- // from a config tacked on to said config.
- {
- Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
- far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
- ASSERT_TRUE(far_config.OnResolved(&err));
-
- Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
- config.own_values().inputs().push_back(SourceFile("//foo/input2.data"));
- config.configs().push_back(LabelConfigPair(&far_config));
- ASSERT_TRUE(config.OnResolved(&err));
-
- Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
- target.set_output_type(Target::SOURCE_SET);
- target.visibility().SetPublic();
- target.sources().push_back(SourceFile("//foo/input1.cc"));
- target.sources().push_back(SourceFile("//foo/input2.cc"));
- target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
- target.configs().push_back(LabelConfigPair(&config));
- target.SetToolchain(setup.toolchain());
- ASSERT_TRUE(target.OnResolved(&err));
-
- std::ostringstream out;
- NinjaBinaryTargetWriter writer(&target, out);
- writer.Run();
-
- const char expected[] =
- "defines =\n"
- "include_dirs =\n"
- "cflags =\n"
- "cflags_cc =\n"
- "root_out_dir = .\n"
- "target_out_dir = obj/foo\n"
- "target_output_name = bar\n"
- "\n"
- "build obj/foo/bar.inputs.stamp: stamp"
- " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
- "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
- " | obj/foo/bar.inputs.stamp\n"
- "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
- " | obj/foo/bar.inputs.stamp\n"
- "\n"
- "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
- "obj/foo/bar.input2.o\n";
-
- EXPECT_EQ(expected, out.str());
- }
-}
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
new file mode 100644
index 0000000..b96e724
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -0,0 +1,875 @@
+// Copyright 2019 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_c_binary_target_writer.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <cstring>
+#include <set>
+#include <sstream>
+#include <unordered_set>
+
+#include "base/strings/string_util.h"
+#include "tools/gn/config_values_extractors.h"
+#include "tools/gn/deps_iterator.h"
+#include "tools/gn/err.h"
+#include "tools/gn/escape.h"
+#include "tools/gn/general_tool.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/ninja_target_command_util.h"
+#include "tools/gn/ninja_utils.h"
+#include "tools/gn/scheduler.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"
+
+namespace {
+
+// Returns the proper escape options for writing compiler and linker flags.
+EscapeOptions GetFlagOptions() {
+ EscapeOptions opts;
+ opts.mode = ESCAPE_NINJA_COMMAND;
+ return opts;
+}
+
+// Returns the language-specific lang recognized by gcc’s -x flag for
+// precompiled header files.
+const char* GetPCHLangForToolType(const char* name) {
+ if (name == CTool::kCToolCc)
+ return "c-header";
+ if (name == CTool::kCToolCxx)
+ return "c++-header";
+ if (name == CTool::kCToolObjC)
+ return "objective-c-header";
+ if (name == CTool::kCToolObjCxx)
+ return "objective-c++-header";
+ NOTREACHED() << "Not a valid PCH tool type: " << name;
+ return "";
+}
+
+// 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()) {
+ const char* tool_name = Tool::kToolNone;
+ if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
+ obj_files->push_back(tool_outputs[0]);
+
+ used_types.Set(GetSourceFileType(source));
+ }
+
+ // Add MSVC precompiled header object files. GCC .gch files are not object
+ // files so they are omitted.
+ if (source_set->config_values().has_precompiled_headers()) {
+ if (used_types.Get(SOURCE_C)) {
+ const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
+ if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+ GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
+ obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+ }
+ }
+ if (used_types.Get(SOURCE_CPP)) {
+ const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
+ if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+ GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
+ obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+ }
+ }
+ if (used_types.Get(SOURCE_M)) {
+ const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
+ if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+ GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
+ obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+ }
+ }
+ if (used_types.Get(SOURCE_MM)) {
+ const CTool* tool =
+ source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+ if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+ GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs);
+ obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+ }
+ }
+ }
+}
+
+} // namespace
+
+NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
+ std::ostream& out)
+ : NinjaBinaryTargetWriter(target, out),
+ tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)) {}
+
+NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
+
+void NinjaCBinaryTargetWriter::Run() {
+ // Figure out what source types are needed.
+ SourceFileTypeSet used_types;
+ for (const auto& source : target_->sources())
+ used_types.Set(GetSourceFileType(source));
+
+ WriteCompilerVars(used_types);
+
+ OutputFile input_dep = WriteInputsStampAndGetDep();
+
+ // 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.
+ //
+ // The order only deps are referenced by each source file compile,
+ // but also by PCH compiles. The latter are annoying to count, so omit
+ // them here. This means that binary targets with a single source file
+ // that also use PCH files won't have a stamp file even though having
+ // one would make output ninja file size a bit lower. That's ok, binary
+ // targets with a single source are rare.
+ size_t num_stamp_uses = target_->sources().size();
+ std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+ std::vector<const Target*>(), num_stamp_uses);
+
+ // For GCC builds, the .gch files are not object files, but still need to be
+ // added as explicit dependencies below. The .gch output files are placed in
+ // |pch_other_files|. This is to prevent linking against them.
+ std::vector<OutputFile> pch_obj_files;
+ std::vector<OutputFile> pch_other_files;
+ WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
+ &pch_other_files);
+ std::vector<OutputFile>* pch_files =
+ !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
+
+ // Treat all pch output files as explicit dependencies of all
+ // compiles that support them. Some notes:
+ //
+ // - On Windows, 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.
+ //
+ // - GCC .gch files are not object files, therefore they are not added to the
+ // object file list.
+ std::vector<OutputFile> obj_files;
+ std::vector<SourceFile> other_files;
+ WriteSources(*pch_files, input_dep, order_only_deps, &obj_files,
+ &other_files);
+
+ // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
+ obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
+ if (!CheckForDuplicateObjectFiles(obj_files))
+ return;
+
+ if (target_->output_type() == Target::SOURCE_SET) {
+ WriteSourceSetStamp(obj_files);
+#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, input_dep);
+ }
+}
+
+void NinjaCBinaryTargetWriter::WriteCompilerVars(
+ const SourceFileTypeSet& used_types) {
+ const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
+ // Defines.
+ if (subst.used[SUBSTITUTION_DEFINES]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
+ RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
+ DefineWriter(), out_);
+ out_ << std::endl;
+ }
+
+ // Include directories.
+ if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
+ out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
+ PathOutput include_path_output(
+ path_output_.current_dir(),
+ settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+ RecursiveTargetConfigToStream<SourceDir>(
+ target_, &ConfigValues::include_dirs,
+ IncludeWriter(include_path_output), out_);
+ out_ << std::endl;
+ }
+
+ bool has_precompiled_headers =
+ target_->config_values().has_precompiled_headers();
+
+ EscapeOptions opts = GetFlagOptions();
+ if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) {
+ WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Tool::kToolNone,
+ &ConfigValues::asmflags, opts, path_output_, out_);
+ }
+ if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) ||
+ used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) {
+ WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
+ &ConfigValues::cflags, opts, path_output_, out_);
+ }
+ if (used_types.Get(SOURCE_C)) {
+ WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
+ CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
+ out_);
+ }
+ if (used_types.Get(SOURCE_CPP)) {
+ WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
+ CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
+ out_);
+ }
+ if (used_types.Get(SOURCE_M)) {
+ WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
+ CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
+ path_output_, out_);
+ }
+ if (used_types.Get(SOURCE_MM)) {
+ WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
+ CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
+ path_output_, out_);
+ }
+
+ WriteSharedVars(subst);
+}
+
+OutputFile NinjaCBinaryTargetWriter::WriteInputsStampAndGetDep() const {
+ CHECK(target_->toolchain()) << "Toolchain not set on target "
+ << target_->label().GetUserVisibleName(true);
+
+ std::vector<const SourceFile*> inputs;
+ for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+ for (const auto& input : iter.cur().inputs()) {
+ inputs.push_back(&input);
+ }
+ }
+
+ if (inputs.size() == 0)
+ return OutputFile(); // No inputs
+
+ // If we only have one input, return it directly instead of writing a stamp
+ // file for it.
+ if (inputs.size() == 1)
+ return OutputFile(settings_->build_settings(), *inputs[0]);
+
+ // Make a stamp file.
+ OutputFile input_stamp_file =
+ GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+ input_stamp_file.value().append(target_->label().name());
+ input_stamp_file.value().append(".inputs.stamp");
+
+ out_ << "build ";
+ path_output_.WriteFile(out_, input_stamp_file);
+ out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+ << GeneralTool::kGeneralToolStamp;
+
+ // File inputs.
+ for (const auto* input : inputs) {
+ out_ << " ";
+ path_output_.WriteFile(out_, *input);
+ }
+
+ out_ << "\n";
+ return input_stamp_file;
+}
+
+void NinjaCBinaryTargetWriter::WritePCHCommands(
+ const SourceFileTypeSet& used_types,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files,
+ std::vector<OutputFile>* other_files) {
+ if (!target_->config_values().has_precompiled_headers())
+ return;
+
+ const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
+ if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
+ used_types.Get(SOURCE_C)) {
+ WritePCHCommand(SUBSTITUTION_CFLAGS_C, CTool::kCToolCc,
+ tool_c->precompiled_header_type(), input_dep,
+ order_only_deps, object_files, other_files);
+ }
+ const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
+ if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
+ used_types.Get(SOURCE_CPP)) {
+ WritePCHCommand(SUBSTITUTION_CFLAGS_CC, CTool::kCToolCxx,
+ tool_cxx->precompiled_header_type(), input_dep,
+ order_only_deps, object_files, other_files);
+ }
+
+ const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
+ if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
+ used_types.Get(SOURCE_M)) {
+ WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, CTool::kCToolObjC,
+ tool_objc->precompiled_header_type(), input_dep,
+ order_only_deps, object_files, other_files);
+ }
+
+ const CTool* tool_objcxx =
+ target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+ if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
+ used_types.Get(SOURCE_MM)) {
+ WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, CTool::kCToolObjCxx,
+ tool_objcxx->precompiled_header_type(), input_dep,
+ order_only_deps, object_files, other_files);
+ }
+}
+
+void NinjaCBinaryTargetWriter::WritePCHCommand(
+ SubstitutionType flag_type,
+ const char* tool_name,
+ CTool::PrecompiledHeaderType header_type,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files,
+ std::vector<OutputFile>* other_files) {
+ switch (header_type) {
+ case CTool::PCH_MSVC:
+ WriteWindowsPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
+ object_files);
+ break;
+ case CTool::PCH_GCC:
+ WriteGCCPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
+ other_files);
+ break;
+ case CTool::PCH_NONE:
+ NOTREACHED() << "Cannot write a PCH command with no PCH header type";
+ break;
+ }
+}
+
+void NinjaCBinaryTargetWriter::WriteGCCPCHCommand(
+ SubstitutionType flag_type,
+ const char* tool_name,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* gch_files) {
+ // Compute the pch output file (it will be language-specific).
+ std::vector<OutputFile> outputs;
+ GetPCHOutputFiles(target_, tool_name, &outputs);
+ if (outputs.empty())
+ return;
+
+ gch_files->insert(gch_files->end(), outputs.begin(), outputs.end());
+
+ std::vector<OutputFile> extra_deps;
+ if (!input_dep.value().empty())
+ extra_deps.push_back(input_dep);
+
+ // Build line to compile the file.
+ WriteCompilerBuildLine(target_->config_values().precompiled_source(),
+ extra_deps, order_only_deps, tool_name, outputs);
+
+ // This build line needs a custom language-specific flags value. Rule-specific
+ // variables are just indented underneath the rule line.
+ out_ << " " << kSubstitutionNinjaNames[flag_type] << " =";
+
+ // Each substitution flag is overwritten in the target rule to replace the
+ // implicitly generated -include flag with the -x <header lang> flag required
+ // for .gch targets.
+ EscapeOptions opts = GetFlagOptions();
+ if (tool_name == CTool::kCToolCc) {
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
+ out_);
+ } else if (tool_name == CTool::kCToolCxx) {
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
+ opts, out_);
+ } else if (tool_name == CTool::kCToolObjC) {
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
+ opts, out_);
+ } else if (tool_name == CTool::kCToolObjCxx) {
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
+ opts, out_);
+ }
+
+ // Append the command to specify the language of the .gch file.
+ out_ << " -x " << GetPCHLangForToolType(tool_name);
+
+ // Write two blank lines to help separate the PCH build lines from the
+ // regular source build lines.
+ out_ << std::endl << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand(
+ SubstitutionType flag_type,
+ const char* tool_name,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files) {
+ // Compute the pch output file (it will be language-specific).
+ std::vector<OutputFile> outputs;
+ GetPCHOutputFiles(target_, tool_name, &outputs);
+ if (outputs.empty())
+ return;
+
+ object_files->insert(object_files->end(), outputs.begin(), outputs.end());
+
+ std::vector<OutputFile> extra_deps;
+ if (!input_dep.value().empty())
+ extra_deps.push_back(input_dep);
+
+ // Build line to compile the file.
+ WriteCompilerBuildLine(target_->config_values().precompiled_source(),
+ extra_deps, order_only_deps, tool_name, outputs);
+
+ // This build line needs a custom language-specific flags value. Rule-specific
+ // variables are just indented underneath the rule line.
+ out_ << " " << kSubstitutionNinjaNames[flag_type] << " =";
+
+ // Append the command to generate the .pch file.
+ // This adds the value to the existing flag instead of overwriting it.
+ out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}";
+ 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 NinjaCBinaryTargetWriter::WriteSources(
+ const std::vector<OutputFile>& pch_deps,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files,
+ std::vector<SourceFile>* other_files) {
+ object_files->reserve(object_files->size() + target_->sources().size());
+
+ std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
+ std::vector<OutputFile> deps;
+ for (const auto& source : target_->sources()) {
+ // Clear the vector but maintain the max capacity to prevent reallocations.
+ deps.resize(0);
+ const char* tool_name = Tool::kToolNone;
+ if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
+ if (GetSourceFileType(source) == SOURCE_DEF)
+ other_files->push_back(source);
+ continue; // No output for this source.
+ }
+
+ if (!input_dep.value().empty())
+ deps.push_back(input_dep);
+
+ if (tool_name != Tool::kToolNone) {
+ // Only include PCH deps that correspond to the tool type, for instance,
+ // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep
+ // for the output of a C tool type.
+ //
+ // This makes the assumption that pch_deps only contains pch output files
+ // with the naming scheme specified in GetWindowsPCHObjectExtension or
+ // GetGCCPCHOutputExtension.
+ const CTool* tool = target_->toolchain()->GetToolAsC(tool_name);
+ if (tool->precompiled_header_type() != CTool::PCH_NONE) {
+ for (const auto& dep : pch_deps) {
+ const std::string& output_value = dep.value();
+ size_t extension_offset = FindExtensionOffset(output_value);
+ if (extension_offset == std::string::npos)
+ continue;
+ std::string output_extension;
+ if (tool->precompiled_header_type() == CTool::PCH_MSVC) {
+ output_extension = GetWindowsPCHObjectExtension(
+ tool_name, output_value.substr(extension_offset - 1));
+ } else if (tool->precompiled_header_type() == CTool::PCH_GCC) {
+ output_extension = GetGCCPCHOutputExtension(tool_name);
+ }
+ if (output_value.compare(
+ output_value.size() - output_extension.size(),
+ output_extension.size(), output_extension) == 0) {
+ deps.push_back(dep);
+ }
+ }
+ }
+ WriteCompilerBuildLine(source, deps, order_only_deps, tool_name,
+ tool_outputs);
+ }
+
+ // 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 NinjaCBinaryTargetWriter::WriteCompilerBuildLine(
+ const SourceFile& source,
+ const std::vector<OutputFile>& extra_deps,
+ const std::vector<OutputFile>& order_only_deps,
+ const char* tool_name,
+ const std::vector<OutputFile>& outputs) {
+ out_ << "build";
+ path_output_.WriteFiles(out_, outputs);
+
+ out_ << ": " << rule_prefix_ << tool_name;
+ out_ << " ";
+ path_output_.WriteFile(out_, source);
+
+ if (!extra_deps.empty()) {
+ out_ << " |";
+ path_output_.WriteFiles(out_, extra_deps);
+ }
+
+ if (!order_only_deps.empty()) {
+ out_ << " ||";
+ path_output_.WriteFiles(out_, order_only_deps);
+ }
+ out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteLinkerStuff(
+ const std::vector<OutputFile>& object_files,
+ const std::vector<SourceFile>& other_files,
+ const OutputFile& input_dep) {
+ std::vector<OutputFile> output_files;
+ SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+ target_, tool_, tool_->outputs(), &output_files);
+
+ out_ << "build";
+ path_output_.WriteFiles(out_, output_files);
+
+ out_ << ": " << rule_prefix_
+ << Tool::GetToolTypeForTargetFinalOutput(target_);
+
+ 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.
+ 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())
+ << "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());
+ }
+ }
+
+ const SourceFile* optional_def_file = nullptr;
+ if (!other_files.empty()) {
+ for (const SourceFile& src_file : other_files) {
+ if (GetSourceFileType(src_file) == SOURCE_DEF) {
+ optional_def_file = &src_file;
+ implicit_deps.push_back(
+ OutputFile(settings_->build_settings(), src_file));
+ break; // Only one def file is allowed.
+ }
+ }
+ }
+
+ // Libraries specified by paths.
+ const OrderedSet<LibFile>& libs = target_->all_libs();
+ for (size_t i = 0; i < libs.size(); i++) {
+ if (libs[i].is_source_file()) {
+ implicit_deps.push_back(
+ OutputFile(settings_->build_settings(), libs[i].source_file()));
+ }
+ }
+
+ // The input dependency is only needed if there are no object files, as the
+ // dependency is normally provided transitively by the source files.
+ if (!input_dep.value().empty() && object_files.empty())
+ implicit_deps.push_back(input_dep);
+
+ // Append implicit dependencies collected above.
+ if (!implicit_deps.empty()) {
+ out_ << " |";
+ path_output_.WriteFiles(out_, implicit_deps);
+ }
+
+ // Append data dependencies as order-only dependencies.
+ //
+ // This will include data dependencies and input dependencies (like when
+ // this target depends on an action). Having the data dependencies in this
+ // list ensures that the data is available at runtime when the user builds
+ // this target.
+ //
+ // The action dependencies are not strictly necessary in this case. They
+ // should also have been collected via the input deps stamp that each source
+ // file has for an order-only dependency, and since this target depends on
+ // the sources, there is already an implicit order-only dependency. However,
+ // it's extra work to separate these out and there's no disadvantage to
+ // listing them again.
+ WriteOrderOnlyDependencies(non_linkable_deps);
+
+ // End of the link "build" line.
+ out_ << std::endl;
+
+ // The remaining things go in the inner scope of the link line.
+ if (target_->output_type() == Target::EXECUTABLE ||
+ target_->output_type() == Target::SHARED_LIBRARY ||
+ target_->output_type() == Target::LOADABLE_MODULE) {
+ WriteLinkerFlags(optional_def_file);
+ WriteLibs();
+ } else if (target_->output_type() == Target::STATIC_LIBRARY) {
+ out_ << " arflags =";
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
+ GetFlagOptions(), out_);
+ out_ << std::endl;
+ }
+ WriteOutputSubstitutions();
+ WriteSolibs(solibs);
+}
+
+void NinjaCBinaryTargetWriter::WriteLinkerFlags(
+ const SourceFile* optional_def_file) {
+ out_ << " ldflags =";
+
+ // First the ldflags from the target and its config.
+ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
+ GetFlagOptions(), out_);
+
+ // Followed by library search paths that have been recursively pushed
+ // through the dependency tree.
+ const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
+ if (!all_lib_dirs.empty()) {
+ // Since we're passing these on the command line to the linker and not
+ // to Ninja, we need to do shell escaping.
+ PathOutput lib_path_output(path_output_.current_dir(),
+ settings_->build_settings()->root_path_utf8(),
+ ESCAPE_NINJA_COMMAND);
+ for (size_t i = 0; i < all_lib_dirs.size(); i++) {
+ out_ << " " << tool_->lib_dir_switch();
+ lib_path_output.WriteDir(out_, all_lib_dirs[i],
+ PathOutput::DIR_NO_LAST_SLASH);
+ }
+ }
+
+ if (optional_def_file) {
+ out_ << " /DEF:";
+ path_output_.WriteFile(out_, *optional_def_file);
+ }
+
+ out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteLibs() {
+ out_ << " libs =";
+
+ // Libraries that have been recursively pushed through the dependency tree.
+ EscapeOptions lib_escape_opts;
+ lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
+ const OrderedSet<LibFile> all_libs = target_->all_libs();
+ const std::string framework_ending(".framework");
+ for (size_t i = 0; i < all_libs.size(); i++) {
+ const LibFile& lib_file = all_libs[i];
+ const std::string& lib_value = lib_file.value();
+ if (lib_file.is_source_file()) {
+ out_ << " ";
+ path_output_.WriteFile(out_, lib_file.source_file());
+ } else if (base::EndsWith(lib_value, framework_ending,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ // Special-case libraries ending in ".framework" to support Mac: Add the
+ // -framework switch and don't add the extension to the output.
+ out_ << " -framework ";
+ EscapeStringToStream(
+ out_, lib_value.substr(0, lib_value.size() - framework_ending.size()),
+ lib_escape_opts);
+ } else {
+ out_ << " " << tool_->lib_switch();
+ EscapeStringToStream(out_, lib_value, lib_escape_opts);
+ }
+ }
+ out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
+ out_ << " output_extension = "
+ << SubstitutionWriter::GetLinkerSubstitution(
+ target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
+ out_ << std::endl;
+ out_ << " output_dir = "
+ << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
+ SUBSTITUTION_OUTPUT_DIR);
+ out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSolibs(
+ const std::vector<OutputFile>& solibs) {
+ if (solibs.empty())
+ return;
+
+ out_ << " solibs =";
+ path_output_.WriteFiles(out_, solibs);
+ out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSourceSetStamp(
+ const std::vector<OutputFile>& object_files) {
+ // The stamp rule for source sets is generally not used, since targets that
+ // 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.
+ 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);
+
+ // The classifier should never put extra object files in a source set:
+ // any source sets that we depend on should appear in our non-linkable
+ // deps instead.
+ DCHECK(extra_object_files.empty());
+
+ std::vector<OutputFile> order_only_deps;
+ for (auto* dep : non_linkable_deps)
+ order_only_deps.push_back(dep->dependency_output_file());
+
+ WriteStampForTarget(object_files, order_only_deps);
+}
+
+void NinjaCBinaryTargetWriter::GetDeps(
+ UniqueVector<OutputFile>* extra_object_files,
+ UniqueVector<const Target*>* linkable_deps,
+ UniqueVector<const Target*>* non_linkable_deps) const {
+ // Normal public/private deps.
+ for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+ ClassifyDependency(pair.ptr, extra_object_files, linkable_deps,
+ non_linkable_deps);
+ }
+
+ // Inherited libraries.
+ for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
+ ClassifyDependency(inherited_target, extra_object_files, linkable_deps,
+ non_linkable_deps);
+ }
+
+ // Data deps.
+ for (const auto& data_dep_pair : target_->data_deps())
+ non_linkable_deps->push_back(data_dep_pair.ptr);
+}
+
+void NinjaCBinaryTargetWriter::ClassifyDependency(
+ const Target* dep,
+ UniqueVector<OutputFile>* extra_object_files,
+ UniqueVector<const Target*>* linkable_deps,
+ UniqueVector<const Target*>* non_linkable_deps) const {
+ // Only the following types of outputs have libraries linked into them:
+ // EXECUTABLE
+ // SHARED_LIBRARY
+ // _complete_ STATIC_LIBRARY
+ //
+ // Child deps of intermediate static libraries get pushed up the
+ // dependency tree until one of these is reached, and source sets
+ // don't link at all.
+ bool can_link_libs = target_->IsFinal();
+
+ if (dep->output_type() == Target::SOURCE_SET ||
+ // If a complete static library depends on an incomplete static library,
+ // manually link in the object files of the dependent library as if it
+ // were a source set. This avoids problems with braindead tools such as
+ // ar which don't properly link dependent static libraries.
+ (target_->complete_static_lib() &&
+ dep->output_type() == Target::STATIC_LIBRARY &&
+ !dep->complete_static_lib())) {
+ // Source sets have their object files linked into final targets
+ // (shared libraries, executables, loadable modules, and complete static
+ // libraries). Intermediate static libraries and other source sets
+ // 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)
+ 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
+ // depends on (like data deps) are also built before the current target
+ // can be complete. Otherwise, these will be skipped since this target
+ // will depend only on the source set's object files.
+ non_linkable_deps->push_back(dep);
+ } else if (target_->complete_static_lib() && dep->IsFinal()) {
+ non_linkable_deps->push_back(dep);
+ } else if (can_link_libs && dep->IsLinkable()) {
+ linkable_deps->push_back(dep);
+ } else {
+ non_linkable_deps->push_back(dep);
+ }
+}
+
+void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies(
+ const UniqueVector<const Target*>& non_linkable_deps) {
+ if (!non_linkable_deps.empty()) {
+ out_ << " ||";
+
+ // Non-linkable targets.
+ for (auto* non_linkable_dep : non_linkable_deps) {
+ out_ << " ";
+ path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
+ }
+ }
+}
+
+bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles(
+ const std::vector<OutputFile>& files) const {
+ std::unordered_set<std::string> set;
+ for (const auto& file : files) {
+ if (!set.insert(file.value()).second) {
+ Err err(
+ target_->defined_from(), "Duplicate object file",
+ "The target " + target_->label().GetUserVisibleName(false) +
+ "\ngenerates two object files with the same name:\n " +
+ file.value() +
+ "\n"
+ "\n"
+ "It could be you accidentally have a file listed twice in the\n"
+ "sources. Or, depending on how your toolchain maps sources to\n"
+ "object files, two source files with the same name in different\n"
+ "directories could map to the same object file.\n"
+ "\n"
+ "In the latter case, either rename one of the files or move one "
+ "of\n"
+ "the sources to a separate source_set to avoid them both being "
+ "in\n"
+ "the same target.");
+ g_scheduler->FailWithError(err);
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/tools/gn/ninja_c_binary_target_writer.h b/tools/gn/ninja_c_binary_target_writer.h
new file mode 100644
index 0000000..4fb89d9
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer.h
@@ -0,0 +1,134 @@
+// Copyright 2019 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_C_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "tools/gn/config_values.h"
+#include "tools/gn/ninja_binary_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 NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter {
+ public:
+ NinjaCBinaryTargetWriter(const Target* target, std::ostream& out);
+ ~NinjaCBinaryTargetWriter() override;
+
+ void Run() override;
+
+ private:
+ typedef std::set<OutputFile> OutputFileSet;
+
+ // Writes all flags for the compiler: includes, defines, cflags, etc.
+ void WriteCompilerVars(const SourceFileTypeSet& used_types);
+
+ // Writes to the output stream a stamp rule for inputs, and
+ // returns the file to be appended to source rules that encodes the
+ // implicit dependencies for the current target. The returned OutputFile
+ // will be empty if there are no inputs.
+ OutputFile WriteInputsStampAndGetDep() const;
+
+ // Writes build lines required for precompiled headers. Any generated
+ // object files will be appended to the |object_files|. Any generated
+ // non-object files (for instance, .gch files from a GCC toolchain, are
+ // appended to |other_files|).
+ //
+ // 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 WritePCHCommands(const SourceFileTypeSet& used_types,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files,
+ std::vector<OutputFile>* other_files);
+
+ // Writes a .pch compile build line for a language type.
+ void WritePCHCommand(SubstitutionType flag_type,
+ const char* tool_name,
+ CTool::PrecompiledHeaderType header_type,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files,
+ std::vector<OutputFile>* other_files);
+
+ void WriteGCCPCHCommand(SubstitutionType flag_type,
+ const char* tool_name,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* gch_files);
+
+ void WriteWindowsPCHCommand(SubstitutionType flag_type,
+ const char* tool_name,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ std::vector<OutputFile>* object_files);
+
+ // pch_deps are additional dependencies to run before the rule. They are
+ // expected to abide by the naming conventions specified by GetPCHOutputFiles.
+ //
+ // order_only_dep are 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>& pch_deps,
+ const OutputFile& input_dep,
+ const std::vector<OutputFile>& order_only_deps,
+ 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 std::vector<OutputFile>& order_only_deps,
+ const char* tool_name,
+ const std::vector<OutputFile>& outputs);
+
+ void WriteLinkerStuff(const std::vector<OutputFile>& object_files,
+ const std::vector<SourceFile>& other_files,
+ const OutputFile& input_dep);
+ void WriteLinkerFlags(const SourceFile* optional_def_file);
+ void WriteLibs();
+ void WriteOutputSubstitutions();
+ 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);
+
+ // Gets all target dependencies and classifies them, as well as accumulates
+ // object files from source sets we need to link.
+ void GetDeps(UniqueVector<OutputFile>* extra_object_files,
+ UniqueVector<const Target*>* linkable_deps,
+ UniqueVector<const Target*>* non_linkable_deps) const;
+
+ // Classifies the dependency as linkable or nonlinkable with the current
+ // target, adding it to the appropriate vector. If the dependency is a source
+ // set we should link in, the source set's object files will be appended to
+ // |extra_object_files|.
+ void ClassifyDependency(const Target* dep,
+ UniqueVector<OutputFile>* extra_object_files,
+ UniqueVector<const Target*>* linkable_deps,
+ UniqueVector<const Target*>* non_linkable_deps) const;
+
+ // Writes the implicit dependencies for the link or stamp line. This is
+ // the "||" and everything following it on the ninja line.
+ //
+ // The order-only dependencies are the non-linkable deps passed in as an
+ // argument, plus the data file depdencies in the target.
+ void WriteOrderOnlyDependencies(
+ const UniqueVector<const Target*>& non_linkable_deps);
+
+ // Checks for duplicates in the given list of output files. If any duplicates
+ // are found, throws an error and return false.
+ bool CheckForDuplicateObjectFiles(const std::vector<OutputFile>& files) const;
+
+ const CTool* tool_;
+
+ DISALLOW_COPY_AND_ASSIGN(NinjaCBinaryTargetWriter);
+};
+
+#endif // TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
diff --git a/tools/gn/ninja_c_binary_target_writer_unittest.cc b/tools/gn/ninja_c_binary_target_writer_unittest.cc
new file mode 100644
index 0000000..829114a
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer_unittest.cc
@@ -0,0 +1,1159 @@
+// Copyright 2019 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_c_binary_target_writer.h"
+
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "tools/gn/config.h"
+#include "tools/gn/ninja_target_command_util.h"
+#include "tools/gn/scheduler.h"
+#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
+#include "tools/gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using NinjaCBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSet) {
+ Err err;
+ TestWithScope setup;
+
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ // Also test object files, which should be just passed through to the
+ // dependents to link.
+ target.sources().push_back(SourceFile("//foo/input3.o"));
+ target.sources().push_back(SourceFile("//foo/input4.obj"));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ // Source set itself.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = bar\n"
+ "\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.o "
+ "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
+ std::string out_str = 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.public_deps().push_back(LabelTargetPair(&target));
+ shlib_target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(shlib_target.OnResolved(&err));
+
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&shlib_target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
+ "\n"
+ "\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 ./libshlib.so: solink obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+ "|| obj/foo/bar.stamp\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n"
+ " output_dir = \n";
+ std::string out_str = 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.public_deps().push_back(LabelTargetPair(&target));
+ stlib_target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(stlib_target.OnResolved(&err));
+
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&stlib_target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libstlib\n"
+ "\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 || obj/foo/bar.stamp\n"
+ " arflags =\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+
+ // Make the static library 'complete', which means it should be linked.
+ stlib_target.set_complete_static_lib(true);
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&stlib_target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libstlib\n"
+ "\n"
+ "\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 obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+ "|| obj/foo/bar.stamp\n"
+ " arflags =\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EscapeDefines) {
+ TestWithScope setup;
+ Err err;
+
+ TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.config_values().defines().push_back("BOOL_DEF");
+ target.config_values().defines().push_back("INT_DEF=123");
+ target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expectedSubstr[] =
+#if defined(OS_WIN)
+ "defines = -DBOOL_DEF -DINT_DEF=123 \"-DSTR_DEF=\\\"ABCD-1\\\"\"";
+#else
+ "defines = -DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\"ABCD-1\\\"";
+#endif
+ std::string out_str = out.str();
+ EXPECT_TRUE(out_str.find(out_str) != std::string::npos);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, StaticLibrary) {
+ TestWithScope setup;
+ Err err;
+
+ TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.config_values().arflags().push_back("--asdf");
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o\n"
+ " arflags = --asdf\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, CompleteStaticLibrary) {
+ TestWithScope setup;
+ Err err;
+
+ TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.config_values().arflags().push_back("--asdf");
+ target.set_complete_static_lib(true);
+
+ TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
+ baz.sources().push_back(SourceFile("//foo/input2.cc"));
+
+ target.public_deps().push_back(LabelTargetPair(&baz));
+
+ ASSERT_TRUE(target.OnResolved(&err));
+ ASSERT_TRUE(baz.OnResolved(&err));
+
+ // A complete static library that depends on an incomplete static library
+ // should link in the dependent object files as if the dependent target
+ // were a source set.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+ "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
+ " arflags = --asdf\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+
+ // Make the dependent static library complete.
+ baz.set_complete_static_lib(true);
+
+ // Dependent complete static libraries should not be linked directly.
+ {
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+ "\n"
+ "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+ "|| obj/foo/libbaz.a\n"
+ " arflags = --asdf\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+ }
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
+ Err err;
+ TestWithScope setup;
+
+ // An action for our library to depend on.
+ Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
+ action.set_output_type(Target::ACTION_FOREACH);
+ action.visibility().SetPublic();
+ action.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(action.OnResolved(&err));
+
+ // A shared library w/ the output_extension set to a custom value.
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+ target.set_output_type(Target::SHARED_LIBRARY);
+ target.set_output_extension(std::string("so.6"));
+ target.set_output_dir(SourceDir("//out/Debug/foo/"));
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.public_deps().push_back(LabelTargetPair(&action));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
+ "\n"
+ "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
+ " || obj/foo/action.stamp\n"
+ "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
+ " || obj/foo/action.stamp\n"
+ "\n"
+ "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
+ // The order-only dependency here is stricly unnecessary since the
+ // sources list this as an order-only dep. See discussion in the code
+ // that writes this.
+ "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so.6\n"
+ " output_dir = foo\n";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
+ Err err;
+ TestWithScope setup;
+
+ SourceFile generated_file("//out/Debug/generated.cc");
+
+ // An action does code generation.
+ Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
+ action.set_output_type(Target::ACTION);
+ action.visibility().SetPublic();
+ action.SetToolchain(setup.toolchain());
+ action.set_output_dir(SourceDir("//out/Debug/foo/"));
+ action.action_values().outputs() =
+ SubstitutionList::MakeForTest("//out/Debug/generated.cc");
+ ASSERT_TRUE(action.OnResolved(&err));
+
+ // A source set compiling geneated code, this target does not publicize any
+ // headers.
+ Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
+ gen_obj.set_output_type(Target::SOURCE_SET);
+ gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
+ gen_obj.sources().push_back(generated_file);
+ gen_obj.visibility().SetPublic();
+ gen_obj.private_deps().push_back(LabelTargetPair(&action));
+ gen_obj.set_all_headers_public(false);
+ gen_obj.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(gen_obj.OnResolved(&err));
+
+ std::ostringstream obj_out;
+ NinjaCBinaryTargetWriter obj_writer(&gen_obj, obj_out);
+ obj_writer.Run();
+
+ const char obj_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = gen_obj\n"
+ "\n"
+ "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
+ " || obj/foo/generate.stamp\n"
+ "\n"
+ "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
+ // The order-only dependency here is strictly unnecessary since the
+ // sources list this as an order-only dep.
+ " || obj/foo/generate.stamp\n";
+
+ std::string obj_str = obj_out.str();
+ EXPECT_EQ(obj_expected, obj_str);
+
+ // A shared library depends on gen_obj, having corresponding header for
+ // generated obj.
+ Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
+ gen_lib.set_output_type(Target::SHARED_LIBRARY);
+ gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
+ gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
+ gen_lib.visibility().SetPublic();
+ gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
+ gen_lib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(gen_lib.OnResolved(&err));
+
+ std::ostringstream lib_out;
+ NinjaCBinaryTargetWriter lib_writer(&gen_lib, lib_out);
+ lib_writer.Run();
+
+ const char lib_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libgen_lib\n"
+ "\n"
+ "\n"
+ "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
+ // The order-only dependency here is strictly unnecessary since
+ // obj/out/Debug/gen_obj.generated.o has dependency to
+ // obj/foo/gen_obj.stamp
+ " || obj/foo/gen_obj.stamp\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n"
+ " output_dir = foo\n";
+
+ std::string lib_str = lib_out.str();
+ EXPECT_EQ(lib_expected, lib_str);
+
+ // An executable depends on gen_lib.
+ Target executable(setup.settings(),
+ Label(SourceDir("//foo/"), "final_target"));
+ executable.set_output_type(Target::EXECUTABLE);
+ executable.set_output_dir(SourceDir("//out/Debug/foo/"));
+ executable.sources().push_back(SourceFile("//foo/main.cc"));
+ executable.private_deps().push_back(LabelTargetPair(&gen_lib));
+ executable.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
+
+ std::ostringstream final_out;
+ NinjaCBinaryTargetWriter final_writer(&executable, final_out);
+ final_writer.Run();
+
+ // There is no order only dependency to action target.
+ const char final_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = final_target\n"
+ "\n"
+ "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
+ "\n"
+ "build ./final_target: link obj/foo/final_target.main.o"
+ " ./libgen_lib.so\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = \n"
+ " output_dir = foo\n";
+
+ std::string final_str = final_out.str();
+ EXPECT_EQ(final_expected, final_str);
+}
+
+// Tests libs are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, LibsAndLibDirs) {
+ Err err;
+ TestWithScope setup;
+
+ // A shared library w/ libs and lib_dirs.
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+ target.set_output_type(Target::SHARED_LIBRARY);
+ target.config_values().libs().push_back(LibFile(SourceFile("//foo/lib1.a")));
+ target.config_values().libs().push_back(LibFile("foo"));
+ target.config_values().lib_dirs().push_back(SourceDir("//foo/bar/"));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libshlib\n"
+ "\n"
+ "\n"
+ "build ./libshlib.so: solink | ../../foo/lib1.a\n"
+ " ldflags = -L../../foo/bar\n"
+ " libs = ../../foo/lib1.a -lfoo\n"
+ " output_extension = .so\n"
+ " output_dir = \n";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EmptyOutputExtension) {
+ Err err;
+ TestWithScope setup;
+
+ // This test is the same as OutputExtensionAndInputDeps, except that we call
+ // set_output_extension("") and ensure that we get an empty one and override
+ // the output prefix so that the name matches the target exactly.
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+ target.set_output_type(Target::SHARED_LIBRARY);
+ target.set_output_prefix_override(true);
+ target.set_output_extension(std::string());
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = shlib\n"
+ "\n"
+ "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
+ "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
+ "\n"
+ "build ./shlib: solink obj/foo/shlib.input1.o "
+ "obj/foo/shlib.input2.o\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = \n"
+ " output_dir = \n";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSetDataDeps) {
+ Err err;
+ TestWithScope setup;
+
+ // This target is a data (runtime) dependency of the intermediate target.
+ Target data(setup.settings(), Label(SourceDir("//foo/"), "data_target"));
+ data.set_output_type(Target::EXECUTABLE);
+ data.visibility().SetPublic();
+ data.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(data.OnResolved(&err));
+
+ // Intermediate source set target.
+ Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
+ inter.set_output_type(Target::SOURCE_SET);
+ inter.visibility().SetPublic();
+ inter.data_deps().push_back(LabelTargetPair(&data));
+ inter.SetToolchain(setup.toolchain());
+ inter.sources().push_back(SourceFile("//foo/inter.cc"));
+ ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
+
+ // Write out the intermediate target.
+ std::ostringstream inter_out;
+ NinjaCBinaryTargetWriter inter_writer(&inter, inter_out);
+ inter_writer.Run();
+
+ // The intermediate source set will be a stamp file that depends on the
+ // object files, and will have an order-only dependency on its data dep and
+ // data file.
+ const char inter_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = inter\n"
+ "\n"
+ "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
+ "\n"
+ "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
+ "./data_target\n";
+ EXPECT_EQ(inter_expected, inter_out.str());
+
+ // Final target.
+ Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+ exe.set_output_type(Target::EXECUTABLE);
+ exe.public_deps().push_back(LabelTargetPair(&inter));
+ exe.SetToolchain(setup.toolchain());
+ exe.sources().push_back(SourceFile("//foo/final.cc"));
+ ASSERT_TRUE(exe.OnResolved(&err));
+
+ std::ostringstream final_out;
+ NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+ final_writer.Run();
+
+ // The final output depends on both object files (one from the final target,
+ // one from the source set) and has an order-only dependency on the source
+ // set's stamp file and the final target's data file. The source set stamp
+ // dependency will create an implicit order-only dependency on the data
+ // target.
+ const char final_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = exe\n"
+ "\n"
+ "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+ "\n"
+ "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
+ "obj/foo/inter.stamp\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
+ Err err;
+ TestWithScope setup;
+
+ Target shared_lib(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ shared_lib.set_output_type(Target::SHARED_LIBRARY);
+ shared_lib.SetToolchain(setup.toolchain());
+ shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
+ shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
+ ASSERT_TRUE(shared_lib.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&shared_lib, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+ "\n"
+ "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
+ " ldflags = /DEF:../../foo/bar.def\n"
+ " libs =\n"
+ " output_extension = .so\n"
+ " output_dir = \n";
+ EXPECT_EQ(expected, out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, LoadableModule) {
+ Err err;
+ TestWithScope setup;
+
+ Target loadable_module(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ loadable_module.set_output_type(Target::LOADABLE_MODULE);
+ loadable_module.visibility().SetPublic();
+ loadable_module.SetToolchain(setup.toolchain());
+ loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
+ ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&loadable_module, out);
+ writer.Run();
+
+ const char loadable_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+ "\n"
+ "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n"
+ " output_dir = \n";
+ EXPECT_EQ(loadable_expected, out.str());
+
+ // Final target.
+ Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+ exe.set_output_type(Target::EXECUTABLE);
+ exe.public_deps().push_back(LabelTargetPair(&loadable_module));
+ exe.SetToolchain(setup.toolchain());
+ exe.sources().push_back(SourceFile("//foo/final.cc"));
+ ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
+
+ std::ostringstream final_out;
+ NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+ final_writer.Run();
+
+ // The final output depends on the loadable module so should have an
+ // order-only dependency on the loadable modules's output file.
+ const char final_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = exe\n"
+ "\n"
+ "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+ "\n"
+ "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = \n"
+ " output_dir = \n";
+ EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, 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"));
+ pch_settings.set_toolchain_label(pch_toolchain.label());
+ pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+ // Declare a C++ compiler that supports PCH.
+ std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+ CTool* cxx_tool = cxx->AsC();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cxx_tool);
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+ pch_toolchain.SetTool(std::move(cxx));
+
+ // Add a C compiler as well.
+ std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+ CTool* cc_tool = cc->AsC();
+ TestWithScope::SetCommandForTool(
+ "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cc_tool);
+ cc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+ pch_toolchain.SetTool(std::move(cc));
+ 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.sources().push_back(SourceFile("//foo/input2.c"));
+ no_pch_target.config_values().cflags_c().push_back("-std=c99");
+ no_pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+ writer.Run();
+
+ const char no_pch_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_c = -std=c99\n"
+ "cflags_cc =\n"
+ "target_output_name = no_pch_target\n"
+ "\n"
+ "build withpch/obj/foo/no_pch_target.input1.o: "
+ "withpch_cxx ../../foo/input1.cc\n"
+ "build withpch/obj/foo/no_pch_target.input2.o: "
+ "withpch_cc ../../foo/input2.c\n"
+ "\n"
+ "build withpch/obj/foo/no_pch_target.stamp: "
+ "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+ "withpch/obj/foo/no_pch_target.input2.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.sources().push_back(SourceFile("//foo/input2.c"));
+ pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(pch_target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&pch_target, out);
+ writer.Run();
+
+ const char pch_win_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ // It should output language-specific pch files.
+ "cflags_c = /Fpwithpch/obj/foo/pch_target_c.pch "
+ "/Yubuild/precompile.h\n"
+ "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch "
+ "/Yubuild/precompile.h\n"
+ "target_output_name = pch_target\n"
+ "\n"
+ // Compile the precompiled source files with /Yc.
+ "build withpch/obj/build/pch_target.precompile.c.o: "
+ "withpch_cc ../../build/precompile.cc\n"
+ " cflags_c = ${cflags_c} /Ycbuild/precompile.h\n"
+ "\n"
+ "build withpch/obj/build/pch_target.precompile.cc.o: "
+ "withpch_cxx ../../build/precompile.cc\n"
+ " cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n"
+ "\n"
+ "build withpch/obj/foo/pch_target.input1.o: "
+ "withpch_cxx ../../foo/input1.cc | "
+ // Explicit dependency on the PCH build step.
+ "withpch/obj/build/pch_target.precompile.cc.o\n"
+ "build withpch/obj/foo/pch_target.input2.o: "
+ "withpch_cc ../../foo/input2.c | "
+ // Explicit dependency on the PCH build step.
+ "withpch/obj/build/pch_target.precompile.c.o\n"
+ "\n"
+ "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
+ "withpch/obj/foo/pch_target.input1.o "
+ "withpch/obj/foo/pch_target.input2.o "
+ // The precompiled object files were added to the outputs.
+ "withpch/obj/build/pch_target.precompile.c.o "
+ "withpch/obj/build/pch_target.precompile.cc.o\n";
+ EXPECT_EQ(pch_win_expected, out.str());
+ }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, GCCPrecompiledHeaders) {
+ 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"));
+ pch_settings.set_toolchain_label(pch_toolchain.label());
+ pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+ // Declare a C++ compiler that supports PCH.
+ std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+ CTool* cxx_tool = cxx->AsC();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cxx_tool);
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+ pch_toolchain.SetTool(std::move(cxx));
+ pch_toolchain.ToolchainSetupComplete();
+
+ // Add a C compiler as well.
+ std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+ CTool* cc_tool = cc->AsC();
+ TestWithScope::SetCommandForTool(
+ "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cc_tool);
+ cc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
+ pch_toolchain.SetTool(std::move(cc));
+ 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.sources().push_back(SourceFile("//foo/input2.c"));
+ no_pch_target.config_values().cflags_c().push_back("-std=c99");
+ no_pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+ writer.Run();
+
+ const char no_pch_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_c = -std=c99\n"
+ "cflags_cc =\n"
+ "target_output_name = no_pch_target\n"
+ "\n"
+ "build withpch/obj/foo/no_pch_target.input1.o: "
+ "withpch_cxx ../../foo/input1.cc\n"
+ "build withpch/obj/foo/no_pch_target.input2.o: "
+ "withpch_cc ../../foo/input2.c\n"
+ "\n"
+ "build withpch/obj/foo/no_pch_target.stamp: "
+ "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+ "withpch/obj/foo/no_pch_target.input2.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_source(
+ SourceFile("//build/precompile.h"));
+ pch_target.config_values().cflags_c().push_back("-std=c99");
+ pch_target.set_output_type(Target::SOURCE_SET);
+ pch_target.visibility().SetPublic();
+ pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+ pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+ pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(pch_target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&pch_target, out);
+ writer.Run();
+
+ const char pch_gcc_expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_c = -std=c99 "
+ "-include withpch/obj/build/pch_target.precompile.h-c\n"
+ "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n"
+ "target_output_name = pch_target\n"
+ "\n"
+ // Compile the precompiled sources with -x <lang>.
+ "build withpch/obj/build/pch_target.precompile.h-c.gch: "
+ "withpch_cc ../../build/precompile.h\n"
+ " cflags_c = -std=c99 -x c-header\n"
+ "\n"
+ "build withpch/obj/build/pch_target.precompile.h-cc.gch: "
+ "withpch_cxx ../../build/precompile.h\n"
+ " cflags_cc = -x c++-header\n"
+ "\n"
+ "build withpch/obj/foo/pch_target.input1.o: "
+ "withpch_cxx ../../foo/input1.cc | "
+ // Explicit dependency on the PCH build step.
+ "withpch/obj/build/pch_target.precompile.h-cc.gch\n"
+ "build withpch/obj/foo/pch_target.input2.o: "
+ "withpch_cc ../../foo/input2.c | "
+ // Explicit dependency on the PCH build step.
+ "withpch/obj/build/pch_target.precompile.h-c.gch\n"
+ "\n"
+ "build withpch/obj/foo/pch_target.stamp: "
+ "withpch_stamp withpch/obj/foo/pch_target.input1.o "
+ "withpch/obj/foo/pch_target.input2.o\n";
+ EXPECT_EQ(pch_gcc_expected, out.str());
+ }
+}
+
+// Should throw an error with the scheduler if a duplicate object file exists.
+// This is dependent on the toolchain's object file mapping.
+TEST_F(NinjaCBinaryTargetWriterTest, DupeObjFileError) {
+ TestWithScope setup;
+ TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
+ target.sources().push_back(SourceFile("//a.cc"));
+ target.sources().push_back(SourceFile("//a.cc"));
+
+ EXPECT_FALSE(scheduler().is_failed());
+
+ scheduler().SuppressOutputForTesting(true);
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ scheduler().SuppressOutputForTesting(false);
+
+ // Should have issued an error.
+ EXPECT_TRUE(scheduler().is_failed());
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, InputFiles) {
+ Err err;
+ TestWithScope setup;
+
+ // This target has one input.
+ {
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = bar\n"
+ "\n"
+ "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+ " | ../../foo/input.data\n"
+ "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+ " | ../../foo/input.data\n"
+ "\n"
+ "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o\n";
+
+ EXPECT_EQ(expected, out.str());
+ }
+
+ // This target has one input but no source files.
+ {
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SHARED_LIBRARY);
+ target.visibility().SetPublic();
+ target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = libbar\n"
+ "\n"
+ "\n"
+ "build ./libbar.so: solink | ../../foo/input.data\n"
+ " ldflags =\n"
+ " libs =\n"
+ " output_extension = .so\n"
+ " output_dir = \n";
+
+ EXPECT_EQ(expected, out.str());
+ }
+
+ // This target has multiple inputs.
+ {
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+ target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = bar\n"
+ "\n"
+ "build obj/foo/bar.inputs.stamp: stamp"
+ " ../../foo/input1.data ../../foo/input2.data\n"
+ "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+ " | obj/foo/bar.inputs.stamp\n"
+ "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+ " | obj/foo/bar.inputs.stamp\n"
+ "\n"
+ "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o\n";
+
+ EXPECT_EQ(expected, out.str());
+ }
+
+ // This target has one input itself, one from an immediate config, and one
+ // from a config tacked on to said config.
+ {
+ Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
+ far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
+ ASSERT_TRUE(far_config.OnResolved(&err));
+
+ Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
+ config.own_values().inputs().push_back(SourceFile("//foo/input2.data"));
+ config.configs().push_back(LabelConfigPair(&far_config));
+ ASSERT_TRUE(config.OnResolved(&err));
+
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+ target.configs().push_back(LabelConfigPair(&config));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/foo\n"
+ "target_output_name = bar\n"
+ "\n"
+ "build obj/foo/bar.inputs.stamp: stamp"
+ " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
+ "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+ " | obj/foo/bar.inputs.stamp\n"
+ "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+ " | obj/foo/bar.inputs.stamp\n"
+ "\n"
+ "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+ "obj/foo/bar.input2.o\n";
+
+ EXPECT_EQ(expected, out.str());
+ }
+}