Skip to content

refactor!: strongly typed values in BuildSettings and BuildFileSettings #903

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Mar 17, 2025

Conversation

waltflanagan
Copy link
Member

Short description 📝

Mostly work that was done on the 9.0.0 branch that shouldn't be coupled to Sendability changes.

This introduces the BuildSetting type that will allow safer manipulation of build settings and set us with more typesafety when working with build settings that have constraints.

Keys issues are that this introduces source breaking changes with the signature of the BuildSettings typealias.

@waltflanagan waltflanagan changed the title Strongly Typed values in BuildSettings refactor: Strongly Typed values in BuildSettings Feb 18, 2025
@waltflanagan waltflanagan changed the title refactor: Strongly Typed values in BuildSettings refactor: strongly typed values in BuildSettings and BuildFileSettings Feb 18, 2025
@waltflanagan waltflanagan force-pushed the waltflanagan/StrongTypes branch 2 times, most recently from a28c2ff to bf96639 Compare February 19, 2025 01:48
kwridan added a commit to kwridan/xcdiff that referenced this pull request Feb 19, 2025
- Updates to xcdiff for compatibility with tuist/XcodeProj#903
- Build setting values are no longer type erased and instead can now either be a `String` or `[String]`
- This alleviates the need to casting values and throwing errors when dealing with build settings

Test Plan:

- Verify CI passes

Signed-off-by: Kassem Wridan <[email protected]>
Copy link
Collaborator

@kwridan kwridan left a comment

Choose a reason for hiding this comment

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

Thanks @waltflanagan this is a much needed change! 👌

Indeed this is source breaking and would likely need a major release

e.g. updates needed for xcdiff bloomberg/xcdiff#145

Comment on lines 445 to 450
// func XCTAssertEqual(_ lhs: BuildSettings, _ rhs: BuildSettings, file: StaticString = #file, line: UInt = #line) {
// XCTAssertEqual(lhs as NSDictionary,
// rhs as NSDictionary,
// file: file,
// line: line)
// }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this still needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

probably not

Copy link
Member

Choose a reason for hiding this comment

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

then let's remove it? 😁

@waltflanagan
Copy link
Member Author

@kwridan thats what i was afraid of. I'll see if i can find some time to put together a 9.0 checklist so we can identify anything else that we may want to break There are some other Any collections but those seem like they could follow a similar approach to these callsites.

Do you think it would be worth a transitional phase that preserves BuildSettings as [String: Any] but introduces strongly typed backing stores and parsing to ease into a 9.0 later? It could be something like typealias TypedBuildSettings = [String: BuildSetting] and we would have interfaces that expose the legacy BuildSetting type but convert to TypedBuildSettings.

@kwridan
Copy link
Collaborator

kwridan commented Feb 20, 2025

I wouldn't shy away from a a major release for this per se as there are already a few compatibility APIs lying around that could do with a clean up:

e.g.

https://github.com/tuist/XcodeProj/blob/main/Sources/XcodeProj/Scheme/XCScheme%2BTestableReference.swift#L10

You are right, there are other collections (e.g. attributes) that could do with a similar update, as such to incrementally tackle them deferring the breaking changes such that they are all done together for the next major release makes sense.

Another accessor could be exposed with the strong types typedBuildSettings: TypedBuildSettings that could then be renamed back to buildSettings once the breaking changes are made, that could offer some value till then but at the slightly inconvenience of introducing new public API that is very short lived and would requires clients to migrate of off.

@fortmarek fortmarek changed the title refactor: strongly typed values in BuildSettings and BuildFileSettings refactor!: strongly typed values in BuildSettings and BuildFileSettings Feb 27, 2025
Copy link
Member

@fortmarek fortmarek left a comment

Choose a reason for hiding this comment

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

Left some minor comments, but overall, I'm very much in favor of this. Getting rid of Any will be quite an improvement when using this library and has been long overdue. Thanks Mike!

@@ -195,3 +213,47 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable {
lhs.buildFile == rhs.buildFile && lhs.buildPhase == rhs.buildPhase
}
}

public enum BuildFileSetting: Sendable, Equatable {
Copy link
Member

Choose a reason for hiding this comment

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

nit: would move this to a separate file.

Comment on lines 221 to 235
public var stringValue: String? {
if case let .string(value) = self {
value
} else {
nil
}
}

public var arrayValue: [String]? {
if case let .array(value) = self {
value
} else {
nil
}
}
Copy link
Member

Choose a reason for hiding this comment

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

where do we plan to use these? I wonder if it would be better for consumers to do the switch themselves. Anything we put here is something that we're committed to not break (or release a major version when we do)

Copy link
Member Author

Choose a reason for hiding this comment

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

Mostly convenience, the usages of these [String: Any] collections generally are casting as? <Type> and this felt like a reasonable convenience that would help minimize the disruption to current codebases (if case let syntax is not fun to work with).

Copy link
Member

Choose a reason for hiding this comment

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

not totally opposed, so we can keep these.

Copy link
Member

Choose a reason for hiding this comment

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

nit: for completely new test suites, I'd start using Swift testing instead. Eventually, we'd like to get to a place where all Tuist repositories use Swift testing, although that's going to be a long way.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good, another thing to learn about.

case string(String)
case array([String])

var valueForWriting: String {
Copy link
Member

Choose a reason for hiding this comment

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

What do you think of this using the CustomStringConvertible instead of a completely custom property?

Copy link
Member Author

Choose a reason for hiding this comment

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

That should work fine 👍

Comment on lines 19 to 33
public var stringValue: String? {
if case let .string(value) = self {
value
} else {
nil
}
}

public var arrayValue: [String]? {
Copy link
Member

Choose a reason for hiding this comment

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

would probably also remove this in favor of consumers doing the switch themselves.

@@ -602,3 +605,64 @@ extension PBXProject: PlistSerializable {
})
}
}

public enum ProjectAttribute: Sendable, Equatable {
Copy link
Member

Choose a reason for hiding this comment

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

nit: would also move this to its own file.

Comment on lines 614 to 628
public var stringValue: String? {
if case let .string(value) = self {
value
} else {
nil
}
}

public var arrayValue: [String]? {
if case let .array(value) = self {
value
} else {
nil
}
}
Copy link
Member

Choose a reason for hiding this comment

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

as mentioned above, would consider not including these.

Comment on lines 445 to 450
// func XCTAssertEqual(_ lhs: BuildSettings, _ rhs: BuildSettings, file: StaticString = #file, line: UInt = #line) {
// XCTAssertEqual(lhs as NSDictionary,
// rhs as NSDictionary,
// file: file,
// line: line)
// }
Copy link
Member

Choose a reason for hiding this comment

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

then let's remove it? 😁

@waltflanagan waltflanagan requested a review from kwridan February 27, 2025 18:30
@waltflanagan
Copy link
Member Author

@kwridan i tackled the other things you mentioned and could use a re-review. also tried to include some convenience methods to make the impact smaller from a migration perspective (avoiding the need for if case let)

Curious generally what else should be done to land this as a 9.0

This was fun. `NSDictionary(buildSettings)` is not able to be compared because we wrap swift values so we must use `==` on the swift types since they are not concrete and equatable.
The settings here are constrained to two cases, one as a string and one as a string array as defined here: https://buck.build/javadoc/com/facebook/buck/apple/xcode/xcodeproj/PBXBuildFile.html

Given the narrow use case we should constraint the available types here to fit the need.
@waltflanagan waltflanagan force-pushed the waltflanagan/StrongTypes branch from 875cafa to 648909e Compare February 28, 2025 00:02
Copy link
Collaborator

@kwridan kwridan left a comment

Choose a reason for hiding this comment

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

Thanks for the looking into the other parts @waltflanagan 🙏

I didn't necessarily mean they had to happen in the build settings PR in one go rather as separate PRs we can incrementally do for the 9.0 release. Is the worry the release process for XcoeProj would automatically ship this PR as an minor release?

The attribute updates may need a closer look to see how to deal with references correctly to ensure their final values are resolved - my thinking is potentially adding another case .targetReference() which would be accounted for.

Copy link
Collaborator

@kwridan kwridan left a comment

Choose a reason for hiding this comment

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

marking as requesting changes per above (for the attributes + target reference issues)

thanks!

@fortmarek
Copy link
Member

Is the worry the release process for XcoeProj would automatically ship this PR as an minor release?

That would actually happen. Given the amount of changes, I think it's fine to bundle everything here. The alternative would be to create a release branch, post individual PRs there, and then merge the release branch into main, so we end up with just one major release. But I'd say it's not worth the extra complexity.

Also removed `Encodable` conformance as we have a custom `plist` method that is used for writing.
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 28, 2025
@waltflanagan
Copy link
Member Author

@kwridan just added the targetReference case and a test that covers calling fix after adding to the project. I think this covers the case you mentioned. strong types made it a quick change 🎉

One other risk of this branch is that it would block other fixes from shipping if this was merged but not quite ready to ship.

lmk next steps on releasing a 9.0.0, i was definitely going to find some time this weekend to check on the XcodeGraph and Tuist impacts of these changes to make sure they fit those needs. I can also see what kind of impact it would be on XcodeGen.

@fortmarek
Copy link
Member

i was definitely going to find some time this weekend to check on the XcodeGraph and Tuist impacts of these changes to make sure they fit those needs. I can also see what kind of impact it would be on XcodeGen.

I think that having a draft PR accommodating the change in XcodeGraph and Tuist would be a good exercise before we merge this. If you would have time for that, that'd be amazing 🙏

Copy link
Member

@fortmarek fortmarek left a comment

Choose a reason for hiding this comment

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

This is good to from my side. But as mentioned, would recommend validating these changes in XcodeGraph and tuist/tuist before merging.

@@ -0,0 +1,43 @@
public enum BuildFileSetting: Sendable, Equatable {
Copy link
Member

Choose a reason for hiding this comment

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

It's pretty self-explanatory but given this is public now, I'd add some basic documentation.

private let yes = "YES"
private let no = "NO"

public enum BuildSetting: Sendable, Equatable {
Copy link
Member

Choose a reason for hiding this comment

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

Would also add some basic documentation.

Comment on lines +25 to +26
case no: false
default: nil
Copy link
Member

Choose a reason for hiding this comment

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

Xcode's behavior is actually defaulting to false if the value is defined but it's not YES or NO. But conceptually, this feels better, so I'll let you decide what's better here 😅

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the purposes of this utility it strictly checking for boolean build settings as opposed to attempting to resolve a boolean value for the build setting.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense!

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 28, 2025
Copy link
Collaborator

@kwridan kwridan left a comment

Choose a reason for hiding this comment

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

Thanks for the updates @waltflanagan

@fortmarek what's the process for marking this as a major release ahead of merging?

Comment on lines +25 to +26
case no: false
default: nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the purposes of this utility it strictly checking for boolean build settings as opposed to attempting to resolve a boolean value for the build setting.

@fortmarek
Copy link
Member

what's the process for marking this as a major release ahead of merging?

I believe the only thing necessary is the exclamation mark already added by @waltflanagan

@fortmarek
Copy link
Member

Thanks for the contribution @waltflanagan! And sorry for taking a bit longer to merge this.

@fortmarek fortmarek merged commit a9eb6af into main Mar 17, 2025
9 checks passed
@fortmarek fortmarek deleted the waltflanagan/StrongTypes branch March 17, 2025 18:10
@waltflanagan
Copy link
Member Author

No worries! glad its moving along. I've had some other things going on as well. Thanks for merging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants