I would love to have replied to the original thread with this information but apparently I can’t make more than 3 replies to a thread. Hopefully this new thread can be merged with the original one.
Context
In the original thread there was a discussion as to what would be required to preserve manifest ordering within the Workspace.swift file. This could be desirable if a project has a lot of modules with inconsistent naming strategies and ordering of the generated modules was desired to increase the navigability between those modules. For example two closely related modules are easier to work with and navigate around if they are close to each other in the Xcode navigator than to have them far apart leading to a lot of scrolling up and down (ignoring CMD+SHIFT+O).
By default the ordering of modules is alphabetical. The desire was to introduce a new generation option called .manifestOrder so the user has more control over the generation order of their projects.
I don’t personally have a lot of time to familiarise myself with all of the inner workings of Tuist, but I did a small investigation to see what would be involved with making changes to support this. These are my findings.
Investigation
I added the following in WorkspaceGenerationOptions.swift and the equivalent XcodeGraph files:
public enum ProjectsOrder: Codable, Equatable, Sendable {
/// Alphabetical order
case alphabetical
/// Keep the order as declared in `Workspace.projects`
case manifestOrder
}
public var projectsOrder: ProjectsOrder
That was then passed through and accessible in WorkspaceStructureGenerator.swift which is where I then tried to apply the workspace defined ordering, as advised by @pepicrft. Unfortunately the ordering has already been scrambled within the Workspace object by the time it is passed to the generateStructure(…) function which means that simply removing the sorted(by: …) from there isn’t enough.
I went digging a bit further in to this and found that we are calling sorted(by: …) elsewhere throughout the process which makes any changes a bit more wide reaching. But after digging a bit more I found that the ordering within the manifest is being lost when applying the graph mapper in ManifestGraphLoader.swift. Before the mappers are applied my ordering looks something like:
(lldb) p graph.workspace.projects.map(.basename)
([String]) 21 values {
[0] = “My App”
[1] = “AFeature”
[2] = “BFeature”
[3] = “CFeature”
[4] = “DFeature”
[5] = “EFeature”
[6] = “FFeature”
[7] = “GFeature”
[8] = “HFeature”
[9] = “SharedData”
[10] = “SharedUI”
[11] = “FeedData”
[12] = “Networking”
[13] = “Tracking”
[14] = “Authentication”
[15] = “FeatureFlagging”
[16] = “Core”
[17] = “Theming”
[18] = “JSONAPI”
[19] = “TestingUtilities”
[20] = “AsyncImageView”
}
But afterwards, and once the dependencies had been added, the ordering looks something like this:
(lldb) p mappedGraph.workspace.projects.map(.basename)
([String]) 50 values {
[0] = “BFeature”
[1] = “Core”
[2] = “SharedData”
[3] = “google-ads-on-device-conversion-ios-sdk”
[4] = “FFeature”
[5] = “promises”
[6] = “PromiseKit”
[7] = “Kingfisher”
[8] = “GFeature”
[9] = “FeedData”
[10] = “combine-schedulers”
[11] = “Authentication”
[12] = “Networking”
[13] = “swift-concurrency-extras”
[14] = “JSONAPI”
[15] = “swift-custom-dump”
[16] = “CFeature”
[17] = “JWTDecode.swift”
[18] = “firebase-ios-sdk”
[19] = “Starscream”
[20] = “swift-navigation”
[21] = “swift-case-paths”
[22] = “swift-clocks”
[23] = “BFeature”
[24] = “SharedUI”
[25] = “swift-syntax”
[26] = “AsyncImageView”
[27] = “DFeature”
[28] = “xctest-dynamic-overlay”
[29] = “swift-collections”
[30] = “FFeature”
[31] = “AFeature”
...
}
So lets say we try to preserve the ordering here (I made a rough and ready modification to ManifestGraphLoader.swift to do this, but it’s still not right) to give us something like:
(lldb) p mappedGraph.workspace.projects.map(.basename)
([String]) 50 values {
[0] = “My App”
[1] = “AFeature”
[2] = “BFeature”
[3] = “CFeature”
[4] = “DFeature”
[5] = “EFeature”
[6] = “FFeature”
[7] = “GFeature”
[8] = “FeedFeature”
[9] = “SharedData”
[10] = “SharedUI”
[11] = “FeedData”
[12] = “Networking”
[13] = “Tracking”
[14] = “Authentication”
[15] = “FeatureFlagging”
[16] = “Core”
[17] = “Theming”
[18] = “JSONAPI”
[19] = “TestingUtilities”
[20] = “AsyncImageView”
[21] = “HFeature”
[22] = “Auth0.swift”
[23] = “GoogleAppMeasurement”
[24] = “GoogleUtilities”
[25] = “JWTDecode.swift”
[26] = “Kingfisher”
[27] = “PromiseKit”
[28] = “SimpleKeychain”
[29] = “Starscream”
[30] = “SwiftyJSON”
[31] = “combine-schedulers”
[32] = “firebase-ios-sdk”
[33] = “google-ads-on-device-conversion-ios-sdk”
...
}
That order gets preserved all of the way down to the WorkspaceDescriptorGenerator.swift at which point it creates a dictionary of generated projects (and also tries to sort the projects so the ordering is lost again) which are then finally passed in to the workspace structure generator.
At this point the ordering of the workspace is lost again as we simply pass on the generated projects and a couple of things out of the workspace. If I make a modification to pass in the [AbsolutePath] array from the workspace for the ordering then I can refer back to it and finally order the projects of the root graph which is returned.
Questions
Now this is a pretty dirty conceptual approach just to prove out what needs to be done here (for my use case it could potentially work with some more tweaks), but I have some concerns. I’m unsure as to why we are sorting in multiple places - it could be performance or logic related, or potentially a code smell which I’m unaware of. But making changes to these places increases blast radius of these changes and I’m unsure if this will have some unintended consequences as I’m not intimately familiar with the project.
I’m open to ideas from any of the regular maintainers as to what should be done here. I’m unsure if there is appetite for such a change, or if making a change like this is opening a can of worms. I don’t have much huge amount of time to dig in much further due to other commitments, but hopefully some healthy discussion can start to move this investigation forwards a little bit.