Skip to content

Feature Request: Add ability to provide custom sorting of group children #3131

Open

Description

@jflan-dd

We organize our modules into layers where each layer can only import code from the layer “below” it. In our current generated project file we sort those layer folders to reflect that hierarchy. We'd like to be able to provide custom sorting logic for specific Xcode project groups to communicate that hierarchy to developers in the project structure.

Here's our existing XcodeGen customization:

options:
  groupOrdering:
    - order:
        - ConsumerApp
        - Packages
        - CommonPackages
    - pattern: "Packages"
      order:
        - AppClip
        - AppIntegration
        - LegacyApp
        - Modules
        - Routing
        - DomainInterfaces
        - Services
        - Capabilities
        - Networking
        - Foundation
        - Resources

Activity

  1. jflan-dd commented on Jul 8, 2025

    @jflan-dd
    ContributorAuthor

    @brentleyjones I've been considering a couple of ways to implement this and I'd love your opinion.

    Option 1: Config file based:

    Add a new argument with the location of a config file. The config file would be similar the one from XcodeGen.

    groupOrdering:
      - path: "Some/Project/Path
        order:
          - TopElement
          - MiddleElement
          - BottomElement

    We'd pass the BazelPath into GroupChildElements.init, search the config file for a path that matches the current BazelPath.path, and then apply the ordering from the config file here, falling back to the built in sort if there's no matching path. (ignoring other edge cases for now)

    Option 2: Like custom SwiftLint rules

    Do something like this: https://github.com/jpsim/swiftlint-bazel-example

    Define a element sorting protocol and then have consumers patch in their custom sorters.

    protocol ElementSorter {
        static func canSortGroup(atPath path: String) -> Bool
    
        static func areAscending(_ lhs: String, _ rhs: String) -> Bool
    }
    
    // To be patched by users
    func sorters() -> [any ElementSorter.Type] { [] }
    
    extension GroupChildElements {
        fileprivate init(
            parentBazelPath: BazelPath,
            children: [GroupChild.ElementAndChildren],
            resolvedRepositories: [ResolvedRepository]
        ) {
            // ...
    
            let sorters = sorters()
            if let sorter = sorters.first(where: { $0.canSortGroup(atPath: parentBazelPath.path) }) {
                elements.sort { sorter.areAscending($0.name, $1.name) }
            } else {
                elements.sort()
            }
    
            // ...
        }
    }
  2. jflan-dd commented on Jul 9, 2025

    @jflan-dd
    ContributorAuthor

    I went down the path of patching in a custom sorter, but realized when I was done that rules_xcodeproj is distributed with precompiled binaries. That's great for performance, but not so great for the patch based solution.

  3. brentleyjones commented on Jul 9, 2025

    @brentleyjones
    Contributor

    Yeah, we want to keep the prebuilt binaries. I think we will want to go the route of an aspect hint, to allow for people to write new hints to customize this behavior (since the hint can be given the paths of a target? or something like that), and the output of that will adjust what the Starlark produces to influence how it's ordered/sorted in the generator.

  4. jflan-dd commented on Aug 5, 2025

    @jflan-dd
    ContributorAuthor

    @brentleyjones There are two things I'm not clear on with the aspect hint approach.

    1. What would the aspect hint be attached to? It seems strange to me to attach a hint about the ordering of a group/folder in Xcode to one of the targets within that folder. Can aspect hints be attached to the rules_xcodeproj macro?

    2. How would that information get to the files and groups generator? Would we need to serialize the ordering to a file and then pass it with GeneratorArguments? If it's going to need to be serialized to a file at some point is there an advantage to going through the hint rather than consumers writing the serialized format directly and passing that as a config file?

  5. brentleyjones commented on Aug 6, 2025

    @brentleyjones
    Contributor

    Hmm, yeah, I guess the folder structure is more of a "package" level thing than a target level thing.

    Okay, let's go with a config file that is passed in by the user. YAML works. And structure it in a way that it could deprecate project_options by having that be one of the top-level things alongside groupOrdering.

  6. jflan-dd commented on Aug 7, 2025

    @jflan-dd
    ContributorAuthor

    I know it was my suggestion to use a config file, but does that go against the design goal of having "All definition of a project exists in BUILD files"?

    I could extend project_options instead. It'd have to be serialized to a file at some point to pass into the Swift generator binary, but it would keep the project definition inside the BUILD files.

  7. brentleyjones commented on Aug 7, 2025

    @brentleyjones
    Contributor

    project_options literally means the project-level options. So we wouldn't want to add to that. We could instead add a new property for this.

    And yeah, it would go against that design, so it's not something we should break lightly. I think this sort of thing can be expressed with Starlark data types, so we should try to do that first.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions