Hi @asynclet! I’m Dosu and I’m helping the Tuist team.
Your analysis is spot-on. The nondeterminism lives in ProjectDescription.Product.from(name:type:products:productTypes:baseProductType:) in PackageInfoMapper.swift. The products parameter is Set<PackageInfo.Product>, and the loop at lines 1944–1948 iterates that set with no defined order [1]:
for product in products { // Set — iteration order is undefined
if let productType = productTypes[product.name] {
return ProjectDescription.Product.from(product: productType)
}
}
When FirebaseCoreInternal belongs to both FirebaseCore (pinned .framework) and FirebaseAnalytics (pinned .staticFramework), which pinned type “wins” depends on whichever element Swift’s Set iterates first — which changes between runs.
The fix would be straightforward: sort products by name before iterating so the first productTypes hit is always the same. Alternatively, collect all matching productTypes hits and apply the same static-trumps-dynamic rule that the reduce at lines 1950–1971 already uses for SPM library types.
Workaround until fixed: pin the shared targets directly by target name in productTypes, e.g.:
PackageSettings(
productTypes: [
"FirebaseCore": .framework,
"FirebaseAnalytics": .staticFramework,
"FirebaseCoreInternal": .staticFramework, // explicit tie-break
// ... other shared targets
]
)
The direct target-name lookup at line 1941 runs before the nondeterministic product-name loop, so an explicit entry for the target name always wins and sidesteps the Set-iteration race.
This is a good candidate for a targeted fix PR — the change is small and isolated. If you’d like to discuss it further or coordinate on a PR, the community forum under the Contributors category is a good place to do that.
To reply, just mention @dosu.
Docs are dead. Just use Dosu.