in Source/cmNinjaNormalTargetGenerator.cxx [1098:1663]
void cmNinjaNormalTargetGenerator::WriteLinkStatement(
std::string const& config, std::string const& 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") {
auto const 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;
}
}
if (gt->HasImportLibrary(config)) {
bundleSkipParts.TextStubs = false;
}
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));
cmStateEnums::TargetType const 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;
if (this->GeneratorTarget->HasLinkDependencyFile(config)) {
this->AddDepfileBinding(vars,
this->ConvertToNinjaPath(
this->GetLocalGenerator()->GetLinkDependencyFile(
this->GeneratorTarget, config)));
}
// 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 we can't split the Swift build model (CMP0157 is OLD or unset), fall
// back on the old one-step "build/link" logic.
if (!this->GetLocalGenerator()->IsSplitSwiftBuild() &&
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->GetSwiftModuleName();
vars["SWIFT_MODULE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
this->ConvertToNinjaPath(gt->GetSwiftModulePath(config)),
cmOutputConverter::SHELL);
vars["SWIFT_SOURCES"] = [this, config]() -> std::string {
std::vector<cmSourceFile const*> sourceFiles;
std::stringstream oss;
this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
cmLocalGenerator const* LocalGen = this->GetLocalGenerator();
for (auto const& source : sourceFiles) {
std::string const 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.
std::vector<cmSourceFile const*> sources;
gt->GetObjectSources(sources, config);
for (auto const& 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));
}
}
if (targetType != cmStateEnums::EXECUTABLE ||
gt->IsExecutableWithExports()) {
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);
localGen.AppendDependencyInfoLinkerFlags(vars["LINK_FLAGS"], gt, config,
this->TargetLinkLanguage(config));
// 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["MANIFESTS"] = this->GetManifests(config);
vars["AIX_EXPORTS"] = this->GetAIXExports(config);
vars["LINK_PATH"] = frameworkPath + linkPath;
vars["CONFIG"] = config;
// 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) || gt->IsArchivedAIXSharedLibrary()) {
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 (!gt->IsApple() && !tgtNames.ImportLibrary.empty()) {
std::string const 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.
cmGeneratorTarget::NameComponents const& components =
gt->GetFullNameComponents(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"] = components.base + components.suffix + dbg_suffix;
}
std::string const 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(), '\\', '/');
}
std::vector<cmCustomCommand> const* 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()) {
std::string const 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() &&
!gt->IsArchivedAIXSharedLibrary());
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 our target is not producing a runtime binary, it doesn't need the
// symlinks (anything that links to the target might, but that consumer will
// get its own order-only dependency).
if (!gt->IsDLLPlatform() && gt->IsRuntimeBinary()) {
if (cmComputeLinkInformation* cli = gt->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);
}
}
}
}
}
// Add dependencies on swiftmodule files when using the swift linker
if (this->TargetLinkLanguage(config) == "Swift") {
if (cmComputeLinkInformation* cli =
this->GeneratorTarget->GetLinkInformation(config)) {
for (auto const& dependency : cli->GetItems()) {
// Both the current target and the linked target must be swift targets
// in order for there to be a swiftmodule to depend on
if (dependency.Target &&
dependency.Target->GetLinkerLanguage(config) == "Swift") {
std::string swiftmodule = this->ConvertToNinjaPath(
dependency.Target->GetSwiftModulePath(config));
linkBuild.ImplicitDeps.emplace_back(swiftmodule);
}
}
}
}
// 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);
if (this->GetGeneratorTarget()->IsApple() &&
this->GetGeneratorTarget()->HasImportLibrary(config)) {
auto dirTBD =
gt->GetDirectory(config, cmStateEnums::ImportLibraryArtifact);
auto targetTBD =
this->ConvertToNinjaPath(cmStrCat(dirTBD, '/', tgtNames.ImportReal));
this->EnsureParentDirectoryExists(targetTBD);
cmNinjaBuild build(this->TextStubsGeneratorRule(config));
build.Comment = cmStrCat("Generate the text-based stubs file ", targetTBD);
build.Outputs.push_back(targetTBD);
build.ExplicitDeps.push_back(targetOutputReal);
globalGen->WriteBuild(this->GetImplFileStream(fileConfig), build);
if (tgtNames.ImportOutput != tgtNames.ImportReal &&
!this->GetGeneratorTarget()->IsFrameworkOnApple()) {
auto outputTBD =
this->ConvertToNinjaPath(cmStrCat(dirTBD, '/', tgtNames.ImportOutput));
std::string const soNameTBD = this->ConvertToNinjaPath(
cmStrCat(dirTBD, '/', tgtNames.ImportLibrary));
cmNinjaBuild slBuild("CMAKE_SYMLINK_IMPORT_LIBRARY");
slBuild.Comment = cmStrCat("Create import library symlink ", outputTBD);
cmNinjaVars slVars;
// If one link has to be created.
if (targetTBD == soNameTBD || outputTBD == soNameTBD) {
slVars["SONAME"] = this->GetLocalGenerator()->ConvertToOutputFormat(
soNameTBD, cmOutputConverter::SHELL);
} else {
slVars["SONAME"].clear();
slBuild.Outputs.push_back(soNameTBD);
if (firstForConfig) {
globalGen->GetByproductsForCleanTarget(config).push_back(soNameTBD);
}
}
slBuild.Outputs.push_back(outputTBD);
if (firstForConfig) {
globalGen->GetByproductsForCleanTarget(config).push_back(outputTBD);
}
slBuild.ExplicitDeps.push_back(targetTBD);
slBuild.Variables = std::move(slVars);
globalGen->WriteBuild(this->GetImplFileStream(fileConfig), slBuild);
}
// Add alias for the import file name
globalGen->AddTargetAlias(tgtNames.ImportOutput, gt, config);
}
}