#include "gn/ninja_c_binary_target_writer.h"
#include <stddef.h>
#include <string.h>
#include <cstring>
#include <set>
#include <sstream>
#include "base/strings/string_util.h"
#include "gn/c_substitution_type.h"
#include "gn/config_values_extractors.h"
#include "gn/deps_iterator.h"
#include "gn/err.h"
#include "gn/escape.h"
#include "gn/filesystem_utils.h"
#include "gn/general_tool.h"
#include "gn/ninja_module_writer_util.h"
#include "gn/ninja_target_command_util.h"
#include "gn/ninja_utils.h"
#include "gn/pool.h"
#include "gn/scheduler.h"
#include "gn/settings.h"
#include "gn/string_utils.h"
#include "gn/substitution_writer.h"
#include "gn/target.h"
namespace {
EscapeOptions GetFlagOptions() {
EscapeOptions opts;
opts.mode = ESCAPE_NINJA_COMMAND;
return opts;
}
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 "";
}
}
NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
std::ostream& out)
: NinjaBinaryTargetWriter(target, out),
tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)) {}
NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
void NinjaCBinaryTargetWriter::Run() {
std::vector<ClangModuleDep> module_dep_info =
GetModuleDepsInformation(target_, resolved());
WriteCompilerVars(module_dep_info);
size_t num_output_uses = target_->sources().size();
std::vector<OutputFile> input_deps =
WriteInputsStampOrPhonyAndGetDep(num_output_uses);
std::vector<OutputFile> order_only_deps = WriteInputDepsStampOrPhonyAndGetDep(
std::vector<const Target*>(), num_output_uses);
std::vector<OutputFile> pch_obj_files;
std::vector<OutputFile> pch_other_files;
WritePCHCommands(input_deps, order_only_deps, &pch_obj_files,
&pch_other_files);
std::vector<OutputFile>* pch_files =
!pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
std::vector<OutputFile> obj_files;
std::vector<OutputFile> extra_files;
std::vector<SourceFile> other_files;
std::vector<OutputFile>* stamp_files = &obj_files;
if (!target_->source_types_used().SwiftSourceUsed()) {
WriteSources(*pch_files, input_deps, order_only_deps, module_dep_info,
&obj_files, &extra_files, &other_files);
} else {
stamp_files = &extra_files;
WriteSwiftSources(input_deps, order_only_deps, &obj_files, &extra_files);
}
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) {
#ifndef NDEBUG
UniqueVector<OutputFile> computed_obj;
AddSourceSetFiles(target_, &computed_obj);
DCHECK_EQ(obj_files.size(), computed_obj.size());
for (const auto& obj : obj_files)
DCHECK(computed_obj.Contains(obj));
#endif
if (!target_->source_types_used().SwiftSourceUsed()) {
stamp_files->insert(stamp_files->end(), extra_files.begin(),
extra_files.end());
}
WriteSourceSetStamp(*stamp_files);
} else {
WriteLinkerStuff(obj_files, other_files, input_deps);
}
}
void NinjaCBinaryTargetWriter::WriteCompilerVars(
const std::vector<ClangModuleDep>& module_dep_info) {
const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
WriteCCompilerVars(subst, false,
true);
WriteModuleNameSubstitution();
if (!module_dep_info.empty()) {
if (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
WriteModuleDepsSubstitution(&CSubstitutionModuleDeps, module_dep_info,
true);
WriteModuleDepsSubstitution(&CSubstitutionModuleDepsNoSelf,
module_dep_info, false);
}
}
WriteSharedVars(subst);
}
void NinjaCBinaryTargetWriter::WriteModuleNameSubstitution() {
if (target_->toolchain()->substitution_bits().used.count(
&CSubstitutionModuleName)) {
out_ << CSubstitutionModuleName.ninja_name << " = ";
EscapeOptions options;
options.mode = ESCAPE_NINJA_COMMAND;
EscapeStringToStream(out_, target_->module_name(), options);
out_ << std::endl;
}
}
void NinjaCBinaryTargetWriter::WriteModuleDepsSubstitution(
const Substitution* substitution,
const std::vector<ClangModuleDep>& module_dep_info,
bool include_self) {
if (target_->toolchain()->substitution_bits().used.count(substitution)) {
EscapeOptions options;
options.mode = ESCAPE_NINJA_COMMAND;
out_ << substitution->ninja_name << " =";
for (const auto& module_dep : module_dep_info) {
if (!module_dep.is_self || include_self) {
out_ << " ";
EscapeStringToStream(out_, "-fmodule-file=", options);
EscapeStringToStream(out_, module_dep.module_name, options);
out_ << "=";
path_output_.WriteFile(out_, module_dep.pcm);
}
}
out_ << std::endl;
}
}
void NinjaCBinaryTargetWriter::WritePCHCommands(
const std::vector<OutputFile>& input_deps,
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 &&
target_->source_types_used().Get(SourceFile::SOURCE_C)) {
WritePCHCommand(&CSubstitutionCFlagsC, CTool::kCToolCc,
tool_c->precompiled_header_type(), input_deps,
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 &&
target_->source_types_used().Get(SourceFile::SOURCE_CPP)) {
WritePCHCommand(&CSubstitutionCFlagsCc, CTool::kCToolCxx,
tool_cxx->precompiled_header_type(), input_deps,
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 &&
target_->source_types_used().Get(SourceFile::SOURCE_M)) {
WritePCHCommand(&CSubstitutionCFlagsObjC, CTool::kCToolObjC,
tool_objc->precompiled_header_type(), input_deps,
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 &&
target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
WritePCHCommand(&CSubstitutionCFlagsObjCc, CTool::kCToolObjCxx,
tool_objcxx->precompiled_header_type(), input_deps,
order_only_deps, object_files, other_files);
}
}
void NinjaCBinaryTargetWriter::WritePCHCommand(
const Substitution* flag_type,
const char* tool_name,
CTool::PrecompiledHeaderType header_type,
const std::vector<OutputFile>& input_deps,
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_deps, order_only_deps,
object_files);
break;
case CTool::PCH_GCC:
WriteGCCPCHCommand(flag_type, tool_name, input_deps, 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(
const Substitution* flag_type,
const char* tool_name,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* gch_files) {
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;
std::copy(input_deps.begin(), input_deps.end(),
std::back_inserter(extra_deps));
WriteCompilerBuildLine({target_->config_values().precompiled_source()},
extra_deps, order_only_deps, tool_name, outputs);
out_ << " " << flag_type->ninja_name << " =";
EscapeOptions opts = GetFlagOptions();
if (tool_name == CTool::kCToolCc) {
RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
target_, &ConfigValues::cflags_c, opts,
out_);
} else if (tool_name == CTool::kCToolCxx) {
RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
target_, &ConfigValues::cflags_cc,
opts, out_);
} else if (tool_name == CTool::kCToolObjC) {
RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
target_, &ConfigValues::cflags_objc,
opts, out_);
} else if (tool_name == CTool::kCToolObjCxx) {
RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
target_, &ConfigValues::cflags_objcc,
opts, out_);
}
out_ << " -x " << GetPCHLangForToolType(tool_name);
out_ << std::endl << std::endl;
}
void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand(
const Substitution* flag_type,
const char* tool_name,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* object_files) {
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;
std::copy(input_deps.begin(), input_deps.end(),
std::back_inserter(extra_deps));
WriteCompilerBuildLine({target_->config_values().precompiled_source()},
extra_deps, order_only_deps, tool_name, outputs);
out_ << " " << flag_type->ninja_name << " =";
out_ << " ${" << flag_type->ninja_name << "}";
out_ << " /Yc" << target_->config_values().precompiled_header();
out_ << std::endl << std::endl;
}
void NinjaCBinaryTargetWriter::WriteSources(
const std::vector<OutputFile>& pch_deps,
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
const std::vector<ClangModuleDep>& module_dep_info,
std::vector<OutputFile>* object_files,
std::vector<OutputFile>* extra_files,
std::vector<SourceFile>* other_files) {
DCHECK(!target_->source_types_used().SwiftSourceUsed());
object_files->reserve(object_files->size() + target_->sources().size());
std::vector<OutputFile> tool_outputs;
std::vector<OutputFile> deps;
for (const auto& source : target_->sources()) {
DCHECK_NE(source.GetType(), SourceFile::SOURCE_SWIFT);
deps.resize(0);
const char* tool_name = Tool::kToolNone;
if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
if (source.IsDefType())
other_files->push_back(source);
continue;
}
std::copy(input_deps.begin(), input_deps.end(), std::back_inserter(deps));
if (tool_name != Tool::kToolNone) {
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);
}
}
}
for (const auto& module_dep : module_dep_info) {
if (tool_outputs[0] != module_dep.pcm)
deps.push_back(module_dep.pcm);
}
WriteCompilerBuildLine({source}, deps, order_only_deps, tool_name,
tool_outputs);
WritePool(out_);
}
if (!source.IsModuleMapType()) {
object_files->push_back(tool_outputs[0]);
} else {
extra_files->push_back(tool_outputs[0]);
}
}
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteSwiftSources(
const std::vector<OutputFile>& input_deps,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* object_files,
std::vector<OutputFile>* output_files) {
DCHECK(target_->builds_swift_module());
target_->swift_values().GetOutputs(target_, output_files);
const BuildSettings* build_settings = settings_->build_settings();
for (const OutputFile& output : *output_files) {
const SourceFile output_as_source = output.AsSourceFile(build_settings);
if (output_as_source.IsObjectType()) {
object_files->push_back(output);
}
}
UniqueVector<OutputFile> swift_order_only_deps;
swift_order_only_deps.reserve(order_only_deps.size());
swift_order_only_deps.Append(order_only_deps.begin(), order_only_deps.end());
for (const Target* swiftmodule :
resolved().GetSwiftModuleDependencies(target_)) {
CHECK(swiftmodule->has_dependency_output());
{
swift_order_only_deps.push_back(swiftmodule->dependency_output());
}
}
const Tool* tool = target_->swift_values().GetTool(target_);
WriteCompilerBuildLine(target_->sources(), input_deps,
swift_order_only_deps.vector(), tool->name(),
*output_files,
false,
true);
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteSourceSetStamp(
const std::vector<OutputFile>& object_files) {
ClassifiedDeps classified_deps = GetClassifiedDeps();
DCHECK(classified_deps.extra_object_files.empty());
std::vector<OutputFile> order_only_deps;
for (auto* dep : classified_deps.non_linkable_deps) {
if (dep->has_dependency_output()) {
order_only_deps.push_back(dep->dependency_output());
}
}
WriteStampOrPhonyForTarget(object_files, order_only_deps);
}
void NinjaCBinaryTargetWriter::WriteLinkerStuff(
const std::vector<OutputFile>& object_files,
const std::vector<SourceFile>& other_files,
const std::vector<OutputFile>& input_deps) {
std::vector<OutputFile> output_files;
SubstitutionWriter::ApplyListToLinkerAsOutputFile(
target_, tool_, tool_->outputs(), &output_files);
out_ << "build";
WriteOutputs(output_files);
out_ << ": " << rule_prefix_ << tool_->name();
ClassifiedDeps classified_deps = GetClassifiedDeps();
path_output_.WriteFiles(out_, object_files);
path_output_.WriteFiles(out_, classified_deps.extra_object_files);
std::vector<OutputFile> implicit_deps;
std::vector<OutputFile> solibs;
for (const Target* cur : classified_deps.linkable_deps) {
DCHECK(!cur->link_output_file().value().empty())
<< "No link output file for "
<< target_->label().GetUserVisibleName(false);
if (cur->output_type() == Target::RUST_LIBRARY ||
cur->output_type() == Target::RUST_PROC_MACRO)
continue;
if (cur->has_dependency_output() &&
cur->dependency_output().value() != cur->link_output_file().value()) {
implicit_deps.push_back(cur->dependency_output());
solibs.push_back(cur->link_output_file());
} else {
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 (src_file.IsDefType()) {
optional_def_file = &src_file;
implicit_deps.push_back(
OutputFile(settings_->build_settings(), src_file));
break;
}
}
}
for (const auto& lib : resolved().GetLinkedLibraries(target_)) {
if (lib.is_source_file()) {
implicit_deps.push_back(
OutputFile(settings_->build_settings(), lib.source_file()));
}
}
for (const Target* dep : classified_deps.framework_deps) {
if (dep->has_dependency_output())
implicit_deps.push_back(dep->dependency_output());
}
std::copy(input_deps.begin(), input_deps.end(),
std::back_inserter(implicit_deps));
std::vector<OutputFile> transitive_rustlibs;
if (target_->IsFinal()) {
for (const auto& inherited : resolved().GetInheritedLibraries(target_)) {
const Target* dep = inherited.target();
if (dep->output_type() == Target::RUST_LIBRARY) {
CHECK(dep->has_dependency_output_file());
transitive_rustlibs.push_back(dep->dependency_output_file());
implicit_deps.push_back(dep->dependency_output_file());
}
}
}
std::vector<OutputFile> swiftmodules;
if (target_->IsFinal()) {
for (const Target* dep : classified_deps.swiftmodule_deps) {
swiftmodules.push_back(dep->swift_values().module_output_file());
implicit_deps.push_back(dep->swift_values().module_output_file());
}
if (target_->builds_swift_module()) {
swiftmodules.push_back(target_->swift_values().module_output_file());
implicit_deps.push_back(target_->swift_values().module_output_file());
}
}
if (!implicit_deps.empty()) {
out_ << " |";
path_output_.WriteFiles(out_, implicit_deps);
}
WriteOrderOnlyDependencies(classified_deps.non_linkable_deps);
out_ << std::endl;
if (target_->output_type() == Target::EXECUTABLE ||
target_->output_type() == Target::SHARED_LIBRARY ||
target_->output_type() == Target::LOADABLE_MODULE) {
out_ << " ldflags =";
WriteLinkerFlags(out_, tool_, optional_def_file);
out_ << std::endl;
out_ << " libs =";
WriteLibs(out_, tool_);
out_ << std::endl;
out_ << " frameworks =";
WriteFrameworks(out_, tool_);
out_ << std::endl;
out_ << " swiftmodules =";
WriteSwiftModules(out_, tool_, swiftmodules);
out_ << std::endl;
} else if (target_->output_type() == Target::STATIC_LIBRARY) {
out_ << " arflags =";
RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
target_, &ConfigValues::arflags,
GetFlagOptions(), out_);
out_ << std::endl;
}
WriteOutputSubstitutions();
WriteLibsList("solibs", solibs);
WriteLibsList("rlibs", transitive_rustlibs);
WritePool(out_);
const Toolchain *toolchain = target_->toolchain();
if (toolchain == nullptr || toolchain->GetToolAsC(CTool::kCToolSolink) == nullptr) {
return;
}
int toolchain_whole_status = toolchain->GetToolAsC(CTool::kCToolSolink)->toolchain_whole_status();
if (toolchain_whole_status == 0) {
WriteWholeArchive(toolchain_whole_status);
} else if (toolchain_whole_status == 1){
WriteNoWholeArchive(toolchain_whole_status);
}
return;
}
void NinjaCBinaryTargetWriter::WriteWholeArchive(int toolchain_whole_status) {
if (target_->whole_archive_deps().size() == 0) {
return;
}
std::string result;
for (const auto& file : target_->whole_archive_deps()) {
std::string label = file.label.GetUserVisibleName(false);
size_t sep = label.find(":");
if (sep == std::string::npos) {
continue;
}
result += label.substr(sep + 1) + ".a";
result += " ";
}
out_ << " toolchain_whole_status = "
<< toolchain_whole_status;
out_ << std::endl;
out_ << " whole-archive = "
<< result;
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteNoWholeArchive(int toolchain_whole_status) {
if (target_->no_whole_archive_deps().size() == 0) {
return;
}
std::string result;
for (const auto& file : target_->no_whole_archive_deps()) {
std::string label = file.label.GetUserVisibleName(false);
size_t sep = label.find(":");
if (sep == std::string::npos) {
continue;
}
result += label.substr(sep + 1) + ".a";
result += " ";
}
out_ << " toolchain_whole_status = "
<< toolchain_whole_status;
out_ << std::endl;
out_ << " no-whole-archive = "
<< result;
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
const std::string output_extension =
SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
&SubstitutionOutputExtension);
out_ << " output_extension =";
if (!output_extension.empty()) {
out_ << " " << output_extension;
}
out_ << std::endl;
const std::string output_dir = SubstitutionWriter::GetLinkerSubstitution(
target_, tool_, &SubstitutionOutputDir);
out_ << " output_dir =";
if (!output_dir.empty()) {
out_ << " " << output_dir;
}
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteLibsList(
const std::string& label,
const std::vector<OutputFile>& libs) {
if (libs.empty())
return;
out_ << " " << label << " =";
PathOutput output(path_output_.current_dir(),
settings_->build_settings()->root_path_utf8(),
ESCAPE_NINJA_COMMAND);
output.WriteFiles(out_, libs);
out_ << std::endl;
}
void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies(
const UniqueVector<const Target*>& non_linkable_deps) {
if (!non_linkable_deps.empty()) {
out_ << " ||";
for (auto* non_linkable_dep : non_linkable_deps) {
if (non_linkable_dep->has_dependency_output()) {
out_ << " ";
path_output_.WriteFile(out_, non_linkable_dep->dependency_output());
}
}
}
}
bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles(
const std::vector<OutputFile>& files) const {
std::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;
}