Skip to content

Instantly share code, notes, and snippets.

@AlexKobachiJP
Last active June 1, 2024 20:26
Show Gist options
  • Save AlexKobachiJP/7b6737fdb2ae3c8cb751f0f7a45404cd to your computer and use it in GitHub Desktop.
Save AlexKobachiJP/7b6737fdb2ae3c8cb751f0f7a45404cd to your computer and use it in GitHub Desktop.
Sort file and package references in an Xcode project.
// 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
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 {
/// 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
}
return true
}
}
}
/// 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
}
return true
}
}
}
}
}
struct MainCommand: AsyncParsableCommand {
static var configuration = CommandConfiguration(
commandName: "...",
abstract: "....",
version: "...",
subcommands: [
XcodeCommand.self
],
helpNames: [ .short, .long ]
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment