Skip to content

Preserve target declaration order in generated project#1619

Open
mirkokg wants to merge 8 commits intoyonaskolb:masterfrom
mirkokg:feature/target-ordering
Open

Preserve target declaration order in generated project#1619
mirkokg wants to merge 8 commits intoyonaskolb:masterfrom
mirkokg:feature/target-ordering

Conversation

@mirkokg
Copy link
Copy Markdown
Contributor

@mirkokg mirkokg commented Apr 17, 2026

Summary

Targets in the generated .xcodeproj now 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 and xcodebuild -list output.

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.load for YAML and JSONSerialization for JSON, both of which return a Swift Dictionary that discards source order. A deterministic sort was imposed to avoid nondeterministic output. Yams also offers Yams.compose, which returns a Node tree that preserves mapping key order — and because JSON is a valid subset of YAML 1.2, Yams.compose handles JSON specs too. This PR uses it for order extraction on both formats, while leaving the main JSONSerialization parse 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: new loadOrderedTargetNames(path:) helper uses Yams.compose to read the ordered keys under the top-level targets: mapping. Works for both .yml and .json specs. Returns [] when targets: is absent (e.g. pure include fragments that only contribute settings: / targetTemplates:).
  • SpecFile: carries a per-file targetDeclarationOrder: [String]. A new resolvedTargetDeclarationOrder() 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 optional targetDeclarationOrder: [String] = []. Before resolveProject runs, declaration keys are expanded through expandedTargetOrder to handle two transformations that happen in resolveProject:
    • name: rename — IncludedTarget with name: IncludedTargetNew contributes IncludedTargetNew.
    • Multiplatform expansion — Foo with platform: [iOS, macOS] contributes Foo_iOS and Foo_macOS, respecting platformPrefix/platformSuffix.
  • Target: extracted multiplatformTargetName(fromExpanded:key:platform:) so the multiplatform naming formula has a single source of truth — shared between resolveMultiplatformTargets and expandedTargetOrder.
  • Sort key: (orderIndex[name] ?? .max, name) using a precomputed [name: position] dictionary — declaration order first, alphabetical tie-break for anything unlisted.
  • PBXProjGenerator: no longer sorts. Pulls project.targets in order, then appends project.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 -list output to match source order.

Programmatically-built Project (memberwise init Project(name:, targets: [...])) already preserved the array order as given — no sort. That path is unchanged.

Example

targets:
  MyApp:
    type: application
    platform: iOS
  MyAppTests:
    type: bundle.unit-test
    platform: iOS
  MyAppUITests:
    type: bundle.ui-testing
    platform: iOS

→ Sidebar order: MyApp, MyAppTests, MyAppUITests. Equivalent JSON spec behaves identically.

Testing

All 76 tests pass. Changes:

  • Added Tests/Fixtures/target_ordering_test.yml and Tests/Fixtures/target_ordering_test.json, and SpecLoadingTests.testTargetDeclarationOrder with two cases — YAML and JSON — each loading a spec with non-alphabetical keys and asserting source order is preserved.
  • Rewrote PBXProjGeneratorTests.testTargetOrdering — 3 cases: declaration order honored from a YAML-parsed Project; order preserved verbatim in a programmatically-built Project; aggregate targets land after native targets.
  • Updated one pre-existing assertion in SpecLoadingTests.testSpecLoader (the paths_test.yml case, deep include chain) that hard-coded the old alphabetical order.
  • Regenerated Tests/Fixtures/CarthageProject, Tests/Fixtures/SPM, and Tests/Fixtures/TestProject pbxproj 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 build clean
  • swift test — 76/76 pass
  • Manual: generate a fixture project with non-alphabetical target declarations (YAML and JSON), confirm Xcode sidebar order and xcodebuild -list output match source

@mirkokg
Copy link
Copy Markdown
Contributor Author

mirkokg commented Apr 17, 2026

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.

@mirkokg mirkokg changed the title Add targetOrdering option to control target order in generated project Preserve target declaration order from YAML in generated project Apr 23, 2026
@mirkokg mirkokg changed the title Preserve target declaration order from YAML in generated project Preserve target declaration order generated project Apr 23, 2026
@mirkokg mirkokg changed the title Preserve target declaration order generated project Preserve target declaration order in generated project Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant