CMake/Source/cmInstallTargetGenerator.cxx

717 lines
22 KiB
C++

/*=========================================================================
Program: CMake - Cross-Platform Makefile Generator
Module: $RCSfile$
Language: C++
Date: $Date$
Version: $Revision$
Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "cmInstallTargetGenerator.h"
#include "cmComputeLinkInformation.h"
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmake.h"
#include <assert.h>
//----------------------------------------------------------------------------
cmInstallTargetGenerator
::cmInstallTargetGenerator(cmTarget& t, const char* dest, bool implib,
const char* file_permissions,
std::vector<std::string> const& configurations,
const char* component, bool optional):
cmInstallGenerator(dest, configurations, component), Target(&t),
ImportLibrary(implib), FilePermissions(file_permissions), Optional(optional)
{
this->NamelinkMode = NamelinkModeNone;
this->Target->SetHaveInstallRule(true);
}
//----------------------------------------------------------------------------
cmInstallTargetGenerator
::~cmInstallTargetGenerator()
{
}
//----------------------------------------------------------------------------
void cmInstallTargetGenerator::GenerateScript(std::ostream& os)
{
// Warn if installing an exclude-from-all target.
if(this->Target->GetPropertyAsBool("EXCLUDE_FROM_ALL"))
{
cmOStringStream msg;
msg << "WARNING: Target \"" << this->Target->GetName()
<< "\" has EXCLUDE_FROM_ALL set and will not be built by default "
<< "but an install rule has been provided for it. CMake does "
<< "not define behavior for this case.";
cmSystemTools::Message(msg.str().c_str(), "Warning");
}
// Track indentation.
Indent indent;
// Begin this block of installation.
std::string component_test =
this->CreateComponentTest(this->Component.c_str());
os << indent << "IF(" << component_test << ")\n";
// Compute the build tree directory from which to copy the target.
std::string fromDir;
if(this->Target->NeedRelinkBeforeInstall())
{
fromDir = this->Target->GetMakefile()->GetStartOutputDirectory();
fromDir += cmake::GetCMakeFilesDirectory();
fromDir += "/CMakeRelink.dir/";
}
else
{
fromDir = this->Target->GetDirectory(0, this->ImportLibrary);
fromDir += "/";
}
// Generate a portion of the script for each configuration.
if(this->ConfigurationTypes->empty())
{
this->GenerateScriptForConfig(os, fromDir.c_str(),
this->ConfigurationName,
indent.Next());
}
else
{
for(std::vector<std::string>::const_iterator i =
this->ConfigurationTypes->begin();
i != this->ConfigurationTypes->end(); ++i)
{
this->GenerateScriptForConfig(os, fromDir.c_str(), i->c_str(),
indent.Next());
}
}
// End this block of installation.
os << indent << "ENDIF(" << component_test << ")\n\n";
}
//----------------------------------------------------------------------------
void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os,
const char* fromDir,
const char* config,
Indent const& indent)
{
// Compute the per-configuration directory containing the files.
std::string fromDirConfig = fromDir;
this->Target->GetMakefile()->GetLocalGenerator()->GetGlobalGenerator()
->AppendDirectoryForConfig("", config, "/", fromDirConfig);
if(config && *config)
{
// Skip this configuration for config-specific installation that
// does not match it.
if(!this->InstallsForConfig(config))
{
return;
}
// Generate a per-configuration block.
std::string config_test = this->CreateConfigTest(config);
os << indent << "IF(" << config_test << ")\n";
this->GenerateScriptForConfigDir(os, fromDirConfig.c_str(), config,
indent.Next());
os << indent << "ENDIF(" << config_test << ")\n";
}
else
{
this->GenerateScriptForConfigDir(os, fromDirConfig.c_str(), config,
indent);
}
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::GenerateScriptForConfigDir(std::ostream& os,
const char* fromDirConfig,
const char* config,
Indent const& indent)
{
// Compute the full path to the main installed file for this target.
NameType nameType = this->ImportLibrary? NameImplib : NameNormal;
std::string toInstallPath = this->GetInstallDestination();
toInstallPath += "/";
toInstallPath += this->GetInstallFilename(this->Target, config, nameType);
// Track whether post-install operations should be added to the
// script.
bool tweakInstalledFile = true;
// Compute the list of files to install for this target.
std::vector<std::string> files;
std::string literal_args;
cmTarget::TargetType type = this->Target->GetType();
if(type == cmTarget::EXECUTABLE)
{
// There is a bug in cmInstallCommand if this fails.
assert(this->NamelinkMode == NamelinkModeNone);
std::string targetName;
std::string targetNameReal;
std::string targetNameImport;
std::string targetNamePDB;
this->Target->GetExecutableNames(targetName, targetNameReal,
targetNameImport, targetNamePDB,
config);
if(this->ImportLibrary)
{
std::string from1 = fromDirConfig;
from1 += targetNameImport;
files.push_back(from1);
// An import library looks like a static library.
type = cmTarget::STATIC_LIBRARY;
}
else
{
std::string from1 = fromDirConfig;
from1 += targetName;
// Handle OSX Bundles.
if(this->Target->IsAppBundleOnApple())
{
// Compute the source locations of the bundle executable and
// Info.plist file.
from1 += ".app";
files.push_back(from1);
type = cmTarget::INSTALL_DIRECTORY;
// Need to apply install_name_tool and stripping to binary
// inside bundle.
toInstallPath += ".app/Contents/MacOS/";
toInstallPath +=
this->GetInstallFilename(this->Target, config, nameType);
literal_args += " USE_SOURCE_PERMISSIONS";
}
else
{
files.push_back(from1);
if(targetNameReal != targetName)
{
std::string from2 = fromDirConfig;
from2 += targetNameReal;
files.push_back(from2);
}
}
}
}
else
{
std::string targetName;
std::string targetNameSO;
std::string targetNameReal;
std::string targetNameImport;
std::string targetNamePDB;
this->Target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
targetNameImport, targetNamePDB,
config);
if(this->ImportLibrary)
{
// There is a bug in cmInstallCommand if this fails.
assert(this->NamelinkMode == NamelinkModeNone);
std::string from1 = fromDirConfig;
from1 += targetNameImport;
files.push_back(from1);
// An import library looks like a static library.
type = cmTarget::STATIC_LIBRARY;
}
else if(this->Target->IsFrameworkOnApple())
{
// There is a bug in cmInstallCommand if this fails.
assert(this->NamelinkMode == NamelinkModeNone);
// Compute the build tree location of the framework directory
std::string from1 = fromDirConfig;
from1 += targetName;
from1 += ".framework";
files.push_back(from1);
type = cmTarget::INSTALL_DIRECTORY;
// Need to apply install_name_tool and stripping to binary
// inside framework.
toInstallPath += ".framework/Versions/";
toInstallPath += this->Target->GetFrameworkVersion();
toInstallPath += "/";
toInstallPath += this->GetInstallFilename(this->Target, config,
NameNormal);
literal_args += " USE_SOURCE_PERMISSIONS";
}
else
{
// Operations done at install time on the installed file should
// be done on the real file and not any of the symlinks.
toInstallPath = this->GetInstallDestination();
toInstallPath += "/";
toInstallPath += targetNameReal;
// Construct the list of file names to install for this library.
bool haveNamelink = false;
std::string fromName;
std::string fromSOName;
std::string fromRealName;
fromName = fromDirConfig;
fromName += targetName;
if(targetNameSO != targetName)
{
haveNamelink = true;
fromSOName = fromDirConfig;
fromSOName += targetNameSO;
}
if(targetNameReal != targetName &&
targetNameReal != targetNameSO)
{
haveNamelink = true;
fromRealName = fromDirConfig;
fromRealName += targetNameReal;
}
// Add the names based on the current namelink mode.
if(haveNamelink)
{
// With a namelink we need to check the mode.
if(this->NamelinkMode == NamelinkModeOnly)
{
// Install the namelink only.
files.push_back(fromName);
tweakInstalledFile = false;
}
else
{
// Install the real file if it has its own name.
if(!fromRealName.empty())
{
files.push_back(fromRealName);
}
// Install the soname link if it has its own name.
if(!fromSOName.empty())
{
files.push_back(fromSOName);
}
// Install the namelink if it is not to be skipped.
if(this->NamelinkMode != NamelinkModeSkip)
{
files.push_back(fromName);
}
}
}
else
{
// Without a namelink there will be only one file. Install it
// if this is not a namelink-only rule.
if(this->NamelinkMode != NamelinkModeOnly)
{
files.push_back(fromName);
}
}
}
}
// Skip this rule if no files are to be installed for the target.
if(files.empty())
{
return;
}
// Construct the path of the file on disk after installation on
// which tweaks may be performed.
std::string toDestDirPath = "$ENV{DESTDIR}";
if(toInstallPath[0] != '/' && toInstallPath[0] != '$')
{
toDestDirPath += "/";
}
toDestDirPath += toInstallPath;
// Add pre-installation tweaks.
if(tweakInstalledFile)
{
// Collect tweaking rules.
cmOStringStream tw;
this->AddRPathCheckRule(tw, indent.Next(), config, toDestDirPath);
std::string tws = tw.str();
// Add the rules, if any.
if(!tws.empty())
{
os << indent << "IF(EXISTS \"" << toDestDirPath << "\")\n";
os << tws;
os << indent << "ENDIF(EXISTS \"" << toDestDirPath << "\")\n";
}
}
// Write code to install the target file.
const char* no_dir_permissions = 0;
const char* no_rename = 0;
const char* no_properties = 0;
bool optional = this->Optional || this->ImportLibrary;
this->AddInstallRule(os, type, files,
optional, no_properties,
this->FilePermissions.c_str(), no_dir_permissions,
no_rename, literal_args.c_str(),
indent);
// Add post-installation tweaks.
if(tweakInstalledFile)
{
// Collect tweaking rules.
cmOStringStream tw;
this->AddInstallNamePatchRule(tw, indent.Next(), config, toDestDirPath);
this->AddChrpathPatchRule(tw, indent.Next(), config, toDestDirPath);
this->AddRanlibRule(tw, indent.Next(), type, toDestDirPath);
this->AddStripRule(tw, indent.Next(), type, toDestDirPath);
std::string tws = tw.str();
// Add the rules, if any.
if(!tws.empty())
{
os << indent << "IF(EXISTS \"" << toDestDirPath << "\")\n";
os << tws;
os << indent << "ENDIF(EXISTS \"" << toDestDirPath << "\")\n";
}
}
}
//----------------------------------------------------------------------------
std::string
cmInstallTargetGenerator::GetInstallFilename(const char* config) const
{
NameType nameType = this->ImportLibrary? NameImplib : NameNormal;
return
cmInstallTargetGenerator::GetInstallFilename(this->Target, config,
nameType);
}
//----------------------------------------------------------------------------
std::string cmInstallTargetGenerator::GetInstallFilename(cmTarget* target,
const char* config,
NameType nameType)
{
std::string fname;
// Compute the name of the library.
if(target->GetType() == cmTarget::EXECUTABLE)
{
std::string targetName;
std::string targetNameReal;
std::string targetNameImport;
std::string targetNamePDB;
target->GetExecutableNames(targetName, targetNameReal,
targetNameImport, targetNamePDB,
config);
if(nameType == NameImplib)
{
// Use the import library name.
fname = targetNameImport;
}
else if(nameType == NameReal)
{
// Use the canonical name.
fname = targetNameReal;
}
else
{
// Use the canonical name.
fname = targetName;
}
}
else
{
std::string targetName;
std::string targetNameSO;
std::string targetNameReal;
std::string targetNameImport;
std::string targetNamePDB;
target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
targetNameImport, targetNamePDB, config);
if(nameType == NameImplib)
{
// Use the import library name.
fname = targetNameImport;
}
else if(nameType == NameSO)
{
// Use the soname.
fname = targetNameSO;
}
else if(nameType == NameReal)
{
// Use the real name.
fname = targetNameReal;
}
else
{
// Use the canonical name.
fname = targetName;
}
}
return fname;
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::AddInstallNamePatchRule(std::ostream& os, Indent const& indent,
const char* config, std::string const& toDestDirPath)
{
if(this->ImportLibrary ||
!(this->Target->GetType() == cmTarget::SHARED_LIBRARY ||
this->Target->GetType() == cmTarget::MODULE_LIBRARY ||
this->Target->GetType() == cmTarget::EXECUTABLE))
{
return;
}
// Fix the install_name settings in installed binaries.
std::string installNameTool =
this->Target->GetMakefile()->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL");
if(!installNameTool.size())
{
return;
}
// Build a map of build-tree install_name to install-tree install_name for
// shared libraries linked to this target.
std::map<cmStdString, cmStdString> install_name_remap;
if(cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config))
{
std::set<cmTarget*> const& sharedLibs = cli->GetSharedLibrariesLinked();
for(std::set<cmTarget*>::const_iterator j = sharedLibs.begin();
j != sharedLibs.end(); ++j)
{
// If the build tree and install tree use different path
// components of the install_name field then we need to create a
// mapping to be applied after installation.
cmTarget* tgt = *j;
std::string for_build = tgt->GetInstallNameDirForBuildTree(config);
std::string for_install = tgt->GetInstallNameDirForInstallTree(config);
if(for_build != for_install)
{
// The directory portions differ. Append the filename to
// create the mapping.
std::string fname =
this->GetInstallFilename(tgt, config, NameSO);
// Map from the build-tree install_name.
for_build += fname;
// Map to the install-tree install_name.
for_install += fname;
// Store the mapping entry.
install_name_remap[for_build] = for_install;
}
}
}
// Edit the install_name of the target itself if necessary.
std::string new_id;
if(this->Target->GetType() == cmTarget::SHARED_LIBRARY)
{
std::string for_build =
this->Target->GetInstallNameDirForBuildTree(config);
std::string for_install =
this->Target->GetInstallNameDirForInstallTree(config);
if(this->Target->IsFrameworkOnApple() && for_install.empty())
{
// Frameworks seem to have an id corresponding to their own full
// path.
// ...
// for_install = fullDestPath_without_DESTDIR_or_name;
}
// If the install name will change on installation set the new id
// on the installed file.
if(for_build != for_install)
{
// Prepare to refer to the install-tree install_name.
new_id = for_install;
new_id += this->GetInstallFilename(this->Target, config, NameSO);
}
}
// Write a rule to run install_name_tool to set the install-tree
// install_name value and references.
if(!new_id.empty() || !install_name_remap.empty())
{
os << indent << "EXECUTE_PROCESS(COMMAND \"" << installNameTool;
os << "\"";
if(!new_id.empty())
{
os << "\n" << indent << " -id \"" << new_id << "\"";
}
for(std::map<cmStdString, cmStdString>::const_iterator
i = install_name_remap.begin();
i != install_name_remap.end(); ++i)
{
os << "\n" << indent << " -change \""
<< i->first << "\" \"" << i->second << "\"";
}
os << "\n" << indent << " \"" << toDestDirPath << "\")\n";
}
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::AddRPathCheckRule(std::ostream& os, Indent const& indent,
const char* config, std::string const& toDestDirPath)
{
// Skip the chrpath if the target does not need it.
if(this->ImportLibrary || !this->Target->IsChrpathUsed())
{
return;
}
// Get the link information for this target.
// It can provide the RPATH.
cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config);
if(!cli)
{
return;
}
// Get the install RPATH from the link information.
std::string newRpath = cli->GetChrpathString();
// Write a rule to remove the installed file if its rpath is not the
// new rpath. This is needed for existing build/install trees when
// the installed rpath changes but the file is not rebuilt.
os << indent << "FILE(RPATH_CHECK\n"
<< indent << " FILE \"" << toDestDirPath << "\"\n"
<< indent << " RPATH \"" << newRpath << "\")\n";
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::AddChrpathPatchRule(std::ostream& os, Indent const& indent,
const char* config, std::string const& toDestDirPath)
{
// Skip the chrpath if the target does not need it.
if(this->ImportLibrary || !this->Target->IsChrpathUsed())
{
return;
}
// Get the link information for this target.
// It can provide the RPATH.
cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config);
if(!cli)
{
return;
}
// Construct the original rpath string to be replaced.
std::string oldRpath = cli->GetRPathString(false);
// Get the install RPATH from the link information.
std::string newRpath = cli->GetChrpathString();
// Skip the rule if the paths are identical
if(oldRpath == newRpath)
{
return;
}
// Write a rule to run chrpath to set the install-tree RPATH
if(newRpath.empty())
{
os << indent << "FILE(RPATH_REMOVE\n"
<< indent << " FILE \"" << toDestDirPath << "\")\n";
}
else
{
os << indent << "FILE(RPATH_CHANGE\n"
<< indent << " FILE \"" << toDestDirPath << "\"\n"
<< indent << " OLD_RPATH \"" << oldRpath << "\"\n"
<< indent << " NEW_RPATH \"" << newRpath << "\")\n";
}
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator::AddStripRule(std::ostream& os,
Indent const& indent,
cmTarget::TargetType type,
const std::string& toDestDirPath)
{
// don't strip static libraries, because it removes the only symbol table
// they have so you can't link to them anymore
if(type == cmTarget::STATIC_LIBRARY)
{
return;
}
// Don't handle OSX Bundles.
if(this->Target->GetMakefile()->IsOn("APPLE") &&
this->Target->GetPropertyAsBool("MACOSX_BUNDLE"))
{
return;
}
if(! this->Target->GetMakefile()->IsSet("CMAKE_STRIP"))
{
return;
}
os << indent << "IF(CMAKE_INSTALL_DO_STRIP)\n";
os << indent << " EXECUTE_PROCESS(COMMAND \""
<< this->Target->GetMakefile()->GetDefinition("CMAKE_STRIP")
<< "\" \"" << toDestDirPath << "\")\n";
os << indent << "ENDIF(CMAKE_INSTALL_DO_STRIP)\n";
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator::AddRanlibRule(std::ostream& os,
Indent const& indent,
cmTarget::TargetType type,
const std::string& toDestDirPath)
{
// Static libraries need ranlib on this platform.
if(type != cmTarget::STATIC_LIBRARY)
{
return;
}
// Perform post-installation processing on the file depending
// on its type.
if(!this->Target->GetMakefile()->IsOn("APPLE"))
{
return;
}
std::string ranlib =
this->Target->GetMakefile()->GetRequiredDefinition("CMAKE_RANLIB");
if(ranlib.empty())
{
return;
}
os << indent << "EXECUTE_PROCESS(COMMAND \""
<< ranlib << "\" \"" << toDestDirPath << "\")\n";
}