Skip to content

feat: add traits to XCRemoteSwiftPackageReference and XCLocalSwiftPackageReference#1114

Open
chigichan24 wants to merge 4 commits intotuist:mainfrom
chigichan24:support-package-traits
Open

feat: add traits to XCRemoteSwiftPackageReference and XCLocalSwiftPackageReference#1114
chigichan24 wants to merge 4 commits intotuist:mainfrom
chigichan24:support-package-traits

Conversation

@chigichan24
Copy link
Copy Markdown

Resolves #1113

Short description 📝

Describe here the purpose of your PR.

Add support for Swift Package traits (SE-0450) on XCRemoteSwiftPackageReference and XCLocalSwiftPackageReference, matching the traits = ( ... ); format that Xcode 26.4+ writes into project.pbxproj. Without this, any call to XcodeProj.write() on a trait-configured project silently discards the user's trait selection, and Xcode falls back to default traits on next open.

Solution 📦

Describe the solution you came up with and the reasons that led you to that solution. If you thought about other solutions don't forget about mentioning them.

Add optional traits: [String]? on both reference types:

  • decodeIfPresent for reading, if let ... { ... } guard in plistKeyAndValue for writing — same shape already used by other optional fields on these types (e.g. versionRequirement).
  • Keep nil and [] distinct. SE-0450 treats traits: [] as explicitly disable default traits, semantically different from no selection. Both forms appear in real-world pbxproj output today:
    • sentry-cocoa macOS CLI sample: traits = ( NoUIFramework, );
    • mlx-swift-examples: traits = ( );
  • XCSwiftPackageProductDependency is intentionally not modified — sampling several real pbxproj files shows Xcode does not emit traits on product-dependency entries; the selection lives on the reference side only.

Alternatives considered:

  • Set<String>? instead of [String]? — rejected because pbxproj arrays preserve source-file order and XcodeProj prioritizes fidelity to the on-disk representation over UI semantics.
  • Collapsing nil/empty into a Bool — rejected because it loses the SE-0450 distinction between "use defaults" and "no traits".

While wiring the equality check in, a pre-existing gap surfaced: no isEqual(to:) overload was synthesized for XCLocalSwiftPackageReference in Equality.generated.swift, so the override func isEqual(to object: Any?) fell back to the PBXContainerItem super and silently ignored relativePath. The first commit adds the missing entry (equivalent to what Sourcery would generate) as an independent prep step so the traits-specific changes stay minimal.

A standalone reproducer comparing stock 9.11.0 vs this branch side-by-side lives at https://github.com/chigichan24/XcodeProj-MinimalProject.

Implementation 👩‍💻👨‍💻

Detail in a checklist the steps that you took to implement the PR.

  • Add missing XCLocalSwiftPackageReference entry to Equality.generated.swift (prep — unrelated to traits but needed so the new traits comparison actually takes effect)
  • Add traits: [String]? to XCRemoteSwiftPackageReference (init parameter, CodingKeys, decodeIfPresent, conditional plistKeyAndValue encode, equality entry) plus unit tests for decode / encode / equality
  • Add the same shape to XCLocalSwiftPackageReference with matching unit tests
  • Add Fixtures/iOS/ProjectWithSwiftPackageTraits.xcodeproj covering both populated and empty-array traits, and extend XcodeProjIntegrationTests with a testReadWriteProducesNoDiff case over the new fixture

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Apr 19, 2026
Comment on lines +548 to +569
/* Begin XCLocalSwiftPackageReference section */
C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference "MyLocalPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = MyLocalPackage;
traits = (
NoUIFramework,
);
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCRemoteSwiftPackageReference section */
42AA19FF22AAF0D600428760 /* XCRemoteSwiftPackageReference "RxSwift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ReactiveX/RxSwift";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.1;
};
traits = (
);
};
/* End XCRemoteSwiftPackageReference section */
Copy link
Copy Markdown
Author

@chigichan24 chigichan24 Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: The reason of PR diff size is XL.

Fixtures/iOS/ProjectWithSwiftPackageTraits.xcodeproj/project.pbxproj is ~600 lines, which makes this PR look larger than it is. It's a copy of the existing ProjectWithXCLocalSwiftPackageReference.xcodeproj fixture with traits = ( ... ); entries added to the Remote and Local reference sections

The substantive delta from the source fixture is few lines (diff output):

< buildConfigurationList = ... "ProjectWithXCLocalSwiftPackageReference" ...;
> buildConfigurationList = ... "ProjectWithSwiftPackageTraits" ...;
< ... "ProjectWithXCLocalSwiftPackageReference" = { ...
> ... "ProjectWithSwiftPackageTraits" = { ...
+   traits = (
+       NoUIFramework,
+   );
+   traits = (
+   );

A new fixture is used rather than modifying the existing one so that testReadWriteProducesNoDiff on ProjectWithXCLocalSwiftPackageReference.xcodeproj stays stable — adding traits = ( ... ); to that existing fixture would have changed what its existing assertion produces and entangled this change with unrelated local-package add/delete tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve Swift Package trait selections across read/write

1 participant