AIX: support VERSION/SOVERSION symlinks for plain shared objects

On AIX, when AIX_SHARED_LIBRARY_ARCHIVE is off, VERSION and SOVERSION
were ignored and only a single `lib<name>.so` was produced.

Add the AIX_SHARED_LIBRARY_SYMLINKS target property, initialized by the
CMAKE_AIX_SHARED_LIBRARY_SYMLINKS variable, to name the shared object
`lib<name>.so.<version>` and create `lib<name>.so.<soversion>` and
`lib<name>.so` symlinks, as on other UNIX platforms.  When the property
is not set, policy CMP0227 selects the default.

AIX shared objects have no builtin soname, so a dependent records the
name of the file used to link it.  Link dependents through the
`lib<name>.so.<soversion>` symlink so they need not be rebuilt for a
compatible update.  If only VERSION is set, link to
`lib<name>.so.<version>`.

Extend the SharedLibraryArchive test to check the file names, symlinks,
and the library name recorded by dependents.
This commit is contained in:
Naropanth Srikar Rao authored and Brad King committed 2026-10-29 12:04:33 -04:00
1 parent edd2712a34
commit 06440c35f6
19 files changed
+245 -10

No files matched your search

+8
View File
@@ -94,6 +94,14 @@ Supported Policies
The following policies are supported.
Policies Introduced by CMake 4.6
--------------------------------
.. toctree::
:maxdepth: 1
CMP0227: AIX shared objects are named with VERSION and SOVERSION. </policy/CMP0227>
Policies Introduced by CMake 4.5
--------------------------------
+1
View File
@@ -149,6 +149,7 @@ Properties on Targets
/prop_tgt/ADDITIONAL_CLEAN_FILES
/prop_tgt/AIX_EXPORT_ALL_SYMBOLS
/prop_tgt/AIX_SHARED_LIBRARY_ARCHIVE
/prop_tgt/AIX_SHARED_LIBRARY_SYMLINKS
/prop_tgt/ALIAS_GLOBAL
/prop_tgt/ALIASED_TARGET
/prop_tgt/ANDROID_ANT_ADDITIONAL_OPTIONS
+1
View File
@@ -413,6 +413,7 @@ Variables that Control the Build
/variable/CMAKE_ADSP_ROOT
/variable/CMAKE_AIX_EXPORT_ALL_SYMBOLS
/variable/CMAKE_AIX_SHARED_LIBRARY_ARCHIVE
/variable/CMAKE_AIX_SHARED_LIBRARY_SYMLINKS
/variable/CMAKE_ANDROID_ANT_ADDITIONAL_OPTIONS
/variable/CMAKE_ANDROID_API
/variable/CMAKE_ANDROID_API_MIN
+31
View File
@@ -0,0 +1,31 @@
CMP0227
-------
.. versionadded:: 4.6
AIX shared objects are named with ``VERSION`` and ``SOVERSION``.
On AIX, when :prop_tgt:`AIX_SHARED_LIBRARY_ARCHIVE` is disabled, a
``SHARED`` library target is built as a plain shared object ``.so`` file.
CMake 4.5 and below ignore the :prop_tgt:`VERSION` and :prop_tgt:`SOVERSION`
target properties for such files because AIX shared objects have no
builtin soname, so only a single ``lib<name>.so`` file is produced.
CMake 4.6 added the :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS` target property
to name the shared object with :prop_tgt:`VERSION` and create
:prop_tgt:`SOVERSION` and unversioned symlinks to it, as on other UNIX
platforms.
CMake 4.6 and above prefer, when :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS`
is not set, to enable versioned names and symlinks by default.
This policy provides compatibility for projects that have not been updated.
The ``OLD`` behavior for this policy is to disable versioned names and
symlinks when :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS` is not set.
The ``NEW`` behavior for this policy is to enable versioned names and
symlinks when :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS` is not set.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.6
.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst
@@ -0,0 +1,28 @@
AIX_SHARED_LIBRARY_SYMLINKS
---------------------------
.. versionadded:: 4.6
On AIX, enable or disable :prop_tgt:`VERSION` and :prop_tgt:`SOVERSION`
file names for a ``SHARED`` library target that is built as a plain shared
object, i.e., when :prop_tgt:`AIX_SHARED_LIBRARY_ARCHIVE` is disabled.
This property has no effect on shared library archives.
* If enabled, the shared object is named ``lib<name>.so.<version>``
and symlinks ``lib<name>.so.<soversion>`` and ``lib<name>.so``
are created, as on other UNIX platforms.
AIX shared objects have no builtin soname, so a dependent records the
name of the file used to link it. CMake links dependents through the
``lib<name>.so.<soversion>`` symlink so they depend on that name at
runtime. If :prop_tgt:`SOVERSION` is not set, dependents link to
``lib<name>.so.<version>`` instead. Projects that do not want
dependents to record the full version should set :prop_tgt:`SOVERSION`.
* If disabled, a single ``lib<name>.so`` file is produced and the
:prop_tgt:`VERSION` and :prop_tgt:`SOVERSION` properties are ignored.
This property defaults to :variable:`CMAKE_AIX_SHARED_LIBRARY_SYMLINKS`
if that variable is set when a non-imported ``SHARED`` library target
is created by :command:`add_library`. If this property is not set,
the default is determined by policy :policy:`CMP0227`.
@@ -0,0 +1,7 @@
aix-shared-library-symlinks
---------------------------
* On AIX, the :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS` target property and
:variable:`CMAKE_AIX_SHARED_LIBRARY_SYMLINKS` variable were added to name
plain shared objects with :prop_tgt:`VERSION` and :prop_tgt:`SOVERSION`
and create symlinks. See policy :policy:`CMP0227`.
@@ -0,0 +1,11 @@
CMAKE_AIX_SHARED_LIBRARY_SYMLINKS
---------------------------------
.. versionadded:: 4.6
On AIX, enable or disable :prop_tgt:`VERSION` and :prop_tgt:`SOVERSION`
file names for plain shared objects.
This variable initializes the :prop_tgt:`AIX_SHARED_LIBRARY_SYMLINKS`
target property on non-imported ``SHARED`` library targets as they are
created by :command:`add_library`. See that target property for details.
+2
View File
@@ -63,6 +63,8 @@ Entry const kEntries[] = {
"Default value for AIX_EXPORT_ALL_SYMBOLS target property"_s },
{ "CMAKE_AIX_SHARED_LIBRARY_ARCHIVE"_s,
"Enable or disable creation of shared library archives"_s },
{ "CMAKE_AIX_SHARED_LIBRARY_SYMLINKS"_s,
"Enable or disable VERSION/SOVERSION names for AIX shared objects"_s },
{ "CMAKE_ANDROID_ANT_ADDITIONAL_OPTIONS"_s,
"Default value for ANDROID_ANT_ADDITIONAL_OPTIONS target property"_s },
{ "CMAKE_ANDROID_API"_s,
+9 -3
View File
@@ -1185,10 +1185,16 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
? cmStateEnums::ImportLibraryArtifact
: cmStateEnums::RuntimeBinaryArtifact;
// Pass the full path to the target file.
// Pass the full path to the target file. AIX shared objects have no
// builtin soname, so the consumer records the file name used to link.
// Link via the SOVERSION symlink so the consumer depends on it at
// runtime.
cmGeneratorTarget::NameType const nameType =
tgt->IsAIXSharedLibraryWithSymlinks()
? cmGeneratorTarget::NameType::SOName
: cmGeneratorTarget::NameType::Real;
BT<std::string> lib = BT<std::string>(
tgt->GetFullPath(config, artifact, cmGeneratorTarget::NameType::Real),
item.Backtrace);
tgt->GetFullPath(config, artifact, nameType), item.Backtrace);
if (tgt->IsAIX() && cmHasLiteralSuffix(lib.Value, "-NOTFOUND") &&
artifact == cmStateEnums::ImportLibraryArtifact) {
// This is an imported executable on AIX that has ENABLE_EXPORTS
+30 -3
View File
@@ -3918,7 +3918,19 @@ std::string cmGeneratorTarget::GetFullPath(std::string const& config,
NameType nameType) const
{
if (this->IsImported()) {
return this->Target->ImportedGetFullPath(config, artifact);
std::string path = this->Target->ImportedGetFullPath(config, artifact);
if (nameType == NameType::SOName &&
artifact == cmStateEnums::RuntimeBinaryArtifact && !path.empty() &&
!cmHasLiteralSuffix(path, "-NOTFOUND")) {
std::string soName = this->GetSOName(config, artifact);
if (!soName.empty()) {
// The soname names a file in the directory of the location.
std::string::size_type const slash = path.rfind('/');
path = cmStrCat(
slash == std::string::npos ? "" : path.substr(0, slash + 1), soName);
}
}
return path;
}
return this->NormalGetFullPath(config, artifact, nameType);
}
@@ -3933,6 +3945,15 @@ std::string cmGeneratorTarget::NormalGetFullPath(
cmStrCat(this->BuildBundleDirectory(fpath, config, FullLevel), '/');
}
if (nameType == NameType::SOName) {
std::string soName = this->GetSOName(config, artifact);
if (!soName.empty()) {
return cmStrCat(fpath, soName);
}
// There is no soname, so fall back to the real name.
nameType = NameType::Real;
}
// Add the full name of the target.
switch (artifact) {
case cmStateEnums::RuntimeBinaryArtifact:
@@ -3994,11 +4015,12 @@ cmGeneratorTarget::Names cmGeneratorTarget::GetLibraryNames(
// Check for library version properties.
cmValue version = this->GetProperty("VERSION");
cmValue soversion = this->GetProperty("SOVERSION");
if (!this->HasSOName(config) ||
if ((!this->HasSOName(config) && !this->IsAIXSharedLibraryWithSymlinks()) ||
this->Makefile->IsOn("CMAKE_PLATFORM_NO_VERSIONED_SONAME") ||
this->IsFrameworkOnApple()) {
// Versioning is supported only for shared libraries and modules,
// and then only when the platform supports an soname flag.
// and then only when the platform supports an soname flag,
// or on AIX when explicitly requested by AIX_SHARED_LIBRARY_SYMLINKS.
version = nullptr;
soversion = nullptr;
}
@@ -6078,6 +6100,11 @@ bool cmGeneratorTarget::IsArchivedAIXSharedLibrary() const
return this->Target->IsArchivedAIXSharedLibrary();
}
bool cmGeneratorTarget::IsAIXSharedLibraryWithSymlinks() const
{
return this->Target->IsAIXSharedLibraryWithSymlinks();
}
bool cmGeneratorTarget::IsImportedFrameworkFolderOnApple(
std::string const& config) const
{
+5
View File
@@ -317,6 +317,7 @@ public:
enum class NameType
{
Output, // The name that other build rules refer to, e.g. libfoo.so.
SOName, // The soname, e.g. libfoo.so.1. Falls back to Real if empty.
Real, // The name of the real file, e.g. libfoo.so.1.2.3.
};
@@ -1045,6 +1046,10 @@ public:
/** Return whether this target is a shared library on AIX. */
bool IsArchivedAIXSharedLibrary() const;
/** Return whether this target is a plain AIX shared object with
VERSION/SOVERSION file names and symlinks. */
bool IsAIXSharedLibraryWithSymlinks() const;
/** Assembly types. The order of the values of this enum is relevant
because of smaller/larger comparison operations! */
enum ManagedType
+6 -2
View File
@@ -682,7 +682,10 @@ class cmMakefile;
"default configure command.", \
4, 5, 0, WARN) \
SELECT(POLICY, CMP0226, "Respect Swift_MODULE_NAME with Xcode Generator", \
4, 5, 0, WARN)
4, 5, 0, WARN) \
SELECT(POLICY, CMP0227, \
"AIX shared objects are named with VERSION and SOVERSION.", 4, 6, 0, \
WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
@@ -742,7 +745,8 @@ class cmMakefile;
F(CMP0214) \
F(CMP0215) \
F(CMP0216) \
F(CMP0226)
F(CMP0226) \
F(CMP0227)
#define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \
F(CMP0116) \
+31 -1
View File
@@ -364,6 +364,7 @@ TargetProperty const StaticTargetProperties[] = {
{ "WATCOM_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
// ---- AIX
{ "AIX_SHARED_LIBRARY_ARCHIVE"_s, IC::SharedLibraryTarget },
{ "AIX_SHARED_LIBRARY_SYMLINKS"_s, IC::SharedLibraryTarget },
// -- Language
// ---- C
COMMON_LANGUAGE_PROPERTIES(C),
@@ -1142,7 +1143,9 @@ cmTarget::cmTarget(std::string name, cm::TargetType type, Visibility vis,
}
// Imported targets must set AIX_SHARED_LIBRARY_ARCHIVE explicitly.
if (this->IsImported() && property == "AIX_SHARED_LIBRARY_ARCHIVE"_s) {
if (this->IsImported() &&
(property == "AIX_SHARED_LIBRARY_ARCHIVE"_s ||
property == "AIX_SHARED_LIBRARY_SYMLINKS"_s)) {
return;
}
@@ -1415,6 +1418,33 @@ bool cmTarget::IsArchivedAIXSharedLibrary() const
return false;
}
bool cmTarget::IsAIXSharedLibraryWithSymlinks() const
{
if (this->GetType() != cm::TargetType::SHARED_LIBRARY || !this->IsAIX() ||
this->IsArchivedAIXSharedLibrary()) {
return false;
}
if (this->IsImported()) {
// The soname of an imported plain shared object, if any, names the
// file to link. Without one, linking uses the imported location.
return true;
}
cmValue value = this->GetProperty("AIX_SHARED_LIBRARY_SYMLINKS");
if (!value.IsEmpty()) {
return value.IsOn();
}
switch (this->GetPolicyStatusCMP0227()) {
case cmPolicies::WARN:
case cmPolicies::OLD:
// The OLD behavior's default is to not create versioned names.
break;
case cmPolicies::NEW:
// The NEW behavior's default is to create versioned names.
return true;
}
return false;
}
bool cmTarget::IsAppBundleOnApple() const
{
return (this->GetType() == cm::TargetType::EXECUTABLE && this->IsApple() &&
+4
View File
@@ -281,6 +281,10 @@ public:
//! Return whether to archive shared library or not on AIX.
bool IsArchivedAIXSharedLibrary() const;
//! Return whether a plain AIX shared object gets VERSION/SOVERSION
//! file names and symlinks.
bool IsAIXSharedLibraryWithSymlinks() const;
//! Return whether this target is an executable Bundle on Apple.
bool IsAppBundleOnApple() const;
@@ -59,6 +59,7 @@
\* CMP0215
\* CMP0216
\* CMP0226
\* CMP0227
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
@@ -16,8 +16,10 @@ set(properties
# Linking properties
## Platforms
### AIX
# Imported targets must set `AIX_SHARED_LIBRARY_ARCHIVE` explicitly.
# Imported targets must set `AIX_SHARED_LIBRARY_ARCHIVE` and
# `AIX_SHARED_LIBRARY_SYMLINKS` explicitly.
"AIX_SHARED_LIBRARY_ARCHIVE" "OFF" "<SAME>"
"AIX_SHARED_LIBRARY_SYMLINKS" "OFF" "<SAME>"
)
prepare_target_types(non_imported_shared_library
+41
View File
@@ -63,3 +63,44 @@ set_property(TARGET nosla_CMP0182 PROPERTY AIX_SHARED_LIBRARY_ARCHIVE OFF)
add_custom_command(TARGET nosla_CMP0182 POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -Dnosla=$<TARGET_FILE:nosla_CMP0182> -Dname=nosla_CMP0182 -P${CMAKE_CURRENT_SOURCE_DIR}/nosla-check.cmake
)
add_library(nosla_symlinks SHARED sla.c)
set_target_properties(nosla_symlinks PROPERTIES
AIX_SHARED_LIBRARY_ARCHIVE OFF
AIX_SHARED_LIBRARY_SYMLINKS ON
VERSION 1.2.3
SOVERSION 1
)
add_custom_command(TARGET nosla_symlinks POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -Dnosla=$<TARGET_FILE:nosla_symlinks> -Dname=nosla_symlinks -Dversion=1.2.3 -Dsoversion=1 -P${CMAKE_CURRENT_SOURCE_DIR}/nosla-symlinks-check.cmake
)
add_executable(UseNoSLA_symlinks use_sla.c)
target_link_libraries(UseNoSLA_symlinks PRIVATE nosla_symlinks)
add_custom_command(TARGET UseNoSLA_symlinks POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -Dexe=$<TARGET_FILE:UseNoSLA_symlinks> -Dexpected=libnosla_symlinks.so.1 -P${CMAKE_CURRENT_SOURCE_DIR}/nosla-symlinks-link-check.cmake
)
# Without SOVERSION, dependents link to the VERSION name.
add_library(nosla_version SHARED sla.c)
set_target_properties(nosla_version PROPERTIES
AIX_SHARED_LIBRARY_ARCHIVE OFF
AIX_SHARED_LIBRARY_SYMLINKS ON
VERSION 1.2.3
)
add_executable(UseNoSLA_version use_sla.c)
target_link_libraries(UseNoSLA_version PRIVATE nosla_version)
add_custom_command(TARGET UseNoSLA_version POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -Dexe=$<TARGET_FILE:UseNoSLA_version> -Dexpected=libnosla_version.so.1.2.3 -P${CMAKE_CURRENT_SOURCE_DIR}/nosla-symlinks-link-check.cmake
)
cmake_policy(SET CMP0227 NEW)
add_library(nosla_CMP0227 SHARED sla.c)
set_target_properties(nosla_CMP0227 PROPERTIES
AIX_SHARED_LIBRARY_ARCHIVE OFF
VERSION 1.2.3
SOVERSION 1
)
add_custom_command(TARGET nosla_CMP0227 POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -Dnosla=$<TARGET_FILE:nosla_CMP0227> -Dname=nosla_CMP0227 -Dversion=1.2.3 -Dsoversion=1 -P${CMAKE_CURRENT_SOURCE_DIR}/nosla-symlinks-check.cmake
)
@@ -0,0 +1,13 @@
if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
# $<TARGET_FILE:...> is the unversioned name, so inspect the directory.
get_filename_component(dir "${nosla}" DIRECTORY)
set(real "${dir}/lib${name}.so.${version}")
if(NOT EXISTS "${real}" OR IS_SYMLINK "${real}")
message(FATAL_ERROR "nosla library real file missing or a symlink:\n ${real}")
endif()
foreach(link IN ITEMS "lib${name}.so.${soversion}" "lib${name}.so")
if(NOT IS_SYMLINK "${dir}/${link}")
message(FATAL_ERROR "nosla library symlink missing:\n ${dir}/${link}")
endif()
endforeach()
endif()
@@ -0,0 +1,13 @@
if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
# AIX shared objects have no soname, so the executable records the
# name of the file it was linked with.
execute_process(COMMAND dump -X32_64 -H "${exe}"
OUTPUT_VARIABLE out RESULT_VARIABLE res)
if(NOT res EQUAL 0)
message(FATAL_ERROR "dump failed on ${exe}")
endif()
string(REPLACE "." "\\." expected_regex "${expected}")
if(NOT out MATCHES "[ \t]${expected_regex}[ \t]*(\n|$)")
message(FATAL_ERROR "${exe} does not depend on ${expected}:\n${out}")
endif()
endif()