OS X: Add RPATH support for Mac.

RPATH support is activated on targets that have the MACOSX_RPATH
property turned on.
For install time, it is also useful to set INSTALL_RPATH to help
find dependent libraries with an @rpath in their install name.

Also adding detection of rpath conflicts when using frameworks.
This commit is contained in:
Clinton Stimpson 2013-04-26 22:04:44 -06:00 committed by Brad King
parent cbe3f2072b
commit 94e7fef226
8 changed files with 289 additions and 32 deletions

View File

@ -30,6 +30,11 @@ set(CMAKE_SHARED_MODULE_SUFFIX ".so")
set(CMAKE_MODULE_EXISTS 1) set(CMAKE_MODULE_EXISTS 1)
set(CMAKE_DL_LIBS "") set(CMAKE_DL_LIBS "")
# Enable rpath support for 10.5 and greater where it is known to work.
if("${DARWIN_MAJOR_VERSION}" GREATER 8)
set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-rpath,")
endif()
set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ")
set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")

View File

@ -1724,6 +1724,17 @@ void
cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath, cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath,
cmTarget* target) cmTarget* target)
{ {
// Ignore targets on Apple where install_name is not @rpath.
// The dependenty library can be found with other means such as
// @loader_path or full paths.
if(this->Makefile->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
{
if(!target->HasMacOSXRpath(this->Config))
{
return;
}
}
// Libraries with unknown type must be handled using just the file // Libraries with unknown type must be handled using just the file
// on disk. // on disk.
if(target->GetType() == cmTarget::UNKNOWN_LIBRARY) if(target->GetType() == cmTarget::UNKNOWN_LIBRARY)
@ -1756,25 +1767,60 @@ void
cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath) cmComputeLinkInformation::AddLibraryRuntimeInfo(std::string const& fullPath)
{ {
// Get the name of the library from the file name. // Get the name of the library from the file name.
bool is_shared_library = false;
std::string file = cmSystemTools::GetFilenameName(fullPath); std::string file = cmSystemTools::GetFilenameName(fullPath);
if(!this->ExtractSharedLibraryName.find(file.c_str()))
if(this->Makefile->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
{
// Check that @rpath is part of the install name.
// If it isn't, return.
std::string soname;
if(!cmSystemTools::GuessLibraryInstallName(fullPath, soname))
{
return;
}
if(soname.find("@rpath") == std::string::npos)
{
return;
}
}
is_shared_library = this->ExtractSharedLibraryName.find(file.c_str());
if(!is_shared_library)
{ {
// On some platforms (AIX) a shared library may look static. // On some platforms (AIX) a shared library may look static.
if(this->ArchivesMayBeShared) if(this->ArchivesMayBeShared)
{ {
if(!this->ExtractStaticLibraryName.find(file.c_str())) if(this->ExtractStaticLibraryName.find(file.c_str()))
{ {
// This is not the name of a shared library or archive. // This is the name of a shared library or archive.
return; is_shared_library = true;
} }
} }
else }
// It could be an Apple framework
if(!is_shared_library)
{
if(fullPath.find(".framework") != std::string::npos)
{ {
// This is not the name of a shared library. cmsys::RegularExpression splitFramework;
return; splitFramework.compile("^(.*)/(.*).framework/.*/(.*)$");
if(splitFramework.find(fullPath) &&
(splitFramework.match(2) == splitFramework.match(3)))
{
is_shared_library = true;
}
} }
} }
if(!is_shared_library)
{
return;
}
// Include this library in the runtime path ordering. // Include this library in the runtime path ordering.
this->OrderRuntimeSearchPath->AddRuntimeLibrary(fullPath); this->OrderRuntimeSearchPath->AddRuntimeLibrary(fullPath);
if(this->LinkWithRuntimePath) if(this->LinkWithRuntimePath)

View File

@ -606,6 +606,12 @@ cmInstallTargetGenerator
return; return;
} }
// Skip if on Apple
if(this->Target->GetMakefile()->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
{
return;
}
// Get the link information for this target. // Get the link information for this target.
// It can provide the RPATH. // It can provide the RPATH.
cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config); cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config);
@ -645,30 +651,62 @@ cmInstallTargetGenerator
return; return;
} }
// Construct the original rpath string to be replaced. if(this->Target->GetMakefile()->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
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; // If using install_name_tool, set up the rules to modify the rpaths.
} std::string installNameTool =
this->Target->GetMakefile()->
GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL");
// Write a rule to run chrpath to set the install-tree RPATH std::vector<std::string> oldRuntimeDirs, newRuntimeDirs;
if(newRpath.empty()) cli->GetRPath(oldRuntimeDirs, false);
{ cli->GetRPath(newRuntimeDirs, true);
os << indent << "FILE(RPATH_REMOVE\n"
<< indent << " FILE \"" << toDestDirPath << "\")\n"; // Note: These are separate commands to avoid install_name_tool
// corruption on 10.6.
for(std::vector<std::string>::const_iterator i = oldRuntimeDirs.begin();
i != oldRuntimeDirs.end(); ++i)
{
os << indent << "execute_process(COMMAND " << installNameTool << "\n";
os << indent << " -delete_rpath \"" << *i << "\"\n";
os << indent << " \"" << toDestDirPath << "\")\n";
}
for(std::vector<std::string>::const_iterator i = newRuntimeDirs.begin();
i != newRuntimeDirs.end(); ++i)
{
os << indent << "execute_process(COMMAND " << installNameTool << "\n";
os << indent << " -add_rpath \"" << *i << "\"\n";
os << indent << " \"" << toDestDirPath << "\")\n";
}
} }
else else
{ {
os << indent << "FILE(RPATH_CHANGE\n" // Construct the original rpath string to be replaced.
<< indent << " FILE \"" << toDestDirPath << "\"\n" std::string oldRpath = cli->GetRPathString(false);
<< indent << " OLD_RPATH \"" << oldRpath << "\"\n"
<< indent << " NEW_RPATH \"" << newRpath << "\")\n"; // 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";
}
} }
} }

View File

@ -36,8 +36,25 @@ public:
OD(od), GlobalGenerator(od->GlobalGenerator) OD(od), GlobalGenerator(od->GlobalGenerator)
{ {
this->FullPath = file; this->FullPath = file;
this->Directory = cmSystemTools::GetFilenamePath(file);
this->FileName = cmSystemTools::GetFilenameName(file); if(file.rfind(".framework") != std::string::npos)
{
cmsys::RegularExpression splitFramework;
splitFramework.compile("^(.*)/(.*).framework/.*/(.*)$");
if(splitFramework.find(file) &&
(splitFramework.match(2) == splitFramework.match(3)))
{
this->Directory = splitFramework.match(1);
this->FileName =
std::string(file.begin() + this->Directory.size() + 1, file.end());
}
}
if(this->FileName.empty())
{
this->Directory = cmSystemTools::GetFilenamePath(file);
this->FileName = cmSystemTools::GetFilenameName(file);
}
} }
virtual ~cmOrderDirectoriesConstraint() {} virtual ~cmOrderDirectoriesConstraint() {}
@ -301,22 +318,42 @@ void cmOrderDirectories::AddRuntimeLibrary(std::string const& fullPath,
// Add the runtime library at most once. // Add the runtime library at most once.
if(this->EmmittedConstraintSOName.insert(fullPath).second) if(this->EmmittedConstraintSOName.insert(fullPath).second)
{ {
std::string soname2 = soname ? soname : "";
// Implicit link directories need special handling. // Implicit link directories need special handling.
if(!this->ImplicitDirectories.empty()) if(!this->ImplicitDirectories.empty())
{ {
std::string dir = cmSystemTools::GetFilenamePath(fullPath); std::string dir = cmSystemTools::GetFilenamePath(fullPath);
if(fullPath.rfind(".framework") != std::string::npos)
{
cmsys::RegularExpression splitFramework;
splitFramework.compile("^(.*)/(.*).framework/(.*)/(.*)$");
if(splitFramework.find(fullPath) &&
(splitFramework.match(2) == splitFramework.match(4)))
{
dir = splitFramework.match(1);
soname2 = splitFramework.match(2);
soname2 += ".framework/";
soname2 += splitFramework.match(3);
soname2 += "/";
soname2 += splitFramework.match(4);
}
}
if(this->ImplicitDirectories.find(dir) != if(this->ImplicitDirectories.find(dir) !=
this->ImplicitDirectories.end()) this->ImplicitDirectories.end())
{ {
this->ImplicitDirEntries.push_back( this->ImplicitDirEntries.push_back(
new cmOrderDirectoriesConstraintSOName(this, fullPath, soname)); new cmOrderDirectoriesConstraintSOName(this, fullPath,
soname2.c_str()));
return; return;
} }
} }
// Construct the runtime information entry for this library. // Construct the runtime information entry for this library.
this->ConstraintEntries.push_back( this->ConstraintEntries.push_back(
new cmOrderDirectoriesConstraintSOName(this, fullPath, soname)); new cmOrderDirectoriesConstraintSOName(this, fullPath,
soname2.c_str()));
} }
else else
{ {

View File

@ -2412,6 +2412,27 @@ bool cmSystemTools::GuessLibrarySOName(std::string const& fullPath,
return false; return false;
} }
//----------------------------------------------------------------------------
bool cmSystemTools::GuessLibraryInstallName(std::string const& fullPath,
std::string& soname)
{
std::vector<cmStdString> cmds;
cmds.push_back("otool");
cmds.push_back("-D");
cmds.push_back(fullPath.c_str());
std::string output;
RunSingleCommand(cmds, &output, 0, 0, OUTPUT_NONE);
std::vector<std::string> strs = cmSystemTools::tokenize(output, "\n");
if(strs.size() == 2)
{
soname = strs[1];
return true;
}
return false;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
#if defined(CMAKE_USE_ELF_PARSER) #if defined(CMAKE_USE_ELF_PARSER)
std::string::size_type cmSystemToolsFindRPath(std::string const& have, std::string::size_type cmSystemToolsFindRPath(std::string const& have,

View File

@ -439,6 +439,10 @@ public:
static bool GuessLibrarySOName(std::string const& fullPath, static bool GuessLibrarySOName(std::string const& fullPath,
std::string& soname); std::string& soname);
/** Try to guess the install name of a shared library. */
static bool GuessLibraryInstallName(std::string const& fullPath,
std::string& soname);
/** Try to set the RPATH in an ELF binary. */ /** Try to set the RPATH in an ELF binary. */
static bool ChangeRPath(std::string const& file, static bool ChangeRPath(std::string const& file,
std::string const& oldRPath, std::string const& oldRPath,

View File

@ -1160,6 +1160,15 @@ void cmTarget::DefineProperties(cmake *cm)
"If a custom Info.plist is specified by this property it may of course " "If a custom Info.plist is specified by this property it may of course "
"hard-code all the settings instead of using the target properties."); "hard-code all the settings instead of using the target properties.");
cm->DefineProperty
("MACOSX_RPATH", cmProperty::TARGET,
"Whether to use rpaths on Mac OS X.",
"When this property is set to true, the directory portion of the"
"\"install_name\" field of shared libraries will default to \"@rpath\"."
"Runtime paths will also be embedded in binaries using this target."
"This property is initialized by the value of the variable "
"CMAKE_MACOSX_RPATH if it is set when a target is created.");
cm->DefineProperty cm->DefineProperty
("ENABLE_EXPORTS", cmProperty::TARGET, ("ENABLE_EXPORTS", cmProperty::TARGET,
"Specify whether an executable exports symbols for loadable modules.", "Specify whether an executable exports symbols for loadable modules.",
@ -1488,6 +1497,8 @@ void cmTarget::SetMakefile(cmMakefile* mf)
this->SetPropertyDefault("LINK_INTERFACE_LIBRARIES", 0); this->SetPropertyDefault("LINK_INTERFACE_LIBRARIES", 0);
this->SetPropertyDefault("WIN32_EXECUTABLE", 0); this->SetPropertyDefault("WIN32_EXECUTABLE", 0);
this->SetPropertyDefault("MACOSX_BUNDLE", 0); this->SetPropertyDefault("MACOSX_BUNDLE", 0);
this->SetPropertyDefault("MACOSX_RPATH", 0);
// Collect the set of configuration types. // Collect the set of configuration types.
std::vector<std::string> configNames; std::vector<std::string> configNames;
@ -3792,6 +3803,75 @@ std::string cmTarget::GetSOName(const char* config)
} }
} }
//----------------------------------------------------------------------------
bool cmTarget::HasMacOSXRpath(const char* config)
{
bool install_name_is_rpath = false;
bool macosx_rpath = this->GetPropertyAsBool("MACOSX_RPATH");
if(!this->IsImportedTarget)
{
const char* install_name = this->GetProperty("INSTALL_NAME_DIR");
bool use_install_name =
this->GetPropertyAsBool("BUILD_WITH_INSTALL_RPATH");
if(install_name && use_install_name &&
std::string(install_name) == "@rpath")
{
install_name_is_rpath = true;
}
}
else
{
// Lookup the imported soname.
if(cmTarget::ImportInfo const* info = this->GetImportInfo(config, this))
{
if(!info->NoSOName && !info->SOName.empty())
{
if(info->SOName.find("@rpath/") == 0)
{
install_name_is_rpath = true;
}
}
else
{
std::string install_name;
cmSystemTools::GuessLibraryInstallName(info->Location, install_name);
if(install_name.find("@rpath") != std::string::npos)
{
install_name_is_rpath = true;
}
}
}
}
if(!install_name_is_rpath && !macosx_rpath)
{
return false;
}
if(!this->Makefile->IsSet("CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG"))
{
cmOStringStream w;
w << "Attempting to use";
if(macosx_rpath)
{
w << " MACOSX_RPATH";
}
else
{
w << " @rpath";
}
w << " without CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG being set.";
w << " This could be because you are using a Mac OS X version";
w << " less than 10.5 or because CMake's platform configuration is";
w << " corrupt.";
cmake* cm = this->Makefile->GetCMakeInstance();
cm->IssueMessage(cmake::FATAL_ERROR, w.str(), this->GetBacktrace());
}
return true;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool cmTarget::IsImportedSharedLibWithoutSOName(const char* config) bool cmTarget::IsImportedSharedLibWithoutSOName(const char* config)
{ {
@ -4432,7 +4512,15 @@ std::string cmTarget::GetInstallNameDirForBuildTree(const char* config)
!this->Makefile->IsOn("CMAKE_SKIP_RPATH") && !this->Makefile->IsOn("CMAKE_SKIP_RPATH") &&
!this->GetPropertyAsBool("SKIP_BUILD_RPATH")) !this->GetPropertyAsBool("SKIP_BUILD_RPATH"))
{ {
std::string dir = this->GetDirectory(config); std::string dir;
if(this->GetPropertyAsBool("MACOSX_RPATH"))
{
dir = "@rpath";
}
else
{
dir = this->GetDirectory(config);
}
dir += "/"; dir += "/";
return dir; return dir;
} }
@ -4459,6 +4547,10 @@ std::string cmTarget::GetInstallNameDirForInstallTree()
dir += "/"; dir += "/";
} }
} }
if(dir.empty() && this->GetPropertyAsBool("MACOSX_RPATH"))
{
dir = "@rpath/";
}
return dir; return dir;
} }
else else
@ -5040,7 +5132,6 @@ void cmTarget::GetLanguages(std::set<cmStdString>& languages) const
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
bool cmTarget::IsChrpathUsed(const char* config) bool cmTarget::IsChrpathUsed(const char* config)
{ {
#if defined(CMAKE_USE_ELF_PARSER)
// Only certain target types have an rpath. // Only certain target types have an rpath.
if(!(this->GetType() == cmTarget::SHARED_LIBRARY || if(!(this->GetType() == cmTarget::SHARED_LIBRARY ||
this->GetType() == cmTarget::MODULE_LIBRARY || this->GetType() == cmTarget::MODULE_LIBRARY ||
@ -5074,6 +5165,12 @@ bool cmTarget::IsChrpathUsed(const char* config)
return false; return false;
} }
if(this->Makefile->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
{
return true;
}
#if defined(CMAKE_USE_ELF_PARSER)
// Enable if the rpath flag uses a separator and the target uses ELF // Enable if the rpath flag uses a separator and the target uses ELF
// binaries. // binaries.
if(const char* ll = this->GetLinkerLanguage(config, this)) if(const char* ll = this->GetLinkerLanguage(config, this))

View File

@ -362,6 +362,9 @@ public:
/** Get the soname of the target. Allowed only for a shared library. */ /** Get the soname of the target. Allowed only for a shared library. */
std::string GetSOName(const char* config); std::string GetSOName(const char* config);
/** Whether this library has @rpath and platform supports it. */
bool HasMacOSXRpath(const char* config);
/** Test for special case of a third-party shared library that has /** Test for special case of a third-party shared library that has
no soname at all. */ no soname at all. */
bool IsImportedSharedLibWithoutSOName(const char* config); bool IsImportedSharedLibWithoutSOName(const char* config);
@ -407,7 +410,13 @@ public:
/** Return true if builtin chrpath will work for this target */ /** Return true if builtin chrpath will work for this target */
bool IsChrpathUsed(const char* config); bool IsChrpathUsed(const char* config);
/** Return the install name directory for the target in the
* build tree. For example: "@rpath/", "@loader_path/",
* or "/full/path/to/library". */
std::string GetInstallNameDirForBuildTree(const char* config); std::string GetInstallNameDirForBuildTree(const char* config);
/** Return the install name directory for the target in the
* install tree. For example: "@rpath/" or "@loader_path/". */
std::string GetInstallNameDirForInstallTree(); std::string GetInstallNameDirForInstallTree();
cmComputeLinkInformation* GetLinkInformation(const char* config, cmComputeLinkInformation* GetLinkInformation(const char* config,