Preserve target declaration order in generated project#1619
Open
mirkokg wants to merge 8 commits intoyonaskolb:masterfrom
Open
Preserve target declaration order in generated project#1619mirkokg wants to merge 8 commits intoyonaskolb:masterfrom
mirkokg wants to merge 8 commits intoyonaskolb:masterfrom
Conversation
Contributor
Author
|
Hi @yonaskolb I added this change to preserve target sorting as declared in specification, previously targets were always alphabetically sorted: let allTargets: [PBXTarget] = targetObjects.valueArray + targetAggregateObjects.valueArray
pbxProject.targets = allTargets
.sorted { $0.name < $1.name }I initially added new option flag to specify the order, but after some sleep and using this in my project, I had a (hopefully) better idea in mind 💡 I think this is much more intuitive for the user. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Targets in the generated
.xcodeprojnow appear in the order they are declared in the source spec, for both YAML and JSON specs. Previously they were always sorted alphabetically, regardless of source order. This affects the Xcode sidebar andxcodebuild -listoutput.Motivation
XcodeGen always sorted targets alphabetically — both at parse time (
Project.swift:184) and when writing the final xcodeproj (PBXProjGenerator.swift:326). The only workaround was renaming/prefixing targets (01_App,02_Tests), which leaks into scheme names, product names, etc.The reason alphabetical sort existed in the first place: XcodeGen uses
Yams.loadfor YAML andJSONSerializationfor JSON, both of which return a SwiftDictionarythat discards source order. A deterministic sort was imposed to avoid nondeterministic output. Yams also offersYams.compose, which returns aNodetree that preserves mapping key order — and because JSON is a valid subset of YAML 1.2,Yams.composehandles JSON specs too. This PR uses it for order extraction on both formats, while leaving the mainJSONSerializationparse path intact.Since Xcode by itself allows you to order targets the way you want I think we should also have an option in Xcodegen to do the same.
Design
Yaml.swift: newloadOrderedTargetNames(path:)helper usesYams.composeto read the ordered keys under the top-leveltargets:mapping. Works for both.ymland.jsonspecs. Returns[]whentargets:is absent (e.g. pure include fragments that only contributesettings:/targetTemplates:).SpecFile: carries a per-filetargetDeclarationOrder: [String]. A newresolvedTargetDeclarationOrder()walks the include graph and concatenates orders, with first-occurrence-wins dedup — an include that declares a target keeps its slot even when the main spec overrides the target's content.Project.init: accepts an optionaltargetDeclarationOrder: [String] = []. BeforeresolveProjectruns, declaration keys are expanded throughexpandedTargetOrderto handle two transformations that happen inresolveProject:name:rename —IncludedTargetwithname: IncludedTargetNewcontributesIncludedTargetNew.Foowithplatform: [iOS, macOS]contributesFoo_iOSandFoo_macOS, respectingplatformPrefix/platformSuffix.Target: extractedmultiplatformTargetName(fromExpanded:key:platform:)so the multiplatform naming formula has a single source of truth — shared betweenresolveMultiplatformTargetsandexpandedTargetOrder.(orderIndex[name] ?? .max, name)using a precomputed[name: position]dictionary — declaration order first, alphabetical tie-break for anything unlisted.PBXProjGenerator: no longer sorts. Pullsproject.targetsin order, then appendsproject.aggregateTargets(which remain alphabetical since they live under a separate YAML/JSON key). Aggregate targets don't participate in declaration ordering; they always come last.Behaviour change
This is a behavior change for every project loaded from a spec file. Regenerating will reshuffle the Xcode sidebar and
xcodebuild -listoutput to match source order.Programmatically-built
Project(memberwise initProject(name:, targets: [...])) already preserved the array order as given — no sort. That path is unchanged.Example
→ Sidebar order:
MyApp,MyAppTests,MyAppUITests. Equivalent JSON spec behaves identically.Testing
All 76 tests pass. Changes:
Tests/Fixtures/target_ordering_test.ymlandTests/Fixtures/target_ordering_test.json, andSpecLoadingTests.testTargetDeclarationOrderwith two cases — YAML and JSON — each loading a spec with non-alphabetical keys and asserting source order is preserved.PBXProjGeneratorTests.testTargetOrdering— 3 cases: declaration order honored from a YAML-parsedProject; order preserved verbatim in a programmatically-builtProject; aggregate targets land after native targets.SpecLoadingTests.testSpecLoader(thepaths_test.ymlcase, deep include chain) that hard-coded the old alphabetical order.Tests/Fixtures/CarthageProject,Tests/Fixtures/SPM, andTests/Fixtures/TestProjectpbxproj files to reflect the new order — these are checked-in generator outputs.Docs
CHANGELOG.md— entry under "Next Version" → "Changed", noting the behavior change.Test plan
swift buildcleanswift test— 76/76 passxcodebuild -listoutput match source