Xcode: Use deterministic object ids for product file references

Follow-up commit 1fa2ec1bbd (Xcode: Use deterministic object ids for
targets, 2024-04-04, v3.30.0-rc1~263^2).  Each target's product
PBXFileReference (the libfoo.a / foo entries under the Products group)
was created with a random object id on every regeneration.  When a
CMake-generated Xcode project is embedded in another Xcode project, that
parent keeps the product reference's id in its remoteGlobalIDString; a
random id means the reference no longer resolves and the link falls back
to a bare `-l` flag (e.g. `ld: library 'foo' not found`).

Derive the product PBXFileReference id from the target name and the
build-tree-relative product path, the same hashing path already used for
the target object id.  The id is now stable across regenerations and
independent of the absolute build tree location.
This commit is contained in:
lapfelix
2026-06-17 09:59:31 -04:00
committed by Brad King
parent 88072f17fe
commit 2cc75c6072
6 changed files with 81 additions and 1 deletions
+4 -1
View File
@@ -3583,7 +3583,10 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeTarget(
target->AddAttribute("name", this->CreateString(gtgt->GetName()));
target->AddAttribute("productName", this->CreateString(gtgt->GetName()));
cmXCodeObject* fileRef = this->CreateObject(cmXCodeObject::PBXFileReference);
cmXCodeObject* fileRef =
this->CreateObject(cmXCodeObject::PBXFileReference,
cmStrCat("PBXFileReference:product:", gtgt->GetName(),
':', targetBinaryPath));
if (char const* fileType = this->GetTargetFileType(gtgt)) {
fileRef->AddAttribute("explicitFileType", this->CreateString(fileType));
}
@@ -0,0 +1,49 @@
# Collect "path -> object id" for every product PBXFileReference (those under
# BUILT_PRODUCTS_DIR) in a generated project.pbxproj.
function(read_product_ids pbxproj out_var)
if(NOT EXISTS "${pbxproj}")
set(RunCMake_TEST_FAILED "Project file does not exist:\n ${pbxproj}" PARENT_SCOPE)
return()
endif()
set(ids "")
file(STRINGS "${pbxproj}" lines)
foreach(line IN LISTS lines)
if(line MATCHES "isa = PBXFileReference;" AND line MATCHES "sourceTree = BUILT_PRODUCTS_DIR;")
string(REGEX MATCH "^[ \t]*([0-9A-F]+) " _ "${line}")
set(id "${CMAKE_MATCH_1}")
string(REGEX MATCH "path = ([^;]+);" _ "${line}")
set(path "${CMAKE_MATCH_1}")
list(APPEND ids "${path}=${id}")
endif()
endforeach()
list(SORT ids)
set(${out_var} "${ids}" PARENT_SCOPE)
endfunction()
set(pbxproj "${RunCMake_TEST_BINARY_DIR}/DeterministicProductIds.xcodeproj/project.pbxproj")
read_product_ids("${pbxproj}" this_ids)
if(RunCMake_TEST_FAILED)
return()
endif()
if(NOT this_ids)
set(RunCMake_TEST_FAILED "No product PBXFileReference entries found in\n ${pbxproj}")
return()
endif()
# On the second generation, compare against the first build tree. The ids must
# be byte-identical even though the build tree path differs.
if(DEFINED DeterministicProductIds_FirstBinaryDir)
read_product_ids(
"${DeterministicProductIds_FirstBinaryDir}/DeterministicProductIds.xcodeproj/project.pbxproj"
first_ids)
if(RunCMake_TEST_FAILED)
return()
endif()
if(NOT this_ids STREQUAL first_ids)
string(REPLACE ";" "\n " a "${first_ids}")
string(REPLACE ";" "\n " b "${this_ids}")
set(RunCMake_TEST_FAILED
"Product PBXFileReference ids are not deterministic across build trees.\nFirst:\n ${a}\nSecond:\n ${b}")
endif()
endif()
@@ -0,0 +1,10 @@
enable_language(C)
# Each target's product PBXFileReference must get a deterministic object id so
# that a parent project embedding this one can keep a stable remoteGlobalIDString.
add_library(productStatic STATIC empty.c)
add_library(productShared SHARED empty.c)
add_executable(productExe empty.c)
# A target in a subdirectory exercises the relative-path component of the key.
add_subdirectory(DeterministicProductIdsSub)
@@ -0,0 +1 @@
add_library(productSubStatic STATIC ../empty.c)
@@ -14,6 +14,19 @@ run_cmake_with_options(ArchsStandard "-DCMAKE_OSX_ARCHITECTURES=$(ARCHS_STANDARD
run_cmake(ExplicitCMakeLists)
run_cmake(ImplicitCMakeLists)
run_cmake(InterfaceLibSources)
function(DeterministicProductIds)
# Generate the same project into two separate build trees and verify that the
# product PBXFileReference object ids are byte-identical: deterministic and
# independent of the build tree location.
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/DeterministicProductIds-build1)
run_cmake(DeterministicProductIds)
set(DeterministicProductIds_FirstBinaryDir ${RunCMake_TEST_BINARY_DIR})
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/DeterministicProductIds-build2)
run_cmake(DeterministicProductIds)
endfunction()
DeterministicProductIds()
run_cmake_with_options(SearchPaths -DCMAKE_CONFIGURATION_TYPES=Debug)
run_cmake_with_options(InheritedParameters -DCMake_TEST_Swift=${CMake_TEST_Swift})
+4
View File
@@ -0,0 +1,4 @@
int productSymbol(void)
{
return 0;
}