
Swift is used as the linker for non-swift files because it needs to pull files like swiftrt.o in when swift symbols are present to ensure that the swift runtime is linked. The swift driver uses clang as the underlying linker, which pulls in crtbegin.o and friends when appropriate, so using Swift as a linker for C/C++ libraries is fine. The output-file-map was getting passed to all Swift invocations, regardless of whether or not we generated one. This patch changes it so that we only include the output-file-map in the Swift compiler invocation if we have actually generated the file.
1490 lines
55 KiB
C++
1490 lines
55 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmNinjaNormalTargetGenerator.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
#include <cm/memory>
|
|
#include <cm/optional>
|
|
#include <cm/vector>
|
|
|
|
#include "cmComputeLinkInformation.h"
|
|
#include "cmCustomCommand.h" // IWYU pragma: keep
|
|
#include "cmCustomCommandGenerator.h"
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmGeneratorTarget.h"
|
|
#include "cmGlobalNinjaGenerator.h"
|
|
#include "cmLinkLineComputer.h"
|
|
#include "cmLinkLineDeviceComputer.h"
|
|
#include "cmLocalCommonGenerator.h"
|
|
#include "cmLocalGenerator.h"
|
|
#include "cmLocalNinjaGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmMessageType.h"
|
|
#include "cmNinjaLinkLineDeviceComputer.h"
|
|
#include "cmNinjaTypes.h"
|
|
#include "cmOSXBundleGenerator.h"
|
|
#include "cmOutputConverter.h"
|
|
#include "cmRulePlaceholderExpander.h"
|
|
#include "cmSourceFile.h"
|
|
#include "cmState.h"
|
|
#include "cmStateDirectory.h"
|
|
#include "cmStateSnapshot.h"
|
|
#include "cmStateTypes.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmValue.h"
|
|
|
|
cmNinjaNormalTargetGenerator::cmNinjaNormalTargetGenerator(
|
|
cmGeneratorTarget* target)
|
|
: cmNinjaTargetGenerator(target)
|
|
{
|
|
if (target->GetType() != cmStateEnums::OBJECT_LIBRARY) {
|
|
// on Windows the output dir is already needed at compile time
|
|
// ensure the directory exists (OutDir test)
|
|
for (auto const& config : this->GetConfigNames()) {
|
|
this->EnsureDirectoryExists(target->GetDirectory(config));
|
|
}
|
|
}
|
|
|
|
this->OSXBundleGenerator = cm::make_unique<cmOSXBundleGenerator>(target);
|
|
this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
|
|
}
|
|
|
|
cmNinjaNormalTargetGenerator::~cmNinjaNormalTargetGenerator() = default;
|
|
|
|
void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
|
|
{
|
|
std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
|
|
if (this->TargetLinkLanguage(config).empty()) {
|
|
cmSystemTools::Error("CMake can not determine linker language for "
|
|
"target: " +
|
|
this->GetGeneratorTarget()->GetName());
|
|
return;
|
|
}
|
|
|
|
// Write the rules for each language.
|
|
this->WriteLanguagesRules(config);
|
|
|
|
// Write the build statements
|
|
bool firstForConfig = true;
|
|
for (auto const& fileConfig : this->GetConfigNames()) {
|
|
if (!this->GetGlobalGenerator()
|
|
->GetCrossConfigs(fileConfig)
|
|
.count(config)) {
|
|
continue;
|
|
}
|
|
this->WriteObjectBuildStatements(config, fileConfig, firstForConfig);
|
|
firstForConfig = false;
|
|
}
|
|
|
|
if (this->GetGeneratorTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) {
|
|
this->WriteObjectLibStatement(config);
|
|
} else {
|
|
firstForConfig = true;
|
|
for (auto const& fileConfig : this->GetConfigNames()) {
|
|
if (!this->GetGlobalGenerator()
|
|
->GetCrossConfigs(fileConfig)
|
|
.count(config)) {
|
|
continue;
|
|
}
|
|
// If this target has cuda language link inputs, and we need to do
|
|
// device linking
|
|
this->WriteDeviceLinkStatement(config, fileConfig, firstForConfig);
|
|
this->WriteLinkStatement(config, fileConfig, firstForConfig);
|
|
firstForConfig = false;
|
|
}
|
|
}
|
|
if (this->GetGlobalGenerator()->EnableCrossConfigBuild()) {
|
|
this->GetGlobalGenerator()->AddTargetAlias(
|
|
this->GetTargetName(), this->GetGeneratorTarget(), "all");
|
|
}
|
|
|
|
// Find ADDITIONAL_CLEAN_FILES
|
|
this->AdditionalCleanFiles(config);
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteLanguagesRules(
|
|
const std::string& config)
|
|
{
|
|
#ifdef NINJA_GEN_VERBOSE_FILES
|
|
cmGlobalNinjaGenerator::WriteDivider(this->GetRulesFileStream());
|
|
this->GetRulesFileStream()
|
|
<< "# Rules for each languages for "
|
|
<< cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType())
|
|
<< " target " << this->GetTargetName() << "\n\n";
|
|
#endif
|
|
|
|
// Write rules for languages compiled in this target.
|
|
std::set<std::string> languages;
|
|
std::vector<cmSourceFile const*> sourceFiles;
|
|
this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
|
|
if (this->HaveRequiredLanguages(sourceFiles, languages)) {
|
|
for (std::string const& language : languages) {
|
|
this->WriteLanguageRules(language, config);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* cmNinjaNormalTargetGenerator::GetVisibleTypeName() const
|
|
{
|
|
switch (this->GetGeneratorTarget()->GetType()) {
|
|
case cmStateEnums::STATIC_LIBRARY:
|
|
return "static library";
|
|
case cmStateEnums::SHARED_LIBRARY:
|
|
return "shared library";
|
|
case cmStateEnums::MODULE_LIBRARY:
|
|
if (this->GetGeneratorTarget()->IsCFBundleOnApple()) {
|
|
return "CFBundle shared module";
|
|
} else {
|
|
return "shared module";
|
|
}
|
|
case cmStateEnums::EXECUTABLE:
|
|
return "executable";
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::LanguageLinkerRule(
|
|
const std::string& config) const
|
|
{
|
|
return cmStrCat(
|
|
this->TargetLinkLanguage(config), "_",
|
|
cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()),
|
|
"_LINKER__",
|
|
cmGlobalNinjaGenerator::EncodeRuleName(
|
|
this->GetGeneratorTarget()->GetName()),
|
|
"_", config);
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::LanguageLinkerDeviceRule(
|
|
const std::string& config) const
|
|
{
|
|
return cmStrCat(
|
|
this->TargetLinkLanguage(config), "_",
|
|
cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()),
|
|
"_DEVICE_LINKER__",
|
|
cmGlobalNinjaGenerator::EncodeRuleName(
|
|
this->GetGeneratorTarget()->GetName()),
|
|
"_", config);
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::LanguageLinkerCudaDeviceRule(
|
|
const std::string& config) const
|
|
{
|
|
return cmStrCat(
|
|
this->TargetLinkLanguage(config), "_DEVICE_LINK__",
|
|
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()),
|
|
'_', config);
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::LanguageLinkerCudaDeviceCompileRule(
|
|
const std::string& config) const
|
|
{
|
|
return cmStrCat(
|
|
this->TargetLinkLanguage(config), "_DEVICE_LINK_COMPILE__",
|
|
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()),
|
|
'_', config);
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::LanguageLinkerCudaFatbinaryRule(
|
|
const std::string& config) const
|
|
{
|
|
return cmStrCat(
|
|
this->TargetLinkLanguage(config), "_FATBINARY__",
|
|
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()),
|
|
'_', config);
|
|
}
|
|
|
|
struct cmNinjaRemoveNoOpCommands
|
|
{
|
|
bool operator()(std::string const& cmd)
|
|
{
|
|
return cmd.empty() || cmd[0] == ':';
|
|
}
|
|
};
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteNvidiaDeviceLinkRule(
|
|
bool useResponseFile, const std::string& config)
|
|
{
|
|
cmNinjaRule rule(this->LanguageLinkerDeviceRule(config));
|
|
if (!this->GetGlobalGenerator()->HasRule(rule.Name)) {
|
|
cmRulePlaceholderExpander::RuleVariables vars;
|
|
vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
|
|
vars.CMTargetType =
|
|
cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType())
|
|
.c_str();
|
|
|
|
vars.Language = "CUDA";
|
|
|
|
// build response file name
|
|
std::string responseFlag = this->GetMakefile()->GetSafeDefinition(
|
|
"CMAKE_CUDA_RESPONSE_FILE_DEVICE_LINK_FLAG");
|
|
|
|
if (!useResponseFile || responseFlag.empty()) {
|
|
vars.Objects = "$in";
|
|
vars.LinkLibraries = "$LINK_PATH $LINK_LIBRARIES";
|
|
} else {
|
|
rule.RspFile = "$RSP_FILE";
|
|
responseFlag += rule.RspFile;
|
|
|
|
// build response file content
|
|
if (this->GetGlobalGenerator()->IsGCCOnWindows()) {
|
|
rule.RspContent = "$in";
|
|
} else {
|
|
rule.RspContent = "$in_newline";
|
|
}
|
|
rule.RspContent += " $LINK_LIBRARIES";
|
|
vars.Objects = responseFlag.c_str();
|
|
vars.LinkLibraries = "";
|
|
}
|
|
|
|
vars.ObjectDir = "$OBJECT_DIR";
|
|
|
|
vars.Target = "$TARGET_FILE";
|
|
|
|
vars.SONameFlag = "$SONAME_FLAG";
|
|
vars.TargetSOName = "$SONAME";
|
|
vars.TargetPDB = "$TARGET_PDB";
|
|
vars.TargetCompilePDB = "$TARGET_COMPILE_PDB";
|
|
|
|
vars.Flags = "$FLAGS";
|
|
vars.LinkFlags = "$LINK_FLAGS";
|
|
vars.Manifests = "$MANIFESTS";
|
|
|
|
vars.LanguageCompileFlags = "$LANGUAGE_COMPILE_FLAGS";
|
|
|
|
std::string launcher;
|
|
cmValue val = this->GetLocalGenerator()->GetRuleLauncher(
|
|
this->GetGeneratorTarget(), "RULE_LAUNCH_LINK");
|
|
if (cmNonempty(val)) {
|
|
launcher = cmStrCat(*val, ' ');
|
|
}
|
|
|
|
std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
|
|
this->GetLocalGenerator()->CreateRulePlaceholderExpander());
|
|
|
|
// Rule for linking library/executable.
|
|
std::vector<std::string> linkCmds = this->ComputeDeviceLinkCmd();
|
|
for (std::string& linkCmd : linkCmds) {
|
|
linkCmd = cmStrCat(launcher, linkCmd);
|
|
rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
|
|
linkCmd, vars);
|
|
}
|
|
|
|
// If there is no ranlib the command will be ":". Skip it.
|
|
cm::erase_if(linkCmds, cmNinjaRemoveNoOpCommands());
|
|
|
|
rule.Command =
|
|
this->GetLocalGenerator()->BuildCommandLine(linkCmds, config, config);
|
|
|
|
// Write the linker rule with response file if needed.
|
|
rule.Comment =
|
|
cmStrCat("Rule for linking ", this->TargetLinkLanguage(config), ' ',
|
|
this->GetVisibleTypeName(), '.');
|
|
rule.Description =
|
|
cmStrCat("Linking ", this->TargetLinkLanguage(config), ' ',
|
|
this->GetVisibleTypeName(), " $TARGET_FILE");
|
|
rule.Restat = "$RESTAT";
|
|
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
}
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteDeviceLinkRules(
|
|
const std::string& config)
|
|
{
|
|
const cmMakefile* mf = this->GetMakefile();
|
|
|
|
cmNinjaRule rule(this->LanguageLinkerCudaDeviceRule(config));
|
|
rule.Command = this->GetLocalGenerator()->BuildCommandLine(
|
|
{ cmStrCat(mf->GetRequiredDefinition("CMAKE_CUDA_DEVICE_LINKER"),
|
|
" -arch=$ARCH $REGISTER -o=$out $in") },
|
|
config, config);
|
|
rule.Comment = "Rule for CUDA device linking.";
|
|
rule.Description = "Linking CUDA $out";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
|
|
cmRulePlaceholderExpander::RuleVariables vars;
|
|
vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
|
|
vars.CMTargetType =
|
|
cmState::GetTargetTypeName(this->GetGeneratorTarget()->GetType()).c_str();
|
|
|
|
vars.Language = "CUDA";
|
|
vars.Object = "$out";
|
|
vars.Fatbinary = "$FATBIN";
|
|
vars.RegisterFile = "$REGISTER";
|
|
vars.LinkFlags = "$LINK_FLAGS";
|
|
|
|
std::string flags = this->GetFlags("CUDA", config);
|
|
vars.Flags = flags.c_str();
|
|
|
|
std::string compileCmd = this->GetMakefile()->GetRequiredDefinition(
|
|
"CMAKE_CUDA_DEVICE_LINK_COMPILE");
|
|
std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
|
|
this->GetLocalGenerator()->CreateRulePlaceholderExpander());
|
|
rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
|
|
compileCmd, vars);
|
|
|
|
rule.Name = this->LanguageLinkerCudaDeviceCompileRule(config);
|
|
rule.Command = this->GetLocalGenerator()->BuildCommandLine({ compileCmd },
|
|
config, config);
|
|
rule.Comment = "Rule for compiling CUDA device stubs.";
|
|
rule.Description = "Compiling CUDA device stub $out";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
|
|
rule.Name = this->LanguageLinkerCudaFatbinaryRule(config);
|
|
rule.Command = this->GetLocalGenerator()->BuildCommandLine(
|
|
{ cmStrCat(mf->GetRequiredDefinition("CMAKE_CUDA_FATBINARY"),
|
|
" -64 -cmdline=--compile-only -compress-all -link "
|
|
"--embedded-fatbin=$out $PROFILES") },
|
|
config, config);
|
|
rule.Comment = "Rule for CUDA fatbinaries.";
|
|
rule.Description = "Creating fatbinary $out";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteLinkRule(bool useResponseFile,
|
|
const std::string& config)
|
|
{
|
|
cmStateEnums::TargetType targetType = this->GetGeneratorTarget()->GetType();
|
|
|
|
std::string linkRuleName = this->LanguageLinkerRule(config);
|
|
if (!this->GetGlobalGenerator()->HasRule(linkRuleName)) {
|
|
cmNinjaRule rule(std::move(linkRuleName));
|
|
cmRulePlaceholderExpander::RuleVariables vars;
|
|
vars.CMTargetName = this->GetGeneratorTarget()->GetName().c_str();
|
|
vars.CMTargetType = cmState::GetTargetTypeName(targetType).c_str();
|
|
|
|
std::string lang = this->TargetLinkLanguage(config);
|
|
vars.Language = lang.c_str();
|
|
vars.AIXExports = "$AIX_EXPORTS";
|
|
|
|
if (this->TargetLinkLanguage(config) == "Swift") {
|
|
vars.SwiftLibraryName = "$SWIFT_LIBRARY_NAME";
|
|
vars.SwiftModule = "$SWIFT_MODULE";
|
|
vars.SwiftModuleName = "$SWIFT_MODULE_NAME";
|
|
vars.SwiftSources = "$SWIFT_SOURCES";
|
|
|
|
vars.Defines = "$DEFINES";
|
|
vars.Flags = "$FLAGS";
|
|
vars.Includes = "$INCLUDES";
|
|
}
|
|
|
|
std::string responseFlag;
|
|
|
|
std::string cmakeVarLang =
|
|
cmStrCat("CMAKE_", this->TargetLinkLanguage(config));
|
|
|
|
// build response file name
|
|
std::string cmakeLinkVar = cmakeVarLang + "_RESPONSE_FILE_LINK_FLAG";
|
|
cmValue flag = this->GetMakefile()->GetDefinition(cmakeLinkVar);
|
|
|
|
if (flag) {
|
|
responseFlag = *flag;
|
|
} else {
|
|
responseFlag = "@";
|
|
}
|
|
|
|
if (!useResponseFile || responseFlag.empty()) {
|
|
vars.Objects = "$in";
|
|
vars.LinkLibraries = "$LINK_PATH $LINK_LIBRARIES";
|
|
} else {
|
|
rule.RspFile = "$RSP_FILE";
|
|
responseFlag += rule.RspFile;
|
|
|
|
// build response file content
|
|
if (this->GetGlobalGenerator()->IsGCCOnWindows()) {
|
|
rule.RspContent = "$in";
|
|
} else {
|
|
rule.RspContent = "$in_newline";
|
|
}
|
|
rule.RspContent += " $LINK_PATH $LINK_LIBRARIES";
|
|
if (this->TargetLinkLanguage(config) == "Swift") {
|
|
vars.SwiftSources = responseFlag.c_str();
|
|
} else {
|
|
vars.Objects = responseFlag.c_str();
|
|
}
|
|
vars.LinkLibraries = "";
|
|
}
|
|
|
|
vars.ObjectDir = "$OBJECT_DIR";
|
|
|
|
vars.Target = "$TARGET_FILE";
|
|
|
|
vars.SONameFlag = "$SONAME_FLAG";
|
|
vars.TargetSOName = "$SONAME";
|
|
vars.TargetInstallNameDir = "$INSTALLNAME_DIR";
|
|
vars.TargetPDB = "$TARGET_PDB";
|
|
|
|
// Setup the target version.
|
|
std::string targetVersionMajor;
|
|
std::string targetVersionMinor;
|
|
{
|
|
std::ostringstream majorStream;
|
|
std::ostringstream minorStream;
|
|
int major;
|
|
int minor;
|
|
this->GetGeneratorTarget()->GetTargetVersion(major, minor);
|
|
majorStream << major;
|
|
minorStream << minor;
|
|
targetVersionMajor = majorStream.str();
|
|
targetVersionMinor = minorStream.str();
|
|
}
|
|
vars.TargetVersionMajor = targetVersionMajor.c_str();
|
|
vars.TargetVersionMinor = targetVersionMinor.c_str();
|
|
|
|
vars.Flags = "$FLAGS";
|
|
vars.LinkFlags = "$LINK_FLAGS";
|
|
vars.Manifests = "$MANIFESTS";
|
|
|
|
std::string langFlags;
|
|
if (targetType != cmStateEnums::EXECUTABLE) {
|
|
langFlags += "$LANGUAGE_COMPILE_FLAGS $ARCH_FLAGS";
|
|
vars.LanguageCompileFlags = langFlags.c_str();
|
|
}
|
|
|
|
std::string linkerLauncher = this->GetLinkerLauncher(config);
|
|
if (cmNonempty(linkerLauncher)) {
|
|
vars.Launcher = linkerLauncher.c_str();
|
|
}
|
|
|
|
std::string launcher;
|
|
cmValue val = this->GetLocalGenerator()->GetRuleLauncher(
|
|
this->GetGeneratorTarget(), "RULE_LAUNCH_LINK");
|
|
if (cmNonempty(val)) {
|
|
launcher = cmStrCat(*val, ' ');
|
|
}
|
|
|
|
std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
|
|
this->GetLocalGenerator()->CreateRulePlaceholderExpander());
|
|
|
|
// Rule for linking library/executable.
|
|
std::vector<std::string> linkCmds = this->ComputeLinkCmd(config);
|
|
for (std::string& linkCmd : linkCmds) {
|
|
linkCmd = cmStrCat(launcher, linkCmd);
|
|
rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
|
|
linkCmd, vars);
|
|
}
|
|
|
|
// If there is no ranlib the command will be ":". Skip it.
|
|
cm::erase_if(linkCmds, cmNinjaRemoveNoOpCommands());
|
|
|
|
linkCmds.insert(linkCmds.begin(), "$PRE_LINK");
|
|
linkCmds.emplace_back("$POST_BUILD");
|
|
rule.Command =
|
|
this->GetLocalGenerator()->BuildCommandLine(linkCmds, config, config);
|
|
|
|
// Write the linker rule with response file if needed.
|
|
rule.Comment =
|
|
cmStrCat("Rule for linking ", this->TargetLinkLanguage(config), ' ',
|
|
this->GetVisibleTypeName(), '.');
|
|
rule.Description =
|
|
cmStrCat("Linking ", this->TargetLinkLanguage(config), ' ',
|
|
this->GetVisibleTypeName(), " $TARGET_FILE");
|
|
rule.Restat = "$RESTAT";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
}
|
|
|
|
auto const tgtNames = this->TargetNames(config);
|
|
if (tgtNames.Output != tgtNames.Real &&
|
|
!this->GetGeneratorTarget()->IsFrameworkOnApple()) {
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
|
|
if (targetType == cmStateEnums::EXECUTABLE) {
|
|
cmNinjaRule rule("CMAKE_SYMLINK_EXECUTABLE");
|
|
{
|
|
std::vector<std::string> cmd;
|
|
cmd.push_back(cmakeCommand + " -E cmake_symlink_executable $in $out");
|
|
cmd.emplace_back("$POST_BUILD");
|
|
rule.Command =
|
|
this->GetLocalGenerator()->BuildCommandLine(cmd, config, config);
|
|
}
|
|
rule.Description = "Creating executable symlink $out";
|
|
rule.Comment = "Rule for creating executable symlink.";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
} else {
|
|
cmNinjaRule rule("CMAKE_SYMLINK_LIBRARY");
|
|
{
|
|
std::vector<std::string> cmd;
|
|
cmd.push_back(cmakeCommand +
|
|
" -E cmake_symlink_library $in $SONAME $out");
|
|
cmd.emplace_back("$POST_BUILD");
|
|
rule.Command =
|
|
this->GetLocalGenerator()->BuildCommandLine(cmd, config, config);
|
|
}
|
|
rule.Description = "Creating library symlink $out";
|
|
rule.Comment = "Rule for creating library symlink.";
|
|
this->GetGlobalGenerator()->AddRule(rule);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeDeviceLinkCmd()
|
|
{
|
|
std::vector<std::string> linkCmds;
|
|
|
|
// this target requires separable cuda compilation
|
|
// now build the correct command depending on if the target is
|
|
// an executable or a dynamic library.
|
|
switch (this->GetGeneratorTarget()->GetType()) {
|
|
case cmStateEnums::STATIC_LIBRARY:
|
|
case cmStateEnums::SHARED_LIBRARY:
|
|
case cmStateEnums::MODULE_LIBRARY: {
|
|
this->GetMakefile()->GetDefExpandList("CMAKE_CUDA_DEVICE_LINK_LIBRARY",
|
|
linkCmds);
|
|
} break;
|
|
case cmStateEnums::EXECUTABLE: {
|
|
this->GetMakefile()->GetDefExpandList(
|
|
"CMAKE_CUDA_DEVICE_LINK_EXECUTABLE", linkCmds);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
return linkCmds;
|
|
}
|
|
|
|
std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeLinkCmd(
|
|
const std::string& config)
|
|
{
|
|
std::vector<std::string> linkCmds;
|
|
cmMakefile* mf = this->GetMakefile();
|
|
{
|
|
// If we have a rule variable prefer it. In the case of static libraries
|
|
// this occurs when things like IPO is enabled, and we need to use the
|
|
// CMAKE_<lang>_CREATE_STATIC_LIBRARY_IPO define instead.
|
|
std::string linkCmdVar = this->GetGeneratorTarget()->GetCreateRuleVariable(
|
|
this->TargetLinkLanguage(config), config);
|
|
cmValue linkCmd = mf->GetDefinition(linkCmdVar);
|
|
if (linkCmd) {
|
|
std::string linkCmdStr = *linkCmd;
|
|
if (this->GetGeneratorTarget()->HasImplibGNUtoMS(config)) {
|
|
std::string ruleVar =
|
|
cmStrCat("CMAKE_", this->GeneratorTarget->GetLinkerLanguage(config),
|
|
"_GNUtoMS_RULE");
|
|
if (cmValue rule = this->Makefile->GetDefinition(ruleVar)) {
|
|
linkCmdStr += *rule;
|
|
}
|
|
}
|
|
cmExpandList(linkCmdStr, linkCmds);
|
|
if (this->UseLWYU) {
|
|
cmValue lwyuCheck = mf->GetDefinition("CMAKE_LINK_WHAT_YOU_USE_CHECK");
|
|
if (lwyuCheck) {
|
|
std::string cmakeCommand = cmStrCat(
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL),
|
|
" -E __run_co_compile --lwyu=");
|
|
cmakeCommand +=
|
|
this->GetLocalGenerator()->EscapeForShell(*lwyuCheck);
|
|
|
|
std::string targetOutputReal =
|
|
this->ConvertToNinjaPath(this->GetGeneratorTarget()->GetFullPath(
|
|
config, cmStateEnums::RuntimeBinaryArtifact,
|
|
/*realname=*/true));
|
|
cmakeCommand += cmStrCat(" --source=", targetOutputReal);
|
|
linkCmds.push_back(std::move(cmakeCommand));
|
|
}
|
|
}
|
|
return linkCmds;
|
|
}
|
|
}
|
|
switch (this->GetGeneratorTarget()->GetType()) {
|
|
case cmStateEnums::STATIC_LIBRARY: {
|
|
// We have archive link commands set. First, delete the existing archive.
|
|
{
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
|
|
linkCmds.push_back(cmakeCommand + " -E rm -f $TARGET_FILE");
|
|
}
|
|
// TODO: Use ARCHIVE_APPEND for archives over a certain size.
|
|
{
|
|
std::string linkCmdVar = cmStrCat(
|
|
"CMAKE_", this->TargetLinkLanguage(config), "_ARCHIVE_CREATE");
|
|
|
|
linkCmdVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
|
|
linkCmdVar, this->TargetLinkLanguage(config), config);
|
|
|
|
std::string const& linkCmd = mf->GetRequiredDefinition(linkCmdVar);
|
|
cmExpandList(linkCmd, linkCmds);
|
|
}
|
|
{
|
|
std::string linkCmdVar = cmStrCat(
|
|
"CMAKE_", this->TargetLinkLanguage(config), "_ARCHIVE_FINISH");
|
|
|
|
linkCmdVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
|
|
linkCmdVar, this->TargetLinkLanguage(config), config);
|
|
|
|
std::string const& linkCmd = mf->GetRequiredDefinition(linkCmdVar);
|
|
cmExpandList(linkCmd, linkCmds);
|
|
}
|
|
#ifdef __APPLE__
|
|
// On macOS ranlib truncates the fractional part of the static archive
|
|
// file modification time. If the archive and at least one contained
|
|
// object file were created within the same second this will make look
|
|
// the archive older than the object file. On subsequent ninja runs this
|
|
// leads to re-achiving and updating dependent targets.
|
|
// As a work-around we touch the archive after ranlib (see #19222).
|
|
{
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
|
|
linkCmds.push_back(cmakeCommand + " -E touch $TARGET_FILE");
|
|
}
|
|
#endif
|
|
} break;
|
|
case cmStateEnums::SHARED_LIBRARY:
|
|
case cmStateEnums::MODULE_LIBRARY:
|
|
break;
|
|
case cmStateEnums::EXECUTABLE:
|
|
if (this->TargetLinkLanguage(config) == "Swift") {
|
|
if (this->GeneratorTarget->IsExecutableWithExports()) {
|
|
this->Makefile->GetDefExpandList("CMAKE_EXE_EXPORTS_Swift_FLAG",
|
|
linkCmds);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
assert(false && "Unexpected target type");
|
|
}
|
|
return linkCmds;
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
|
|
const std::string& config, const std::string& fileConfig,
|
|
bool firstForConfig)
|
|
{
|
|
cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
|
|
if (!globalGen->GetLanguageEnabled("CUDA")) {
|
|
return;
|
|
}
|
|
|
|
cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
|
|
|
|
bool requiresDeviceLinking = requireDeviceLinking(
|
|
*this->GeneratorTarget, *this->GetLocalGenerator(), config);
|
|
if (!requiresDeviceLinking) {
|
|
return;
|
|
}
|
|
|
|
// First and very important step is to make sure while inside this
|
|
// step our link language is set to CUDA
|
|
std::string const& objExt =
|
|
this->Makefile->GetSafeDefinition("CMAKE_CUDA_OUTPUT_EXTENSION");
|
|
|
|
std::string targetOutputDir =
|
|
cmStrCat(this->GetLocalGenerator()->GetTargetDirectory(genTarget),
|
|
globalGen->ConfigDirectory(config), "/");
|
|
targetOutputDir = globalGen->ExpandCFGIntDir(targetOutputDir, config);
|
|
|
|
std::string targetOutputReal =
|
|
this->ConvertToNinjaPath(targetOutputDir + "cmake_device_link" + objExt);
|
|
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(targetOutputReal);
|
|
}
|
|
this->DeviceLinkObject = targetOutputReal;
|
|
|
|
// Write comments.
|
|
cmGlobalNinjaGenerator::WriteDivider(this->GetCommonFileStream());
|
|
this->GetCommonFileStream()
|
|
<< "# Device Link build statements for "
|
|
<< cmState::GetTargetTypeName(genTarget->GetType()) << " target "
|
|
<< this->GetTargetName() << "\n\n";
|
|
|
|
if (this->Makefile->GetSafeDefinition("CMAKE_CUDA_COMPILER_ID") == "Clang") {
|
|
std::string architecturesStr =
|
|
this->GeneratorTarget->GetSafeProperty("CUDA_ARCHITECTURES");
|
|
|
|
if (cmIsOff(architecturesStr)) {
|
|
this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
|
|
"CUDA_SEPARABLE_COMPILATION on Clang "
|
|
"requires CUDA_ARCHITECTURES to be set.");
|
|
return;
|
|
}
|
|
|
|
this->WriteDeviceLinkRules(config);
|
|
this->WriteDeviceLinkStatements(config, cmExpandedList(architecturesStr),
|
|
targetOutputReal);
|
|
} else {
|
|
this->WriteNvidiaDeviceLinkStatement(config, fileConfig, targetOutputDir,
|
|
targetOutputReal);
|
|
}
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatements(
|
|
const std::string& config, const std::vector<std::string>& architectures,
|
|
const std::string& output)
|
|
{
|
|
// Ensure there are no duplicates.
|
|
const cmNinjaDeps explicitDeps = [&]() -> std::vector<std::string> {
|
|
std::unordered_set<std::string> depsSet;
|
|
const cmNinjaDeps linkDeps =
|
|
this->ComputeLinkDeps(this->TargetLinkLanguage(config), config, true);
|
|
const cmNinjaDeps objects = this->GetObjects(config);
|
|
depsSet.insert(linkDeps.begin(), linkDeps.end());
|
|
depsSet.insert(objects.begin(), objects.end());
|
|
|
|
std::vector<std::string> deps;
|
|
std::copy(depsSet.begin(), depsSet.end(), std::back_inserter(deps));
|
|
return deps;
|
|
}();
|
|
|
|
cmGlobalNinjaGenerator* globalGen{ this->GetGlobalGenerator() };
|
|
const std::string objectDir =
|
|
cmStrCat(this->GeneratorTarget->GetSupportDirectory(),
|
|
globalGen->ConfigDirectory(config));
|
|
const std::string ninjaOutputDir = this->ConvertToNinjaPath(objectDir);
|
|
|
|
cmNinjaBuild fatbinary(this->LanguageLinkerCudaFatbinaryRule(config));
|
|
|
|
// Link device code for each architecture.
|
|
for (const std::string& architectureKind : architectures) {
|
|
// Clang always generates real code, so strip the specifier.
|
|
const std::string architecture =
|
|
architectureKind.substr(0, architectureKind.find('-'));
|
|
const std::string cubin =
|
|
cmStrCat(ninjaOutputDir, "/sm_", architecture, ".cubin");
|
|
|
|
cmNinjaBuild dlink(this->LanguageLinkerCudaDeviceRule(config));
|
|
dlink.ExplicitDeps = explicitDeps;
|
|
dlink.Outputs = { cubin };
|
|
dlink.Variables["ARCH"] = cmStrCat("sm_", architecture);
|
|
|
|
// The generated register file contains macros that when expanded register
|
|
// the device routines. Because the routines are the same for all
|
|
// architectures the register file will be the same too. Thus generate it
|
|
// only on the first invocation to reduce overhead.
|
|
if (fatbinary.ExplicitDeps.empty()) {
|
|
dlink.Variables["REGISTER"] = cmStrCat(
|
|
"--register-link-binaries=", ninjaOutputDir, "/cmake_cuda_register.h");
|
|
}
|
|
|
|
fatbinary.Variables["PROFILES"] +=
|
|
cmStrCat(" -im=profile=sm_", architecture, ",file=", cubin);
|
|
fatbinary.ExplicitDeps.emplace_back(cubin);
|
|
|
|
globalGen->WriteBuild(this->GetCommonFileStream(), dlink);
|
|
}
|
|
|
|
// Combine all architectures into a single fatbinary.
|
|
fatbinary.Outputs = { cmStrCat(ninjaOutputDir, "/cmake_cuda_fatbin.h") };
|
|
globalGen->WriteBuild(this->GetCommonFileStream(), fatbinary);
|
|
|
|
// Compile the stub that registers the kernels and contains the fatbinaries.
|
|
cmLocalNinjaGenerator* localGen{ this->GetLocalGenerator() };
|
|
cmNinjaBuild dcompile(this->LanguageLinkerCudaDeviceCompileRule(config));
|
|
dcompile.Outputs = { output };
|
|
dcompile.ExplicitDeps = { cmStrCat(ninjaOutputDir, "/cmake_cuda_fatbin.h") };
|
|
dcompile.Variables["FATBIN"] = localGen->ConvertToOutputFormat(
|
|
cmStrCat(objectDir, "/cmake_cuda_fatbin.h"), cmOutputConverter::SHELL);
|
|
dcompile.Variables["REGISTER"] = localGen->ConvertToOutputFormat(
|
|
cmStrCat(objectDir, "/cmake_cuda_register.h"), cmOutputConverter::SHELL);
|
|
|
|
cmNinjaLinkLineDeviceComputer linkLineComputer(
|
|
localGen, localGen->GetStateSnapshot().GetDirectory(), globalGen);
|
|
linkLineComputer.SetUseNinjaMulti(globalGen->IsMultiConfig());
|
|
|
|
// Link libraries and paths are only used during the final executable/library
|
|
// link.
|
|
std::string frameworkPath;
|
|
std::string linkPath;
|
|
std::string linkLibs;
|
|
localGen->GetDeviceLinkFlags(linkLineComputer, config, linkLibs,
|
|
dcompile.Variables["LINK_FLAGS"], frameworkPath,
|
|
linkPath, this->GetGeneratorTarget());
|
|
|
|
globalGen->WriteBuild(this->GetCommonFileStream(), dcompile);
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteNvidiaDeviceLinkStatement(
|
|
const std::string& config, const std::string& fileConfig,
|
|
const std::string& outputDir, const std::string& output)
|
|
{
|
|
cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
|
|
cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
|
|
|
|
std::string targetOutputImplib = this->ConvertToNinjaPath(
|
|
genTarget->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
|
|
|
|
if (config != fileConfig) {
|
|
std::string targetOutputFileConfigDir =
|
|
cmStrCat(this->GetLocalGenerator()->GetTargetDirectory(genTarget),
|
|
globalGen->ConfigDirectory(fileConfig), "/");
|
|
targetOutputFileConfigDir =
|
|
globalGen->ExpandCFGIntDir(outputDir, fileConfig);
|
|
if (outputDir == targetOutputFileConfigDir) {
|
|
return;
|
|
}
|
|
|
|
if (!genTarget->GetFullName(config, cmStateEnums::ImportLibraryArtifact)
|
|
.empty() &&
|
|
!genTarget
|
|
->GetFullName(fileConfig, cmStateEnums::ImportLibraryArtifact)
|
|
.empty() &&
|
|
targetOutputImplib ==
|
|
this->ConvertToNinjaPath(genTarget->GetFullPath(
|
|
fileConfig, cmStateEnums::ImportLibraryArtifact))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Compute the comment.
|
|
cmNinjaBuild build(this->LanguageLinkerDeviceRule(config));
|
|
build.Comment =
|
|
cmStrCat("Link the ", this->GetVisibleTypeName(), ' ', output);
|
|
|
|
cmNinjaVars& vars = build.Variables;
|
|
|
|
// Compute outputs.
|
|
build.Outputs.push_back(output);
|
|
// Compute specific libraries to link with.
|
|
build.ExplicitDeps = this->GetObjects(config);
|
|
build.ImplicitDeps =
|
|
this->ComputeLinkDeps(this->TargetLinkLanguage(config), config);
|
|
|
|
std::string frameworkPath;
|
|
std::string linkPath;
|
|
|
|
std::string createRule =
|
|
genTarget->GetCreateRuleVariable(this->TargetLinkLanguage(config), config);
|
|
cmLocalNinjaGenerator& localGen = *this->GetLocalGenerator();
|
|
|
|
vars["TARGET_FILE"] =
|
|
localGen.ConvertToOutputFormat(output, cmOutputConverter::SHELL);
|
|
|
|
cmNinjaLinkLineDeviceComputer linkLineComputer(
|
|
this->GetLocalGenerator(),
|
|
this->GetLocalGenerator()->GetStateSnapshot().GetDirectory(), globalGen);
|
|
linkLineComputer.SetUseNinjaMulti(globalGen->IsMultiConfig());
|
|
|
|
localGen.GetDeviceLinkFlags(linkLineComputer, config, vars["LINK_LIBRARIES"],
|
|
vars["LINK_FLAGS"], frameworkPath, linkPath,
|
|
genTarget);
|
|
|
|
this->addPoolNinjaVariable("JOB_POOL_LINK", genTarget, vars);
|
|
|
|
vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
|
|
|
|
vars["MANIFESTS"] = this->GetManifests(config);
|
|
|
|
vars["LINK_PATH"] = frameworkPath + linkPath;
|
|
|
|
// Compute language specific link flags.
|
|
std::string langFlags;
|
|
localGen.AddLanguageFlagsForLinking(langFlags, genTarget, "CUDA", config);
|
|
vars["LANGUAGE_COMPILE_FLAGS"] = langFlags;
|
|
|
|
auto const tgtNames = this->TargetNames(config);
|
|
if (genTarget->HasSOName(config)) {
|
|
vars["SONAME_FLAG"] =
|
|
this->GetMakefile()->GetSONameFlag(this->TargetLinkLanguage(config));
|
|
vars["SONAME"] = localGen.ConvertToOutputFormat(tgtNames.SharedObject,
|
|
cmOutputConverter::SHELL);
|
|
if (genTarget->GetType() == cmStateEnums::SHARED_LIBRARY) {
|
|
std::string install_dir =
|
|
this->GetGeneratorTarget()->GetInstallNameDirForBuildTree(config);
|
|
if (!install_dir.empty()) {
|
|
vars["INSTALLNAME_DIR"] = localGen.ConvertToOutputFormat(
|
|
install_dir, cmOutputConverter::SHELL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!tgtNames.ImportLibrary.empty()) {
|
|
const std::string impLibPath = localGen.ConvertToOutputFormat(
|
|
targetOutputImplib, cmOutputConverter::SHELL);
|
|
vars["TARGET_IMPLIB"] = impLibPath;
|
|
this->EnsureParentDirectoryExists(targetOutputImplib);
|
|
}
|
|
|
|
const std::string objPath =
|
|
cmStrCat(this->GetGeneratorTarget()->GetSupportDirectory(),
|
|
globalGen->ConfigDirectory(config));
|
|
|
|
vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
this->ConvertToNinjaPath(objPath), cmOutputConverter::SHELL);
|
|
this->EnsureDirectoryExists(objPath);
|
|
|
|
this->SetMsvcTargetPdbVariable(vars, config);
|
|
|
|
std::string& linkLibraries = vars["LINK_LIBRARIES"];
|
|
std::string& link_path = vars["LINK_PATH"];
|
|
if (globalGen->IsGCCOnWindows()) {
|
|
// ar.exe can't handle backslashes in rsp files (implicitly used by gcc)
|
|
std::replace(linkLibraries.begin(), linkLibraries.end(), '\\', '/');
|
|
std::replace(link_path.begin(), link_path.end(), '\\', '/');
|
|
}
|
|
|
|
// Device linking currently doesn't support response files so
|
|
// do not check if the user has explicitly forced a response file.
|
|
int const commandLineLengthLimit =
|
|
static_cast<int>(cmSystemTools::CalculateCommandLineLengthLimit()) -
|
|
globalGen->GetRuleCmdLength(build.Rule);
|
|
|
|
build.RspFile = this->ConvertToNinjaPath(
|
|
cmStrCat("CMakeFiles/", genTarget->GetName(),
|
|
globalGen->IsMultiConfig() ? cmStrCat('.', config) : "", ".rsp"));
|
|
|
|
// Gather order-only dependencies.
|
|
this->GetLocalGenerator()->AppendTargetDepends(
|
|
this->GetGeneratorTarget(), build.OrderOnlyDeps, config, config,
|
|
DependOnTargetArtifact);
|
|
|
|
// Write the build statement for this target.
|
|
bool usedResponseFile = false;
|
|
globalGen->WriteBuild(this->GetCommonFileStream(), build,
|
|
commandLineLengthLimit, &usedResponseFile);
|
|
this->WriteNvidiaDeviceLinkRule(usedResponseFile, config);
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteLinkStatement(
|
|
const std::string& config, const std::string& fileConfig,
|
|
bool firstForConfig)
|
|
{
|
|
cmMakefile* mf = this->GetMakefile();
|
|
cmGlobalNinjaGenerator* globalGen = this->GetGlobalGenerator();
|
|
cmGeneratorTarget* gt = this->GetGeneratorTarget();
|
|
|
|
std::string targetOutput = this->ConvertToNinjaPath(gt->GetFullPath(config));
|
|
std::string targetOutputReal = this->ConvertToNinjaPath(
|
|
gt->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact,
|
|
/*realname=*/true));
|
|
std::string targetOutputImplib = this->ConvertToNinjaPath(
|
|
gt->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
|
|
|
|
if (config != fileConfig) {
|
|
if (targetOutput ==
|
|
this->ConvertToNinjaPath(gt->GetFullPath(fileConfig))) {
|
|
return;
|
|
}
|
|
if (targetOutputReal ==
|
|
this->ConvertToNinjaPath(
|
|
gt->GetFullPath(fileConfig, cmStateEnums::RuntimeBinaryArtifact,
|
|
/*realname=*/true))) {
|
|
return;
|
|
}
|
|
if (!gt->GetFullName(config, cmStateEnums::ImportLibraryArtifact)
|
|
.empty() &&
|
|
!gt->GetFullName(fileConfig, cmStateEnums::ImportLibraryArtifact)
|
|
.empty() &&
|
|
targetOutputImplib ==
|
|
this->ConvertToNinjaPath(gt->GetFullPath(
|
|
fileConfig, cmStateEnums::ImportLibraryArtifact))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto const tgtNames = this->TargetNames(config);
|
|
if (gt->IsAppBundleOnApple()) {
|
|
// Create the app bundle
|
|
std::string outpath = gt->GetDirectory(config);
|
|
this->OSXBundleGenerator->CreateAppBundle(tgtNames.Output, outpath,
|
|
config);
|
|
|
|
// Calculate the output path
|
|
targetOutput = cmStrCat(outpath, '/', tgtNames.Output);
|
|
targetOutput = this->ConvertToNinjaPath(targetOutput);
|
|
targetOutputReal = cmStrCat(outpath, '/', tgtNames.Real);
|
|
targetOutputReal = this->ConvertToNinjaPath(targetOutputReal);
|
|
} else if (gt->IsFrameworkOnApple()) {
|
|
// Create the library framework.
|
|
|
|
cmOSXBundleGenerator::SkipParts bundleSkipParts;
|
|
if (globalGen->GetName() == "Ninja Multi-Config") {
|
|
const auto postFix = this->GeneratorTarget->GetFilePostfix(config);
|
|
// Skip creating Info.plist when there are multiple configurations, and
|
|
// the current configuration has a postfix. The non-postfix configuration
|
|
// Info.plist can be used by all the other configurations.
|
|
if (!postFix.empty()) {
|
|
bundleSkipParts.infoPlist = true;
|
|
}
|
|
}
|
|
|
|
this->OSXBundleGenerator->CreateFramework(
|
|
tgtNames.Output, gt->GetDirectory(config), config, bundleSkipParts);
|
|
} else if (gt->IsCFBundleOnApple()) {
|
|
// Create the core foundation bundle.
|
|
this->OSXBundleGenerator->CreateCFBundle(tgtNames.Output,
|
|
gt->GetDirectory(config), config);
|
|
}
|
|
|
|
// Write comments.
|
|
cmGlobalNinjaGenerator::WriteDivider(this->GetImplFileStream(fileConfig));
|
|
const cmStateEnums::TargetType targetType = gt->GetType();
|
|
this->GetImplFileStream(fileConfig)
|
|
<< "# Link build statements for " << cmState::GetTargetTypeName(targetType)
|
|
<< " target " << this->GetTargetName() << "\n\n";
|
|
|
|
cmNinjaBuild linkBuild(this->LanguageLinkerRule(config));
|
|
cmNinjaVars& vars = linkBuild.Variables;
|
|
|
|
// Compute the comment.
|
|
linkBuild.Comment =
|
|
cmStrCat("Link the ", this->GetVisibleTypeName(), ' ', targetOutputReal);
|
|
|
|
// Compute outputs.
|
|
linkBuild.Outputs.push_back(targetOutputReal);
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(targetOutputReal);
|
|
}
|
|
|
|
if (this->TargetLinkLanguage(config) == "Swift") {
|
|
vars["SWIFT_LIBRARY_NAME"] = [this, config]() -> std::string {
|
|
cmGeneratorTarget::Names targetNames =
|
|
this->GetGeneratorTarget()->GetLibraryNames(config);
|
|
return targetNames.Base;
|
|
}();
|
|
|
|
vars["SWIFT_MODULE_NAME"] = [gt]() -> std::string {
|
|
if (cmValue name = gt->GetProperty("Swift_MODULE_NAME")) {
|
|
return *name;
|
|
}
|
|
return gt->GetName();
|
|
}();
|
|
|
|
vars["SWIFT_MODULE"] = [this](const std::string& module) -> std::string {
|
|
std::string directory =
|
|
this->GetLocalGenerator()->GetCurrentBinaryDirectory();
|
|
if (cmValue prop = this->GetGeneratorTarget()->GetProperty(
|
|
"Swift_MODULE_DIRECTORY")) {
|
|
directory = *prop;
|
|
}
|
|
|
|
std::string name = module + ".swiftmodule";
|
|
if (cmValue prop =
|
|
this->GetGeneratorTarget()->GetProperty("Swift_MODULE")) {
|
|
name = *prop;
|
|
}
|
|
|
|
return this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
this->ConvertToNinjaPath(directory + "/" + name),
|
|
cmOutputConverter::SHELL);
|
|
}(vars["SWIFT_MODULE_NAME"]);
|
|
|
|
vars["SWIFT_SOURCES"] = [this, config]() -> std::string {
|
|
std::vector<cmSourceFile const*> sources;
|
|
std::stringstream oss;
|
|
|
|
this->GetGeneratorTarget()->GetObjectSources(sources, config);
|
|
cmLocalGenerator const* LocalGen = this->GetLocalGenerator();
|
|
for (const auto& source : sources) {
|
|
const std::string sourcePath = source->GetLanguage() == "Swift"
|
|
? this->GetCompiledSourceNinjaPath(source)
|
|
: this->GetObjectFilePath(source, config);
|
|
oss << " "
|
|
<< LocalGen->ConvertToOutputFormat(sourcePath,
|
|
cmOutputConverter::SHELL);
|
|
}
|
|
return oss.str();
|
|
}();
|
|
|
|
// Since we do not perform object builds, compute the
|
|
// defines/flags/includes here so that they can be passed along
|
|
// appropriately.
|
|
vars["DEFINES"] = this->GetDefines("Swift", config);
|
|
vars["FLAGS"] = this->GetFlags("Swift", config);
|
|
vars["INCLUDES"] = this->GetIncludes("Swift", config);
|
|
this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]);
|
|
}
|
|
|
|
// Compute specific libraries to link with.
|
|
if (this->TargetLinkLanguage(config) == "Swift") {
|
|
std::vector<cmSourceFile const*> sources;
|
|
gt->GetObjectSources(sources, config);
|
|
for (const auto& source : sources) {
|
|
if (source->GetLanguage() == "Swift") {
|
|
linkBuild.Outputs.push_back(
|
|
this->ConvertToNinjaPath(this->GetObjectFilePath(source, config)));
|
|
linkBuild.ExplicitDeps.emplace_back(
|
|
this->GetCompiledSourceNinjaPath(source));
|
|
} else {
|
|
linkBuild.ExplicitDeps.emplace_back(
|
|
this->GetObjectFilePath(source, config));
|
|
}
|
|
}
|
|
linkBuild.Outputs.push_back(vars["SWIFT_MODULE"]);
|
|
} else {
|
|
linkBuild.ExplicitDeps = this->GetObjects(config);
|
|
}
|
|
|
|
std::vector<std::string> extraISPCObjects =
|
|
this->GetGeneratorTarget()->GetGeneratedISPCObjects(config);
|
|
std::transform(extraISPCObjects.begin(), extraISPCObjects.end(),
|
|
std::back_inserter(linkBuild.ExplicitDeps),
|
|
this->MapToNinjaPath());
|
|
|
|
linkBuild.ImplicitDeps =
|
|
this->ComputeLinkDeps(this->TargetLinkLanguage(config), config);
|
|
|
|
if (!this->DeviceLinkObject.empty()) {
|
|
linkBuild.ExplicitDeps.push_back(this->DeviceLinkObject);
|
|
}
|
|
|
|
std::string frameworkPath;
|
|
std::string linkPath;
|
|
|
|
std::string createRule =
|
|
gt->GetCreateRuleVariable(this->TargetLinkLanguage(config), config);
|
|
bool useWatcomQuote = mf->IsOn(createRule + "_USE_WATCOM_QUOTE");
|
|
cmLocalNinjaGenerator& localGen = *this->GetLocalGenerator();
|
|
|
|
vars["TARGET_FILE"] =
|
|
localGen.ConvertToOutputFormat(targetOutputReal, cmOutputConverter::SHELL);
|
|
|
|
std::unique_ptr<cmLinkLineComputer> linkLineComputer =
|
|
globalGen->CreateLinkLineComputer(
|
|
this->GetLocalGenerator(),
|
|
this->GetLocalGenerator()->GetStateSnapshot().GetDirectory());
|
|
linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
|
|
linkLineComputer->SetUseNinjaMulti(globalGen->IsMultiConfig());
|
|
|
|
localGen.GetTargetFlags(linkLineComputer.get(), config,
|
|
vars["LINK_LIBRARIES"], vars["FLAGS"],
|
|
vars["LINK_FLAGS"], frameworkPath, linkPath, gt);
|
|
|
|
// Add OS X version flags, if any.
|
|
if (this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY ||
|
|
this->GeneratorTarget->GetType() == cmStateEnums::MODULE_LIBRARY) {
|
|
this->AppendOSXVerFlag(vars["LINK_FLAGS"],
|
|
this->TargetLinkLanguage(config), "COMPATIBILITY",
|
|
true);
|
|
this->AppendOSXVerFlag(vars["LINK_FLAGS"],
|
|
this->TargetLinkLanguage(config), "CURRENT", false);
|
|
}
|
|
|
|
this->addPoolNinjaVariable("JOB_POOL_LINK", gt, vars);
|
|
|
|
this->UseLWYU = this->GetLocalGenerator()->AppendLWYUFlags(
|
|
vars["LINK_FLAGS"], this->GetGeneratorTarget(),
|
|
this->TargetLinkLanguage(config));
|
|
|
|
vars["LINK_FLAGS"] = globalGen->EncodeLiteral(vars["LINK_FLAGS"]);
|
|
|
|
vars["MANIFESTS"] = this->GetManifests(config);
|
|
vars["AIX_EXPORTS"] = this->GetAIXExports(config);
|
|
|
|
vars["LINK_PATH"] = frameworkPath + linkPath;
|
|
|
|
// Compute architecture specific link flags. Yes, these go into a different
|
|
// variable for executables, probably due to a mistake made when duplicating
|
|
// code between the Makefile executable and library generators.
|
|
if (targetType == cmStateEnums::EXECUTABLE) {
|
|
std::string t = vars["FLAGS"];
|
|
localGen.AddArchitectureFlags(t, gt, this->TargetLinkLanguage(config),
|
|
config);
|
|
vars["FLAGS"] = t;
|
|
} else {
|
|
std::string t = vars["ARCH_FLAGS"];
|
|
localGen.AddArchitectureFlags(t, gt, this->TargetLinkLanguage(config),
|
|
config);
|
|
vars["ARCH_FLAGS"] = t;
|
|
t.clear();
|
|
localGen.AddLanguageFlagsForLinking(
|
|
t, gt, this->TargetLinkLanguage(config), config);
|
|
vars["LANGUAGE_COMPILE_FLAGS"] = t;
|
|
}
|
|
if (gt->HasSOName(config)) {
|
|
vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage(config));
|
|
vars["SONAME"] = localGen.ConvertToOutputFormat(tgtNames.SharedObject,
|
|
cmOutputConverter::SHELL);
|
|
if (targetType == cmStateEnums::SHARED_LIBRARY) {
|
|
std::string install_dir = gt->GetInstallNameDirForBuildTree(config);
|
|
if (!install_dir.empty()) {
|
|
vars["INSTALLNAME_DIR"] = localGen.ConvertToOutputFormat(
|
|
install_dir, cmOutputConverter::SHELL);
|
|
}
|
|
}
|
|
}
|
|
|
|
cmGlobalNinjaGenerator::CCOutputs byproducts(this->GetGlobalGenerator());
|
|
|
|
if (!tgtNames.ImportLibrary.empty()) {
|
|
const std::string impLibPath = localGen.ConvertToOutputFormat(
|
|
targetOutputImplib, cmOutputConverter::SHELL);
|
|
vars["TARGET_IMPLIB"] = impLibPath;
|
|
this->EnsureParentDirectoryExists(targetOutputImplib);
|
|
if (gt->HasImportLibrary(config)) {
|
|
// Some linkers may update a binary without touching its import lib.
|
|
byproducts.ExplicitOuts.emplace_back(targetOutputImplib);
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(
|
|
targetOutputImplib);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!this->SetMsvcTargetPdbVariable(vars, config)) {
|
|
// It is common to place debug symbols at a specific place,
|
|
// so we need a plain target name in the rule available.
|
|
std::string prefix;
|
|
std::string base;
|
|
std::string suffix;
|
|
gt->GetFullNameComponents(prefix, base, suffix, config);
|
|
std::string dbg_suffix = ".dbg";
|
|
// TODO: Where to document?
|
|
if (cmValue d = mf->GetDefinition("CMAKE_DEBUG_SYMBOL_SUFFIX")) {
|
|
dbg_suffix = *d;
|
|
}
|
|
vars["TARGET_PDB"] = base + suffix + dbg_suffix;
|
|
}
|
|
|
|
const std::string objPath =
|
|
cmStrCat(gt->GetSupportDirectory(), globalGen->ConfigDirectory(config));
|
|
vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
this->ConvertToNinjaPath(objPath), cmOutputConverter::SHELL);
|
|
this->EnsureDirectoryExists(objPath);
|
|
|
|
std::string& linkLibraries = vars["LINK_LIBRARIES"];
|
|
std::string& link_path = vars["LINK_PATH"];
|
|
if (globalGen->IsGCCOnWindows()) {
|
|
// ar.exe can't handle backslashes in rsp files (implicitly used by gcc)
|
|
std::replace(linkLibraries.begin(), linkLibraries.end(), '\\', '/');
|
|
std::replace(link_path.begin(), link_path.end(), '\\', '/');
|
|
}
|
|
|
|
const std::vector<cmCustomCommand>* cmdLists[3] = {
|
|
>->GetPreBuildCommands(), >->GetPreLinkCommands(),
|
|
>->GetPostBuildCommands()
|
|
};
|
|
|
|
std::vector<std::string> preLinkCmdLines;
|
|
std::vector<std::string> postBuildCmdLines;
|
|
|
|
std::vector<std::string>* cmdLineLists[3] = { &preLinkCmdLines,
|
|
&preLinkCmdLines,
|
|
&postBuildCmdLines };
|
|
|
|
for (unsigned i = 0; i != 3; ++i) {
|
|
for (cmCustomCommand const& cc : *cmdLists[i]) {
|
|
if (config == fileConfig ||
|
|
this->GetLocalGenerator()->HasUniqueByproducts(cc.GetByproducts(),
|
|
cc.GetBacktrace())) {
|
|
cmCustomCommandGenerator ccg(cc, fileConfig, this->GetLocalGenerator(),
|
|
true, config);
|
|
localGen.AppendCustomCommandLines(ccg, *cmdLineLists[i]);
|
|
std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
|
|
byproducts.Add(ccByproducts);
|
|
std::transform(
|
|
ccByproducts.begin(), ccByproducts.end(),
|
|
std::back_inserter(globalGen->GetByproductsForCleanTarget()),
|
|
this->MapToNinjaPath());
|
|
}
|
|
}
|
|
}
|
|
|
|
// maybe create .def file from list of objects
|
|
cmGeneratorTarget::ModuleDefinitionInfo const* mdi =
|
|
gt->GetModuleDefinitionInfo(config);
|
|
if (mdi && mdi->DefFileGenerated) {
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
|
|
std::string cmd =
|
|
cmStrCat(cmakeCommand, " -E __create_def ",
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
mdi->DefFile, cmOutputConverter::SHELL),
|
|
' ');
|
|
std::string obj_list_file = mdi->DefFile + ".objs";
|
|
cmd += this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
obj_list_file, cmOutputConverter::SHELL);
|
|
|
|
cmValue nm_executable = this->GetMakefile()->GetDefinition("CMAKE_NM");
|
|
if (cmNonempty(nm_executable)) {
|
|
cmd += " --nm=";
|
|
cmd += this->LocalCommonGenerator->ConvertToOutputFormat(
|
|
*nm_executable, cmOutputConverter::SHELL);
|
|
}
|
|
preLinkCmdLines.push_back(std::move(cmd));
|
|
|
|
// create a list of obj files for the -E __create_def to read
|
|
cmGeneratedFileStream fout(obj_list_file);
|
|
|
|
if (mdi->WindowsExportAllSymbols) {
|
|
cmNinjaDeps objs = this->GetObjects(config);
|
|
for (std::string const& obj : objs) {
|
|
if (cmHasLiteralSuffix(obj, ".obj")) {
|
|
fout << obj << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
for (cmSourceFile const* src : mdi->Sources) {
|
|
fout << src->GetFullPath() << "\n";
|
|
}
|
|
}
|
|
// If we have any PRE_LINK commands, we need to go back to CMAKE_BINARY_DIR
|
|
// for the link commands.
|
|
if (!preLinkCmdLines.empty()) {
|
|
const std::string homeOutDir = localGen.ConvertToOutputFormat(
|
|
localGen.GetBinaryDirectory(), cmOutputConverter::SHELL);
|
|
preLinkCmdLines.push_back("cd " + homeOutDir);
|
|
}
|
|
|
|
vars["PRE_LINK"] = localGen.BuildCommandLine(
|
|
preLinkCmdLines, config, fileConfig, "pre-link", this->GeneratorTarget);
|
|
std::string postBuildCmdLine =
|
|
localGen.BuildCommandLine(postBuildCmdLines, config, fileConfig,
|
|
"post-build", this->GeneratorTarget);
|
|
|
|
cmNinjaVars symlinkVars;
|
|
bool const symlinkNeeded =
|
|
(targetOutput != targetOutputReal && !gt->IsFrameworkOnApple());
|
|
if (!symlinkNeeded) {
|
|
vars["POST_BUILD"] = postBuildCmdLine;
|
|
} else {
|
|
vars["POST_BUILD"] = cmGlobalNinjaGenerator::SHELL_NOOP;
|
|
symlinkVars["POST_BUILD"] = postBuildCmdLine;
|
|
}
|
|
|
|
std::string cmakeVarLang =
|
|
cmStrCat("CMAKE_", this->TargetLinkLanguage(config));
|
|
|
|
// build response file name
|
|
std::string cmakeLinkVar = cmakeVarLang + "_RESPONSE_FILE_LINK_FLAG";
|
|
|
|
cmValue flag = this->GetMakefile()->GetDefinition(cmakeLinkVar);
|
|
|
|
bool const lang_supports_response =
|
|
!(this->TargetLinkLanguage(config) == "RC" ||
|
|
(this->TargetLinkLanguage(config) == "CUDA" && !flag));
|
|
int commandLineLengthLimit = -1;
|
|
if (!lang_supports_response || !this->ForceResponseFile()) {
|
|
commandLineLengthLimit =
|
|
static_cast<int>(cmSystemTools::CalculateCommandLineLengthLimit()) -
|
|
globalGen->GetRuleCmdLength(linkBuild.Rule);
|
|
}
|
|
|
|
linkBuild.RspFile = this->ConvertToNinjaPath(
|
|
cmStrCat("CMakeFiles/", gt->GetName(),
|
|
globalGen->IsMultiConfig() ? cmStrCat('.', config) : "", ".rsp"));
|
|
|
|
// Gather order-only dependencies.
|
|
this->GetLocalGenerator()->AppendTargetDepends(
|
|
gt, linkBuild.OrderOnlyDeps, config, fileConfig, DependOnTargetArtifact);
|
|
|
|
// Add order-only dependencies on versioning symlinks of shared libs we link.
|
|
if (!this->GeneratorTarget->IsDLLPlatform()) {
|
|
if (cmComputeLinkInformation* cli =
|
|
this->GeneratorTarget->GetLinkInformation(config)) {
|
|
for (auto const& item : cli->GetItems()) {
|
|
if (item.Target &&
|
|
item.Target->GetType() == cmStateEnums::SHARED_LIBRARY &&
|
|
!item.Target->IsFrameworkOnApple()) {
|
|
std::string const& lib =
|
|
this->ConvertToNinjaPath(item.Target->GetFullPath(config));
|
|
if (std::find(linkBuild.ImplicitDeps.begin(),
|
|
linkBuild.ImplicitDeps.end(),
|
|
lib) == linkBuild.ImplicitDeps.end()) {
|
|
linkBuild.OrderOnlyDeps.emplace_back(lib);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ninja should restat after linking if and only if there are byproducts.
|
|
vars["RESTAT"] = byproducts.ExplicitOuts.empty() ? "" : "1";
|
|
|
|
linkBuild.Outputs.reserve(linkBuild.Outputs.size() +
|
|
byproducts.ExplicitOuts.size());
|
|
std::move(byproducts.ExplicitOuts.begin(), byproducts.ExplicitOuts.end(),
|
|
std::back_inserter(linkBuild.Outputs));
|
|
linkBuild.WorkDirOuts = std::move(byproducts.WorkDirOuts);
|
|
|
|
// Write the build statement for this target.
|
|
bool usedResponseFile = false;
|
|
globalGen->WriteBuild(this->GetImplFileStream(fileConfig), linkBuild,
|
|
commandLineLengthLimit, &usedResponseFile);
|
|
this->WriteLinkRule(usedResponseFile, config);
|
|
|
|
if (symlinkNeeded) {
|
|
if (targetType == cmStateEnums::EXECUTABLE) {
|
|
cmNinjaBuild build("CMAKE_SYMLINK_EXECUTABLE");
|
|
build.Comment = "Create executable symlink " + targetOutput;
|
|
build.Outputs.push_back(targetOutput);
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(targetOutput);
|
|
}
|
|
build.ExplicitDeps.push_back(targetOutputReal);
|
|
build.Variables = std::move(symlinkVars);
|
|
globalGen->WriteBuild(this->GetImplFileStream(fileConfig), build);
|
|
} else {
|
|
cmNinjaBuild build("CMAKE_SYMLINK_LIBRARY");
|
|
build.Comment = "Create library symlink " + targetOutput;
|
|
|
|
std::string const soName = this->ConvertToNinjaPath(
|
|
this->GetTargetFilePath(tgtNames.SharedObject, config));
|
|
// If one link has to be created.
|
|
if (targetOutputReal == soName || targetOutput == soName) {
|
|
symlinkVars["SONAME"] =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
soName, cmOutputConverter::SHELL);
|
|
} else {
|
|
symlinkVars["SONAME"].clear();
|
|
build.Outputs.push_back(soName);
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(soName);
|
|
}
|
|
}
|
|
build.Outputs.push_back(targetOutput);
|
|
if (firstForConfig) {
|
|
globalGen->GetByproductsForCleanTarget(config).push_back(targetOutput);
|
|
}
|
|
build.ExplicitDeps.push_back(targetOutputReal);
|
|
build.Variables = std::move(symlinkVars);
|
|
|
|
globalGen->WriteBuild(this->GetImplFileStream(fileConfig), build);
|
|
}
|
|
}
|
|
|
|
// Add aliases for the file name and the target name.
|
|
globalGen->AddTargetAlias(tgtNames.Output, gt, config);
|
|
globalGen->AddTargetAlias(this->GetTargetName(), gt, config);
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteObjectLibStatement(
|
|
const std::string& config)
|
|
{
|
|
// Write a phony output that depends on all object files.
|
|
{
|
|
cmNinjaBuild build("phony");
|
|
build.Comment = "Object library " + this->GetTargetName();
|
|
this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
|
|
build.Outputs, config);
|
|
this->GetLocalGenerator()->AppendTargetOutputs(
|
|
this->GetGeneratorTarget(),
|
|
this->GetGlobalGenerator()->GetByproductsForCleanTarget(config), config);
|
|
build.ExplicitDeps = this->GetObjects(config);
|
|
this->GetGlobalGenerator()->WriteBuild(this->GetCommonFileStream(), build);
|
|
}
|
|
|
|
// Add aliases for the target name.
|
|
this->GetGlobalGenerator()->AddTargetAlias(
|
|
this->GetTargetName(), this->GetGeneratorTarget(), config);
|
|
}
|
|
|
|
cmGeneratorTarget::Names cmNinjaNormalTargetGenerator::TargetNames(
|
|
const std::string& config) const
|
|
{
|
|
if (this->GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE) {
|
|
return this->GeneratorTarget->GetExecutableNames(config);
|
|
}
|
|
return this->GeneratorTarget->GetLibraryNames(config);
|
|
}
|
|
|
|
std::string cmNinjaNormalTargetGenerator::TargetLinkLanguage(
|
|
const std::string& config) const
|
|
{
|
|
return this->GeneratorTarget->GetLinkerLanguage(config);
|
|
}
|