Skip to content

Instantly share code, notes, and snippets.

@AlexKobachiJP
Last active June 1, 2024 20:26

Revisions

  1. AlexKobachiJP revised this gist Feb 21, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion XcodeCommand
    Original file line number Diff line number Diff line change
    @@ -33,7 +33,7 @@ extension MainCommand.XcodeCommand {
    @Argument(help: "The path to the Xcode project to process.")
    var project: String

    mutating func run() throws {
    func run() throws {
    let path = Path(project)
    let xcodeproj = try XcodeProj(path: path)

  2. AlexKobachiJP revised this gist Feb 21, 2023. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions XcodeCommand
    Original file line number Diff line number Diff line change
    @@ -53,9 +53,12 @@ extension MainCommand.XcodeCommand {
    }

    extension PBXProj {

    /// Sorts the list of packages in the project's Package Dependencies tab alphabetically.
    func sortPackageDependencies() {
    if let packages = rootObject?.packages {
    rootObject?.packages = packages.sorted {
    // Without lower-casing, "SomePackage" will come before "a-package".
    if let lhs = $0.name?.lowercased(), let rhs = $1.name?.lowercased() {
    return lhs < rhs
    }
    @@ -64,10 +67,12 @@ extension PBXProj {
    }
    }

    /// Sorts the list of packages in a Link Binary with Libraries build phase.
    func sortFrameworksBuildPhases() {
    for phase in frameworksBuildPhases {
    if let files = phase.files {
    phase.files = files.sorted {
    // Without lower-casing, "SomePackage" will come before "a-package".
    if let lhs = $0.product?.productName.lowercased(), let rhs = $1.product?.productName.lowercased() {
    return lhs < rhs
    }
  3. AlexKobachiJP created this gist Feb 21, 2023.
    91 changes: 91 additions & 0 deletions XcodeCommand
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,91 @@
    // Copyright © 2023 Alex Kovács. All rights reserved.

    import ArgumentParser
    import Foundation
    import PathKit
    import XcodeProj

    // See below for `MainCommand` skeleton.
    extension MainCommand {
    struct XcodeCommand: ParsableCommand {
    static var configuration = CommandConfiguration(
    commandName: "xcode",
    abstract: "Process Xcode project files.",
    subcommands: [
    SortCommand.self
    ]
    )
    }
    }

    extension MainCommand.XcodeCommand {
    struct SortCommand: ParsableCommand {
    static var configuration = CommandConfiguration(
    commandName: "sort",
    abstract: "Sorts file and package references in the project.",
    discussion: """
    - Files in the navigator are sorted by filename, with groups always appearing first.
    - Files in target build phases are sorted by filename.
    - Packages in the project's package dependency list and in target framework build phases lower-case the name for sorting.
    """
    )

    @Argument(help: "The path to the Xcode project to process.")
    var project: String

    mutating func run() throws {
    let path = Path(project)
    let xcodeproj = try XcodeProj(path: path)

    // Let the framework handle what it does.
    let settings = PBXOutputSettings(
    projNavigatorFileOrder: .byFilenameGroupsFirst,
    projBuildPhaseFileOrder: .byFilename
    )
    try xcodeproj.write(path: path, override: true, outputSettings: settings)

    // Now massage the parts the framework doesn't handle according to our needs and save again.
    xcodeproj.pbxproj.sortPackageDependencies()
    xcodeproj.pbxproj.sortFrameworksBuildPhases()
    try xcodeproj.write(path: path, override: true)
    }
    }
    }

    extension PBXProj {
    func sortPackageDependencies() {
    if let packages = rootObject?.packages {
    rootObject?.packages = packages.sorted {
    if let lhs = $0.name?.lowercased(), let rhs = $1.name?.lowercased() {
    return lhs < rhs
    }
    return true
    }
    }
    }

    func sortFrameworksBuildPhases() {
    for phase in frameworksBuildPhases {
    if let files = phase.files {
    phase.files = files.sorted {
    if let lhs = $0.product?.productName.lowercased(), let rhs = $1.product?.productName.lowercased() {
    return lhs < rhs
    }
    return true
    }
    }
    }
    }
    }

    struct MainCommand: AsyncParsableCommand {
    static var configuration = CommandConfiguration(
    commandName: "...",
    abstract: "....",
    version: "...",
    subcommands: [
    XcodeCommand.self
    ],
    helpNames: [ .short, .long ]
    )
    }