e15a7075b5
A common idiom in CMake-based build systems is to have custom commands that generate files not listed explicitly as outputs so that these files do not have to be newer than the inputs. The file modification times of such "byproducts" are updated only when their content changes. Then other build rules can depend on the byproducts explicitly so that their dependents rebuild when the content of the original byproducts really does change. This "undeclared byproduct" approach is necessary for Makefile, VS, and Xcode build tools because if a byproduct were listed as an output of a rule then the rule would always rerun when the input is newer than the byproduct but the byproduct may never be updated. Ninja solves this problem by offering a 'restat' feature to check whether an output was really modified after running a rule and tracking the fact that it is up to date separately from its timestamp. However, Ninja also stats all dependencies up front and will only restat files that are listed as outputs of rules with the 'restat' option enabled. Therefore an undeclared byproduct that does not exist at the start of the build will be considered missing and the build will fail even if other dependencies would cause the byproduct to be available before its dependents build. CMake works around this limitation by adding 'phony' build rules for custom command dependencies in the build tree that do not have any explicit specification of what produces them. This is not optimal because it prevents Ninja from reporting an error when an input to a rule really is missing. A better approach is to allow projects to explicitly specify the byproducts of their custom commands so that no phony rules are needed for them. In order to work with the non-Ninja generators, the byproducts must be known separately from the outputs. Add a new "BYPRODUCTS" option to the add_custom_command and add_custom_target commands to specify byproducts explicitly. Teach the Ninja generator to specify byproducts as outputs of the custom commands. In the case of POST_BUILD, PRE_LINK, and PRE_BUILD events on targets that link, the byproducts must be specified as outputs of the link rule that runs the commands. Activate 'restat' for such rules so that Ninja knows it needs to check the byproducts, but not for link rules that have no byproducts.
702 lines
24 KiB
C++
702 lines
24 KiB
C++
/*============================================================================
|
|
CMake - Cross Platform Makefile Generator
|
|
Copyright 2011 Peter Collingbourne <peter@pcc.me.uk>
|
|
Copyright 2011 Nicolas Despres <nicolas.despres@gmail.com>
|
|
|
|
Distributed under the OSI-approved BSD License (the "License");
|
|
see accompanying file Copyright.txt for details.
|
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
See the License for more information.
|
|
============================================================================*/
|
|
#include "cmNinjaNormalTargetGenerator.h"
|
|
#include "cmLocalNinjaGenerator.h"
|
|
#include "cmGlobalNinjaGenerator.h"
|
|
#include "cmSourceFile.h"
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmOSXBundleGenerator.h"
|
|
#include "cmGeneratorTarget.h"
|
|
#include "cmCustomCommandGenerator.h"
|
|
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
cmNinjaNormalTargetGenerator::
|
|
cmNinjaNormalTargetGenerator(cmGeneratorTarget* target)
|
|
: cmNinjaTargetGenerator(target->Target)
|
|
, TargetNameOut()
|
|
, TargetNameSO()
|
|
, TargetNameReal()
|
|
, TargetNameImport()
|
|
, TargetNamePDB()
|
|
, TargetLinkLanguage("")
|
|
{
|
|
this->TargetLinkLanguage = target->Target
|
|
->GetLinkerLanguage(this->GetConfigName());
|
|
if (target->GetType() == cmTarget::EXECUTABLE)
|
|
target->Target->GetExecutableNames(this->TargetNameOut,
|
|
this->TargetNameReal,
|
|
this->TargetNameImport,
|
|
this->TargetNamePDB,
|
|
GetLocalGenerator()->GetConfigName());
|
|
else
|
|
target->Target->GetLibraryNames(this->TargetNameOut,
|
|
this->TargetNameSO,
|
|
this->TargetNameReal,
|
|
this->TargetNameImport,
|
|
this->TargetNamePDB,
|
|
GetLocalGenerator()->GetConfigName());
|
|
|
|
if(target->GetType() != cmTarget::OBJECT_LIBRARY)
|
|
{
|
|
// on Windows the output dir is already needed at compile time
|
|
// ensure the directory exists (OutDir test)
|
|
EnsureDirectoryExists(target->Target->GetDirectory(this->GetConfigName()));
|
|
}
|
|
|
|
this->OSXBundleGenerator = new cmOSXBundleGenerator(target,
|
|
this->GetConfigName());
|
|
this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
|
|
}
|
|
|
|
cmNinjaNormalTargetGenerator::~cmNinjaNormalTargetGenerator()
|
|
{
|
|
delete this->OSXBundleGenerator;
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::Generate()
|
|
{
|
|
if (this->TargetLinkLanguage.empty()) {
|
|
cmSystemTools::Error("CMake can not determine linker language for "
|
|
"target: ",
|
|
this->GetTarget()->GetName().c_str());
|
|
return;
|
|
}
|
|
|
|
// Write the rules for each language.
|
|
this->WriteLanguagesRules();
|
|
|
|
// Write the build statements
|
|
this->WriteObjectBuildStatements();
|
|
|
|
if(this->GetTarget()->GetType() == cmTarget::OBJECT_LIBRARY)
|
|
{
|
|
this->WriteObjectLibStatement();
|
|
}
|
|
else
|
|
{
|
|
this->WriteLinkRule(false); // write rule without rspfile support
|
|
this->WriteLinkRule(true); // write rule with rspfile support
|
|
this->WriteLinkStatement();
|
|
}
|
|
}
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteLanguagesRules()
|
|
{
|
|
#ifdef NINJA_GEN_VERBOSE_FILES
|
|
cmGlobalNinjaGenerator::WriteDivider(this->GetRulesFileStream());
|
|
this->GetRulesFileStream()
|
|
<< "# Rules for each languages for "
|
|
<< cmTarget::GetTargetTypeName(this->GetTarget()->GetType())
|
|
<< " target "
|
|
<< this->GetTargetName()
|
|
<< "\n\n";
|
|
#endif
|
|
|
|
std::set<std::string> languages;
|
|
this->GetTarget()->GetLanguages(languages,
|
|
this->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE"));
|
|
for(std::set<std::string>::const_iterator l = languages.begin();
|
|
l != languages.end();
|
|
++l)
|
|
this->WriteLanguageRules(*l);
|
|
}
|
|
|
|
const char *cmNinjaNormalTargetGenerator::GetVisibleTypeName() const
|
|
{
|
|
switch (this->GetTarget()->GetType()) {
|
|
case cmTarget::STATIC_LIBRARY:
|
|
return "static library";
|
|
case cmTarget::SHARED_LIBRARY:
|
|
return "shared library";
|
|
case cmTarget::MODULE_LIBRARY:
|
|
if (this->GetTarget()->IsCFBundleOnApple())
|
|
return "CFBundle shared module";
|
|
else
|
|
return "shared module";
|
|
case cmTarget::EXECUTABLE:
|
|
return "executable";
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
cmNinjaNormalTargetGenerator
|
|
::LanguageLinkerRule() const
|
|
{
|
|
return this->TargetLinkLanguage
|
|
+ "_"
|
|
+ cmTarget::GetTargetTypeName(this->GetTarget()->GetType())
|
|
+ "_LINKER";
|
|
}
|
|
|
|
void
|
|
cmNinjaNormalTargetGenerator
|
|
::WriteLinkRule(bool useResponseFile)
|
|
{
|
|
cmTarget::TargetType targetType = this->GetTarget()->GetType();
|
|
std::string ruleName = this->LanguageLinkerRule();
|
|
if (useResponseFile)
|
|
ruleName += "_RSP_FILE";
|
|
|
|
// Select whether to use a response file for objects.
|
|
std::string rspfile;
|
|
std::string rspcontent;
|
|
|
|
if (!this->GetGlobalGenerator()->HasRule(ruleName)) {
|
|
cmLocalGenerator::RuleVariables vars;
|
|
vars.RuleLauncher = "RULE_LAUNCH_LINK";
|
|
vars.CMTarget = this->GetTarget();
|
|
vars.Language = this->TargetLinkLanguage.c_str();
|
|
|
|
std::string responseFlag;
|
|
if (!useResponseFile) {
|
|
vars.Objects = "$in";
|
|
vars.LinkLibraries = "$LINK_PATH $LINK_LIBRARIES";
|
|
} else {
|
|
std::string cmakeVarLang = "CMAKE_";
|
|
cmakeVarLang += this->TargetLinkLanguage;
|
|
|
|
// build response file name
|
|
std::string cmakeLinkVar = cmakeVarLang + "_RESPONSE_FILE_LINK_FLAG";
|
|
const char * flag = GetMakefile()->GetDefinition(cmakeLinkVar);
|
|
if(flag) {
|
|
responseFlag = flag;
|
|
} else {
|
|
responseFlag = "@";
|
|
}
|
|
rspfile = "$RSP_FILE";
|
|
responseFlag += rspfile;
|
|
|
|
// build response file content
|
|
rspcontent = "$in_newline $LINK_PATH $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.TargetInstallNameDir = "$INSTALLNAME_DIR";
|
|
vars.TargetPDB = "$TARGET_PDB";
|
|
|
|
// Setup the target version.
|
|
std::string targetVersionMajor;
|
|
std::string targetVersionMinor;
|
|
{
|
|
cmOStringStream majorStream;
|
|
cmOStringStream minorStream;
|
|
int major;
|
|
int minor;
|
|
this->GetTarget()->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";
|
|
|
|
std::string langFlags;
|
|
if (targetType != cmTarget::EXECUTABLE)
|
|
{
|
|
langFlags += "$LANGUAGE_COMPILE_FLAGS $ARCH_FLAGS";
|
|
vars.LanguageCompileFlags = langFlags.c_str();
|
|
}
|
|
|
|
// Rule for linking library/executable.
|
|
std::vector<std::string> linkCmds = this->ComputeLinkCmd();
|
|
for(std::vector<std::string>::iterator i = linkCmds.begin();
|
|
i != linkCmds.end();
|
|
++i)
|
|
{
|
|
this->GetLocalGenerator()->ExpandRuleVariables(*i, vars);
|
|
}
|
|
linkCmds.insert(linkCmds.begin(), "$PRE_LINK");
|
|
linkCmds.push_back("$POST_BUILD");
|
|
std::string linkCmd =
|
|
this->GetLocalGenerator()->BuildCommandLine(linkCmds);
|
|
|
|
// Write the linker rule with response file if needed.
|
|
cmOStringStream comment;
|
|
comment << "Rule for linking " << this->TargetLinkLanguage << " "
|
|
<< this->GetVisibleTypeName() << ".";
|
|
cmOStringStream description;
|
|
description << "Linking " << this->TargetLinkLanguage << " "
|
|
<< this->GetVisibleTypeName() << " $TARGET_FILE";
|
|
this->GetGlobalGenerator()->AddRule(ruleName,
|
|
linkCmd,
|
|
description.str(),
|
|
comment.str(),
|
|
/*depfile*/ "",
|
|
/*deptype*/ "",
|
|
rspfile,
|
|
rspcontent,
|
|
/*restat*/ "$RESTAT",
|
|
/*generator*/ false);
|
|
}
|
|
|
|
if (this->TargetNameOut != this->TargetNameReal &&
|
|
!this->GetTarget()->IsFrameworkOnApple()) {
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
this->GetMakefile()->GetRequiredDefinition("CMAKE_COMMAND"),
|
|
cmLocalGenerator::SHELL);
|
|
if (targetType == cmTarget::EXECUTABLE)
|
|
this->GetGlobalGenerator()->AddRule("CMAKE_SYMLINK_EXECUTABLE",
|
|
cmakeCommand +
|
|
" -E cmake_symlink_executable"
|
|
" $in $out && $POST_BUILD",
|
|
"Creating executable symlink $out",
|
|
"Rule for creating "
|
|
"executable symlink.",
|
|
/*depfile*/ "",
|
|
/*deptype*/ "",
|
|
/*rspfile*/ "",
|
|
/*rspcontent*/ "",
|
|
/*restat*/ "",
|
|
/*generator*/ false);
|
|
else
|
|
this->GetGlobalGenerator()->AddRule("CMAKE_SYMLINK_LIBRARY",
|
|
cmakeCommand +
|
|
" -E cmake_symlink_library"
|
|
" $in $SONAME $out && $POST_BUILD",
|
|
"Creating library symlink $out",
|
|
"Rule for creating "
|
|
"library symlink.",
|
|
/*depfile*/ "",
|
|
/*deptype*/ "",
|
|
/*rspfile*/ "",
|
|
/*rspcontent*/ "",
|
|
/*restat*/ "",
|
|
/*generator*/ false);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string>
|
|
cmNinjaNormalTargetGenerator
|
|
::ComputeLinkCmd()
|
|
{
|
|
std::vector<std::string> linkCmds;
|
|
cmMakefile* mf = this->GetMakefile();
|
|
{
|
|
std::string linkCmdVar = this->GetGeneratorTarget()
|
|
->GetCreateRuleVariable(this->TargetLinkLanguage, this->GetConfigName());
|
|
const char *linkCmd = mf->GetDefinition(linkCmdVar);
|
|
if (linkCmd)
|
|
{
|
|
cmSystemTools::ExpandListArgument(linkCmd, linkCmds);
|
|
return linkCmds;
|
|
}
|
|
}
|
|
switch (this->GetTarget()->GetType()) {
|
|
case cmTarget::STATIC_LIBRARY: {
|
|
// We have archive link commands set. First, delete the existing archive.
|
|
{
|
|
std::string cmakeCommand =
|
|
this->GetLocalGenerator()->ConvertToOutputFormat(
|
|
mf->GetRequiredDefinition("CMAKE_COMMAND"),
|
|
cmLocalGenerator::SHELL);
|
|
linkCmds.push_back(cmakeCommand + " -E remove $TARGET_FILE");
|
|
}
|
|
// TODO: Use ARCHIVE_APPEND for archives over a certain size.
|
|
{
|
|
std::string linkCmdVar = "CMAKE_";
|
|
linkCmdVar += this->TargetLinkLanguage;
|
|
linkCmdVar += "_ARCHIVE_CREATE";
|
|
const char *linkCmd = mf->GetRequiredDefinition(linkCmdVar);
|
|
cmSystemTools::ExpandListArgument(linkCmd, linkCmds);
|
|
}
|
|
{
|
|
std::string linkCmdVar = "CMAKE_";
|
|
linkCmdVar += this->TargetLinkLanguage;
|
|
linkCmdVar += "_ARCHIVE_FINISH";
|
|
const char *linkCmd = mf->GetRequiredDefinition(linkCmdVar);
|
|
cmSystemTools::ExpandListArgument(linkCmd, linkCmds);
|
|
}
|
|
return linkCmds;
|
|
}
|
|
case cmTarget::SHARED_LIBRARY:
|
|
case cmTarget::MODULE_LIBRARY:
|
|
case cmTarget::EXECUTABLE:
|
|
break;
|
|
default:
|
|
assert(0 && "Unexpected target type");
|
|
}
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
|
|
static int calculateCommandLineLengthLimit(int linkRuleLength)
|
|
{
|
|
#ifdef _WIN32
|
|
return 8000 - linkRuleLength;
|
|
#elif defined(__linux) || defined(__APPLE__) || defined(__HAIKU__)
|
|
// for instance ARG_MAX is 2096152 on Ubuntu or 262144 on Mac
|
|
return ((int)sysconf(_SC_ARG_MAX)) - linkRuleLength - 1000;
|
|
#else
|
|
(void)linkRuleLength;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
|
|
void cmNinjaNormalTargetGenerator::WriteLinkStatement()
|
|
{
|
|
cmTarget& target = *this->GetTarget();
|
|
const std::string cfgName = this->GetConfigName();
|
|
std::string targetOutput = ConvertToNinjaPath(
|
|
target.GetFullPath(cfgName));
|
|
std::string targetOutputReal = ConvertToNinjaPath(
|
|
target.GetFullPath(cfgName,
|
|
/*implib=*/false,
|
|
/*realpath=*/true));
|
|
std::string targetOutputImplib = ConvertToNinjaPath(
|
|
target.GetFullPath(cfgName,
|
|
/*implib=*/true));
|
|
|
|
if (target.IsAppBundleOnApple())
|
|
{
|
|
// Create the app bundle
|
|
std::string outpath = target.GetDirectory(cfgName);
|
|
this->OSXBundleGenerator->CreateAppBundle(this->TargetNameOut, outpath);
|
|
|
|
// Calculate the output path
|
|
targetOutput = outpath;
|
|
targetOutput += "/";
|
|
targetOutput += this->TargetNameOut;
|
|
targetOutput = this->ConvertToNinjaPath(targetOutput);
|
|
targetOutputReal = outpath;
|
|
targetOutputReal += "/";
|
|
targetOutputReal += this->TargetNameReal;
|
|
targetOutputReal = this->ConvertToNinjaPath(targetOutputReal);
|
|
}
|
|
else if (target.IsFrameworkOnApple())
|
|
{
|
|
// Create the library framework.
|
|
this->OSXBundleGenerator->CreateFramework(this->TargetNameOut,
|
|
target.GetDirectory(cfgName));
|
|
}
|
|
else if(target.IsCFBundleOnApple())
|
|
{
|
|
// Create the core foundation bundle.
|
|
this->OSXBundleGenerator->CreateCFBundle(this->TargetNameOut,
|
|
target.GetDirectory(cfgName));
|
|
}
|
|
|
|
// Write comments.
|
|
cmGlobalNinjaGenerator::WriteDivider(this->GetBuildFileStream());
|
|
const cmTarget::TargetType targetType = target.GetType();
|
|
this->GetBuildFileStream()
|
|
<< "# Link build statements for "
|
|
<< cmTarget::GetTargetTypeName(targetType)
|
|
<< " target "
|
|
<< this->GetTargetName()
|
|
<< "\n\n";
|
|
|
|
cmNinjaDeps emptyDeps;
|
|
cmNinjaVars vars;
|
|
|
|
// Compute the comment.
|
|
cmOStringStream comment;
|
|
comment <<
|
|
"Link the " << this->GetVisibleTypeName() << " " << targetOutputReal;
|
|
|
|
// Compute outputs.
|
|
cmNinjaDeps outputs;
|
|
outputs.push_back(targetOutputReal);
|
|
|
|
// Compute specific libraries to link with.
|
|
cmNinjaDeps explicitDeps = this->GetObjects();
|
|
cmNinjaDeps implicitDeps = this->ComputeLinkDeps();
|
|
|
|
cmMakefile* mf = this->GetMakefile();
|
|
|
|
std::string frameworkPath;
|
|
std::string linkPath;
|
|
cmGeneratorTarget& genTarget = *this->GetGeneratorTarget();
|
|
|
|
std::string createRule =
|
|
genTarget.GetCreateRuleVariable(this->TargetLinkLanguage,
|
|
this->GetConfigName());
|
|
bool useWatcomQuote = mf->IsOn(createRule+"_USE_WATCOM_QUOTE");
|
|
cmLocalNinjaGenerator& localGen = *this->GetLocalGenerator();
|
|
|
|
vars["TARGET_FILE"] =
|
|
localGen.ConvertToOutputFormat(targetOutputReal, cmLocalGenerator::SHELL);
|
|
|
|
localGen.GetTargetFlags(vars["LINK_LIBRARIES"],
|
|
vars["FLAGS"],
|
|
vars["LINK_FLAGS"],
|
|
frameworkPath,
|
|
linkPath,
|
|
&genTarget,
|
|
useWatcomQuote);
|
|
|
|
this->addPoolNinjaVariable("JOB_POOL_LINK", &target, vars);
|
|
|
|
this->AddModuleDefinitionFlag(vars["LINK_FLAGS"]);
|
|
vars["LINK_FLAGS"] = cmGlobalNinjaGenerator
|
|
::EncodeLiteral(vars["LINK_FLAGS"]);
|
|
|
|
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 == cmTarget::EXECUTABLE)
|
|
{
|
|
std::string t = vars["FLAGS"];
|
|
localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
|
|
vars["FLAGS"] = t;
|
|
}
|
|
else
|
|
{
|
|
std::string t = vars["ARCH_FLAGS"];
|
|
localGen.AddArchitectureFlags(t, &genTarget, TargetLinkLanguage, cfgName);
|
|
vars["ARCH_FLAGS"] = t;
|
|
t = "";
|
|
localGen.AddLanguageFlags(t, TargetLinkLanguage, cfgName);
|
|
vars["LANGUAGE_COMPILE_FLAGS"] = t;
|
|
}
|
|
|
|
if (target.HasSOName(cfgName))
|
|
{
|
|
vars["SONAME_FLAG"] = mf->GetSONameFlag(this->TargetLinkLanguage);
|
|
vars["SONAME"] = this->TargetNameSO;
|
|
if (targetType == cmTarget::SHARED_LIBRARY)
|
|
{
|
|
std::string install_dir = target.GetInstallNameDirForBuildTree(cfgName);
|
|
if (!install_dir.empty())
|
|
{
|
|
vars["INSTALLNAME_DIR"] = localGen.Convert(install_dir,
|
|
cmLocalGenerator::NONE,
|
|
cmLocalGenerator::SHELL,
|
|
false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!this->TargetNameImport.empty())
|
|
{
|
|
const std::string impLibPath = localGen.ConvertToOutputFormat(
|
|
targetOutputImplib,
|
|
cmLocalGenerator::SHELL);
|
|
vars["TARGET_IMPLIB"] = impLibPath;
|
|
EnsureParentDirectoryExists(impLibPath);
|
|
if(target.HasImportLibrary())
|
|
{
|
|
outputs.push_back(targetOutputImplib);
|
|
}
|
|
}
|
|
|
|
if (!this->SetMsvcTargetPdbVariable(vars))
|
|
{
|
|
// 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;
|
|
target.GetFullNameComponents(prefix, base, suffix);
|
|
std::string dbg_suffix = ".dbg";
|
|
// TODO: Where to document?
|
|
if (mf->GetDefinition("CMAKE_DEBUG_SYMBOL_SUFFIX"))
|
|
{
|
|
dbg_suffix = mf->GetDefinition("CMAKE_DEBUG_SYMBOL_SUFFIX");
|
|
}
|
|
vars["TARGET_PDB"] = base + suffix + dbg_suffix;
|
|
}
|
|
|
|
if (mf->IsOn("CMAKE_COMPILER_IS_MINGW"))
|
|
{
|
|
const std::string objPath = GetTarget()->GetSupportDirectory();
|
|
vars["OBJECT_DIR"] = ConvertToNinjaPath(objPath);
|
|
EnsureDirectoryExists(objPath);
|
|
// ar.exe can't handle backslashes in rsp files (implicitly used by gcc)
|
|
std::string& linkLibraries = vars["LINK_LIBRARIES"];
|
|
std::replace(linkLibraries.begin(), linkLibraries.end(), '\\', '/');
|
|
std::string& link_path = vars["LINK_PATH"];
|
|
std::replace(link_path.begin(), link_path.end(), '\\', '/');
|
|
}
|
|
|
|
const std::vector<cmCustomCommand> *cmdLists[3] = {
|
|
&target.GetPreBuildCommands(),
|
|
&target.GetPreLinkCommands(),
|
|
&target.GetPostBuildCommands()
|
|
};
|
|
|
|
std::vector<std::string> preLinkCmdLines, postBuildCmdLines;
|
|
std::vector<std::string> *cmdLineLists[3] = {
|
|
&preLinkCmdLines,
|
|
&preLinkCmdLines,
|
|
&postBuildCmdLines
|
|
};
|
|
|
|
cmNinjaDeps byproducts;
|
|
for (unsigned i = 0; i != 3; ++i)
|
|
{
|
|
for (std::vector<cmCustomCommand>::const_iterator
|
|
ci = cmdLists[i]->begin();
|
|
ci != cmdLists[i]->end(); ++ci)
|
|
{
|
|
cmCustomCommandGenerator ccg(*ci, cfgName, mf);
|
|
localGen.AppendCustomCommandLines(ccg, *cmdLineLists[i]);
|
|
std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
|
|
std::transform(ccByproducts.begin(), ccByproducts.end(),
|
|
std::back_inserter(byproducts), MapToNinjaPath());
|
|
}
|
|
}
|
|
|
|
// If we have any PRE_LINK commands, we need to go back to HOME_OUTPUT for
|
|
// the link commands.
|
|
if (!preLinkCmdLines.empty())
|
|
{
|
|
const std::string homeOutDir = localGen.ConvertToOutputFormat(
|
|
mf->GetHomeOutputDirectory(),
|
|
cmLocalGenerator::SHELL);
|
|
preLinkCmdLines.push_back("cd " + homeOutDir);
|
|
}
|
|
|
|
vars["PRE_LINK"] = localGen.BuildCommandLine(preLinkCmdLines);
|
|
std::string postBuildCmdLine = localGen.BuildCommandLine(postBuildCmdLines);
|
|
|
|
cmNinjaVars symlinkVars;
|
|
if (targetOutput == targetOutputReal)
|
|
{
|
|
vars["POST_BUILD"] = postBuildCmdLine;
|
|
}
|
|
else
|
|
{
|
|
vars["POST_BUILD"] = ":";
|
|
symlinkVars["POST_BUILD"] = postBuildCmdLine;
|
|
}
|
|
|
|
cmGlobalNinjaGenerator& globalGen = *this->GetGlobalGenerator();
|
|
|
|
int commandLineLengthLimit = 1;
|
|
const char* forceRspFile = "CMAKE_NINJA_FORCE_RESPONSE_FILE";
|
|
if (!mf->IsDefinitionSet(forceRspFile) &&
|
|
cmSystemTools::GetEnv(forceRspFile) == 0)
|
|
{
|
|
commandLineLengthLimit = calculateCommandLineLengthLimit(
|
|
globalGen.GetRuleCmdLength(this->LanguageLinkerRule()));
|
|
}
|
|
|
|
const std::string rspfile =
|
|
std::string(cmake::GetCMakeFilesDirectoryPostSlash())
|
|
+ target.GetName() + ".rsp";
|
|
|
|
// Gather order-only dependencies.
|
|
cmNinjaDeps orderOnlyDeps;
|
|
this->GetLocalGenerator()->AppendTargetDepends(this->GetTarget(),
|
|
orderOnlyDeps);
|
|
|
|
// Ninja should restat after linking if and only if there are byproducts.
|
|
vars["RESTAT"] = byproducts.empty()? "" : "1";
|
|
|
|
for (cmNinjaDeps::const_iterator oi = byproducts.begin(),
|
|
oe = byproducts.end();
|
|
oi != oe; ++oi)
|
|
{
|
|
this->GetGlobalGenerator()->SeenCustomCommandOutput(*oi);
|
|
outputs.push_back(*oi);
|
|
}
|
|
|
|
// Write the build statement for this target.
|
|
globalGen.WriteBuild(this->GetBuildFileStream(),
|
|
comment.str(),
|
|
this->LanguageLinkerRule(),
|
|
outputs,
|
|
explicitDeps,
|
|
implicitDeps,
|
|
orderOnlyDeps,
|
|
vars,
|
|
rspfile,
|
|
commandLineLengthLimit);
|
|
|
|
if (targetOutput != targetOutputReal && !target.IsFrameworkOnApple())
|
|
{
|
|
if (targetType == cmTarget::EXECUTABLE)
|
|
{
|
|
globalGen.WriteBuild(this->GetBuildFileStream(),
|
|
"Create executable symlink " + targetOutput,
|
|
"CMAKE_SYMLINK_EXECUTABLE",
|
|
cmNinjaDeps(1, targetOutput),
|
|
cmNinjaDeps(1, targetOutputReal),
|
|
emptyDeps,
|
|
emptyDeps,
|
|
symlinkVars);
|
|
}
|
|
else
|
|
{
|
|
cmNinjaDeps symlinks;
|
|
const std::string soName = this->GetTargetFilePath(this->TargetNameSO);
|
|
// If one link has to be created.
|
|
if (targetOutputReal == soName || targetOutput == soName)
|
|
{
|
|
symlinkVars["SONAME"] = soName;
|
|
}
|
|
else
|
|
{
|
|
symlinkVars["SONAME"] = "";
|
|
symlinks.push_back(soName);
|
|
}
|
|
symlinks.push_back(targetOutput);
|
|
globalGen.WriteBuild(this->GetBuildFileStream(),
|
|
"Create library symlink " + targetOutput,
|
|
"CMAKE_SYMLINK_LIBRARY",
|
|
symlinks,
|
|
cmNinjaDeps(1, targetOutputReal),
|
|
emptyDeps,
|
|
emptyDeps,
|
|
symlinkVars);
|
|
}
|
|
}
|
|
|
|
// Add aliases for the file name and the target name.
|
|
globalGen.AddTargetAlias(this->TargetNameOut, &target);
|
|
globalGen.AddTargetAlias(this->GetTargetName(), &target);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void cmNinjaNormalTargetGenerator::WriteObjectLibStatement()
|
|
{
|
|
// Write a phony output that depends on all object files.
|
|
cmNinjaDeps outputs;
|
|
this->GetLocalGenerator()->AppendTargetOutputs(this->GetTarget(), outputs);
|
|
cmNinjaDeps depends = this->GetObjects();
|
|
this->GetGlobalGenerator()->WritePhonyBuild(this->GetBuildFileStream(),
|
|
"Object library "
|
|
+ this->GetTargetName(),
|
|
outputs,
|
|
depends);
|
|
|
|
// Add aliases for the target name.
|
|
this->GetGlobalGenerator()->AddTargetAlias(this->GetTargetName(),
|
|
this->GetTarget());
|
|
}
|