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()); + } +}