install,export: Maybe transform OBJECT libraries to INTERFACE libraries

Teach the `install` and `export` commands to support installing and
exporting `OBJECT` libraries without their object files.  Transform
them to `INTERFACE` libraries in such cases.

For `install(TARGETS)`, activate this when no destination for the object
files is specified.  For `export`, activate this only under Xcode with
multiple architectures when we have no well-defined object file
locations to give to clients.
This commit is contained in:
Brad King 2018-02-28 10:58:07 -05:00
parent d870148537
commit ea0ce73a19
23 changed files with 123 additions and 63 deletions

View File

@ -40,6 +40,13 @@ policy CMP0022 is NEW. If a library target is included in the export
but a target to which it links is not included the behavior is
unspecified.
.. note::
:ref:`Object Libraries` under :generator:`Xcode` have special handling if
multiple architectures are listed in :variable:`CMAKE_OSX_ARCHITECTURES`.
In this case they will be exported as :ref:`Interface Libraries` with
no object files available to clients.
::
export(PACKAGE <name>)

View File

@ -183,6 +183,11 @@ export called ``<export-name>``. It must appear before any ``RUNTIME``,
``LIBRARY``, ``ARCHIVE``, or ``OBJECTS`` options. To actually install the
export file itself, call ``install(EXPORT)``, documented below.
:ref:`Interface Libraries` may be listed among the targets to install.
They install no artifacts but will be included in an associated ``EXPORT``.
If :ref:`Object Libraries` are listed but given no destination for their
object files, they will be exported as :ref:`Interface Libraries`.
Installing a target with the :prop_tgt:`EXCLUDE_FROM_ALL` target property
set to ``TRUE`` has undefined behavior.

View File

@ -40,7 +40,8 @@ void cmExportBuildAndroidMKGenerator::GenerateExpectedTargetsCode(
}
void cmExportBuildAndroidMKGenerator::GenerateImportTargetCode(
std::ostream& os, const cmGeneratorTarget* target)
std::ostream& os, cmGeneratorTarget const* target,
cmStateEnums::TargetType /*targetType*/)
{
std::string targetName = this->Namespace;
targetName += target->GetExportName();

View File

@ -11,6 +11,7 @@
#include "cmExportBuildFileGenerator.h"
#include "cmExportFileGenerator.h"
#include "cmStateTypes.h"
class cmGeneratorTarget;
@ -47,8 +48,9 @@ protected:
void GenerateImportHeaderCode(std::ostream& os,
const std::string& config = "") override;
void GenerateImportFooterCode(std::ostream& os) override;
void GenerateImportTargetCode(std::ostream& os,
const cmGeneratorTarget* target) override;
void GenerateImportTargetCode(
std::ostream& os, cmGeneratorTarget const* target,
cmStateEnums::TargetType /*targetType*/) override;
void GenerateExpectedTargetsCode(
std::ostream& os, const std::string& expectedTargets) override;
void GenerateImportPropertyCode(

View File

@ -59,7 +59,7 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
this->LG->GetMakefile()->GetBacktrace());
return false;
}
if (te->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
if (this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY) {
this->GenerateRequiredCMakeVersion(os, "3.0.0");
}
}
@ -71,7 +71,7 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
// Create all the imported targets.
for (cmGeneratorTarget* gte : this->Exports) {
this->GenerateImportTargetCode(os, gte);
this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
gte->Target->AppendBuildInterfaceIncludes();
@ -128,12 +128,13 @@ void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
// Collect import properties for this target.
ImportPropertyMap properties;
if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
this->SetImportLocationProperty(config, suffix, target, properties);
}
if (!properties.empty()) {
// Get the rest of the target details.
if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
if (this->GetExportTargetType(target) !=
cmStateEnums::INTERFACE_LIBRARY) {
this->SetImportDetailProperties(config, suffix, target, properties,
missingTargets);
this->SetImportLinkInterface(config, suffix,
@ -153,6 +154,19 @@ void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
}
}
cmStateEnums::TargetType cmExportBuildFileGenerator::GetExportTargetType(
cmGeneratorTarget const* target) const
{
cmStateEnums::TargetType targetType = target->GetType();
// An object library exports as an interface library if we cannot
// tell clients where to find the objects.
if (targetType == cmStateEnums::OBJECT_LIBRARY &&
!this->LG->GetGlobalGenerator()->HasKnownObjectFileLocation(nullptr)) {
targetType = cmStateEnums::INTERFACE_LIBRARY;
}
return targetType;
}
void cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
{
this->ExportSet = exportSet;

View File

@ -6,6 +6,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmExportFileGenerator.h"
#include "cmStateTypes.h"
#include <iosfwd>
#include <string>
@ -53,6 +54,8 @@ protected:
void GenerateImportTargetsConfig(
std::ostream& os, const std::string& config, std::string const& suffix,
std::vector<std::string>& missingTargets) override;
cmStateEnums::TargetType GetExportTargetType(
cmGeneratorTarget const* target) const;
void HandleMissingTarget(std::string& link_libs,
std::vector<std::string>& missingTargets,
cmGeneratorTarget* depender,

View File

@ -146,17 +146,6 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
}
if (cmTarget* target = gg->FindTarget(currentTarget)) {
if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
std::string reason;
if (!this->Makefile->GetGlobalGenerator()
->HasKnownObjectFileLocation(&reason)) {
std::ostringstream e;
e << "given OBJECT library \"" << currentTarget
<< "\" which may not be exported" << reason << ".";
this->SetError(e.str());
return false;
}
}
if (target->GetType() == cmStateEnums::UTILITY) {
this->SetError("given custom target \"" + currentTarget +
"\" which may not be exported.");

View File

@ -901,8 +901,10 @@ void cmExportFileGenerator::GenerateExpectedTargetsCode(
"\n\n";
/* clang-format on */
}
void cmExportFileGenerator::GenerateImportTargetCode(
std::ostream& os, const cmGeneratorTarget* target)
std::ostream& os, cmGeneratorTarget const* target,
cmStateEnums::TargetType targetType)
{
// Construct the imported target name.
std::string targetName = this->Namespace;
@ -911,7 +913,7 @@ void cmExportFileGenerator::GenerateImportTargetCode(
// Create the imported target.
os << "# Create imported target " << targetName << "\n";
switch (target->GetType()) {
switch (targetType) {
case cmStateEnums::EXECUTABLE:
os << "add_executable(" << targetName << " IMPORTED)\n";
break;

View File

@ -6,6 +6,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmGeneratorExpression.h"
#include "cmStateTypes.h"
#include "cmVersion.h"
#include "cmVersionConfig.h"
@ -76,7 +77,8 @@ protected:
virtual void GenerateImportFooterCode(std::ostream& os);
void GenerateImportVersionCode(std::ostream& os);
virtual void GenerateImportTargetCode(std::ostream& os,
cmGeneratorTarget const* target);
cmGeneratorTarget const* target,
cmStateEnums::TargetType targetType);
virtual void GenerateImportPropertyCode(std::ostream& os,
const std::string& config,
cmGeneratorTarget const* target,

View File

@ -55,7 +55,8 @@ void cmExportInstallAndroidMKGenerator::GenerateImportFooterCode(std::ostream&)
}
void cmExportInstallAndroidMKGenerator::GenerateImportTargetCode(
std::ostream& os, const cmGeneratorTarget* target)
std::ostream& os, cmGeneratorTarget const* target,
cmStateEnums::TargetType /*targetType*/)
{
std::string targetName = this->Namespace;
targetName += target->GetExportName();

View File

@ -12,6 +12,7 @@
#include "cmExportFileGenerator.h"
#include "cmExportInstallFileGenerator.h"
#include "cmStateTypes.h"
class cmGeneratorTarget;
class cmInstallExportGenerator;
@ -41,8 +42,9 @@ protected:
void GenerateImportHeaderCode(std::ostream& os,
const std::string& config = "") override;
void GenerateImportFooterCode(std::ostream& os) override;
void GenerateImportTargetCode(std::ostream& os,
const cmGeneratorTarget* target) override;
void GenerateImportTargetCode(
std::ostream& os, cmGeneratorTarget const* target,
cmStateEnums::TargetType /*targetType*/) override;
void GenerateExpectedTargetsCode(
std::ostream& os, const std::string& expectedTargets) override;
void GenerateImportPropertyCode(

View File

@ -75,11 +75,12 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
// Create all the imported targets.
for (cmTargetExport* te : allTargets) {
cmGeneratorTarget* gt = te->Target;
cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
requiresConfigFiles =
requiresConfigFiles || gt->GetType() != cmStateEnums::INTERFACE_LIBRARY;
requiresConfigFiles || targetType != cmStateEnums::INTERFACE_LIBRARY;
this->GenerateImportTargetCode(os, gt);
this->GenerateImportTargetCode(os, gt, targetType);
ImportPropertyMap properties;
@ -114,7 +115,7 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
require2_8_12 = true;
}
}
if (gt->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
if (targetType == cmStateEnums::INTERFACE_LIBRARY) {
require3_0_0 = true;
}
if (gt->GetProperty("INTERFACE_SOURCES")) {
@ -308,7 +309,7 @@ void cmExportInstallFileGenerator::GenerateImportTargetsConfig(
// Add each target in the set to the export.
for (cmTargetExport* te : *this->IEGen->GetExportSet()->GetTargetExports()) {
// Collect import properties for this target.
if (te->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
if (this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY) {
continue;
}
@ -426,6 +427,19 @@ void cmExportInstallFileGenerator::SetImportLocationProperty(
}
}
cmStateEnums::TargetType cmExportInstallFileGenerator::GetExportTargetType(
cmTargetExport const* targetExport) const
{
cmStateEnums::TargetType targetType = targetExport->Target->GetType();
// An OBJECT library installed with no OBJECTS DESTINATION
// is transformed to an INTERFACE library.
if (targetType == cmStateEnums::OBJECT_LIBRARY &&
targetExport->ObjectsGenerator == nullptr) {
targetType = cmStateEnums::INTERFACE_LIBRARY;
}
return targetType;
}
void cmExportInstallFileGenerator::HandleMissingTarget(
std::string& link_libs, std::vector<std::string>& missingTargets,
cmGeneratorTarget* depender, cmGeneratorTarget* dependee)

View File

@ -6,6 +6,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmExportFileGenerator.h"
#include "cmStateTypes.h"
#include <iosfwd>
#include <map>
@ -17,6 +18,7 @@ class cmGeneratorTarget;
class cmGlobalGenerator;
class cmInstallExportGenerator;
class cmInstallTargetGenerator;
class cmTargetExport;
/** \class cmExportInstallFileGenerator
* \brief Generate a file exporting targets from an install tree.
@ -57,6 +59,8 @@ protected:
void GenerateImportTargetsConfig(
std::ostream& os, const std::string& config, std::string const& suffix,
std::vector<std::string>& missingTargets) override;
cmStateEnums::TargetType GetExportTargetType(
cmTargetExport const* targetExport) const;
void HandleMissingTarget(std::string& link_libs,
std::vector<std::string>& missingTargets,
cmGeneratorTarget* depender,

View File

@ -32,7 +32,7 @@ bool cmExportTryCompileFileGenerator::GenerateMainFile(std::ostream& os)
this->Exports.pop_back();
if (emitted.insert(te).second) {
emittedDeps.insert(te);
this->GenerateImportTargetCode(os, te);
this->GenerateImportTargetCode(os, te, te->GetType());
ImportPropertyMap properties;

View File

@ -360,17 +360,6 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
this->SetError(e.str());
return false;
}
if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
std::string reason;
if (!this->Makefile->GetGlobalGenerator()->HasKnownObjectFileLocation(
&reason)) {
std::ostringstream e;
e << "TARGETS given OBJECT library \"" << tgt
<< "\" which may not be installed" << reason << ".";
this->SetError(e.str());
return false;
}
}
// Store the target in the list to be installed.
targets.push_back(target);
} else {
@ -534,15 +523,23 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
case cmStateEnums::OBJECT_LIBRARY: {
// Objects use OBJECT properties.
if (!objectArgs.GetDestination().empty()) {
// Verify that we know where the objects are to install them.
std::string reason;
if (!this->Makefile->GetGlobalGenerator()
->HasKnownObjectFileLocation(&reason)) {
std::ostringstream e;
e << "TARGETS given OBJECT library \"" << target.GetName()
<< "\" whose objects may not be installed" << reason << ".";
this->SetError(e.str());
return false;
}
objectGenerator =
CreateInstallTargetGenerator(target, objectArgs, false);
} else {
std::ostringstream e;
e << "TARGETS given no OBJECTS DESTINATION for object library "
"target \""
<< target.GetName() << "\".";
this->SetError(e.str());
return false;
// Installing an OBJECT library without a destination transforms
// it to an INTERFACE library. It installs no files but can be
// exported.
}
} break;
case cmStateEnums::EXECUTABLE: {

View File

@ -83,11 +83,13 @@ set_property(TARGET testLib7 PROPERTY OUTPUT_NAME testLib7-$<CONFIG>)
add_library(testLib8 OBJECT testLib8A.c testLib8B.c sub/testLib8C.c)
if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "[;$]")
set(maybe_testLib8 testLib8)
set(maybe_OBJECTS_DESTINATION OBJECTS DESTINATION $<1:lib>)
else()
set(maybe_testLib8 "")
set(maybe_OBJECTS_DESTINATION "")
endif()
add_library(testLib9Obj OBJECT testLib9Obj.c)
# Test using the target_link_libraries command to set the
# LINK_INTERFACE_LIBRARIES* properties. We construct two libraries
# providing the same two symbols. In each library one of the symbols
@ -483,7 +485,7 @@ install(
TARGETS
testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3 testExe4
testExe2lib testLib4lib testLib4libdbg testLib4libopt
testLib6 testLib7 ${maybe_testLib8}
testLib6 testLib7 testLib8
testLibCycleA testLibCycleB
testLibNoSONAME
cmp0022NEW cmp0022OLD
@ -492,10 +494,15 @@ install(
RUNTIME DESTINATION $<1:bin>
LIBRARY DESTINATION $<1:lib> NAMELINK_SKIP
ARCHIVE DESTINATION $<1:lib>
OBJECTS DESTINATION $<1:lib>
${maybe_OBJECTS_DESTINATION}
FRAMEWORK DESTINATION Frameworks
BUNDLE DESTINATION Applications
)
install(
TARGETS
testLib9Obj
EXPORT exp
)
if (APPLE)
file(COPY testLib4.h DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/testLib4.framework/Headers)
file(COPY testLib4.h DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug/testLib4.framework/Headers)
@ -545,7 +552,8 @@ export(TARGETS testExe1 testLib1 testLib2 testLib3
FILE ExportBuildTree.cmake
)
export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 testExe2lib
${maybe_testLib8}
testLib8
testLib9Obj
testLib4lib testLib4libdbg testLib4libopt
testLibCycleA testLibCycleB
testLibNoSONAME

View File

@ -0,0 +1,4 @@
int testLib9Obj(void)
{
return 0;
}

View File

@ -229,6 +229,8 @@ add_library(imp_lib1b STATIC imp_lib1.c)
target_link_libraries(imp_lib1b bld_testLib2)
if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "[;$]")
set(bld_objlib_type OBJECT_LIBRARY)
# Create a executable that is using objects imported from the install tree
add_executable(imp_testLib8 imp_testLib8.c $<TARGET_OBJECTS:exp_testLib8>)
@ -236,6 +238,18 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode" OR NOT CMAKE_OSX_ARCHITECTURES MATCHES "
# Create a executable that is using objects imported from the build tree
add_executable(imp_testLib8b imp_testLib8.c $<TARGET_OBJECTS:bld_testLib8>)
endif()
else()
set(bld_objlib_type INTERFACE_LIBRARY)
endif()
# Check that object libraries were transformed on export as expected.
get_property(type TARGET exp_testLib9Obj PROPERTY TYPE)
if(NOT type STREQUAL INTERFACE_LIBRARY)
message(FATAL_ERROR "exp_testLib9Obj type is '${type}', not 'INTERFACE_LIBRARY'")
endif()
get_property(type TARGET bld_testLib9Obj PROPERTY TYPE)
if(NOT type STREQUAL "${bld_objlib_type}")
message(FATAL_ERROR "bld_testLib9Obj type is '${type}', not '${bld_objlib_type}'")
endif()
#-----------------------------------------------------------------------------

View File

@ -1,5 +0,0 @@
CMake Error at ExportNotSupported.cmake:[0-9]+ \(export\):
export given OBJECT library "A" which may not be exported under Xcode with
multiple architectures.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -1,2 +0,0 @@
add_library(A OBJECT a.c)
export(TARGETS A FILE AExport.cmake)

View File

@ -1,5 +1,5 @@
CMake Error at InstallNotSupported.cmake:[0-9]+ \(install\):
install TARGETS given OBJECT library "A" which may not be installed under
Xcode with multiple architectures.
install TARGETS given OBJECT library "A" whose objects may not be installed
under Xcode with multiple architectures.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -6,14 +6,13 @@ run_cmake(BadSourceExpression3)
run_cmake(BadObjSource1)
run_cmake(BadObjSource2)
if(RunCMake_GENERATOR STREQUAL "Xcode" AND "$ENV{CMAKE_OSX_ARCHITECTURES}" MATCHES "[;$]")
run_cmake(ExportNotSupported)
run_cmake(ImportNotSupported)
run_cmake(InstallNotSupported)
else()
run_cmake(Export)
run_cmake(Import)
run_cmake(Install)
endif()
run_cmake(Export)
run_cmake(LinkObjLHS)
run_cmake(LinkObjRHS1)
run_cmake(LinkObjRHS2)