From 2be5aabaffaaed0b92a8c2ac13c28d2aff5b267a Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Mon, 23 Jul 2018 21:57:17 +1000 Subject: [PATCH 1/4] sort Projects and Frameworks groups at the end --- Sources/XcodeGenKit/PBXProjGenerator.swift | 19 +++++++++++-------- .../Project.xcodeproj/project.pbxproj | 4 ++-- .../SourceGeneratorTests.swift | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 776a171b..b796d766 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -16,7 +16,7 @@ public class PBXProjGenerator { var targetAggregateObjects: [String: ObjectReference] = [:] var targetBuildFiles: [String: ObjectReference] = [:] var targetFileReferences: [String: String] = [:] - var topLevelGroups: Set = [] + var carthageFrameworksByPlatform: [String: Set] = [:] var frameworkFiles: [String] = [] @@ -80,6 +80,8 @@ public class PBXProjGenerator { ) ) + var derivedGroups: [ObjectReference] = [] + let mainGroup = createObject( id: "Project", PBXGroup( @@ -161,7 +163,7 @@ public class PBXProjGenerator { name: "Products" ) ) - topLevelGroups.insert(productGroup.reference) + derivedGroups.append(productGroup) if !carthageFrameworksByPlatform.isEmpty { var platforms: [PBXGroup] = [] @@ -199,15 +201,16 @@ public class PBXProjGenerator { name: "Frameworks" ) ) - topLevelGroups.insert(group.reference) + derivedGroups.append(group) } - for rootGroup in sourceGenerator.rootGroups { - topLevelGroups.insert(rootGroup) - } - - mainGroup.object.children = Array(topLevelGroups) + mainGroup.object.children = Array(sourceGenerator.rootGroups) sortGroups(group: mainGroup) + // add derived groups at the end + derivedGroups.forEach(sortGroups) + mainGroup.object.children += derivedGroups + .sorted { $0.object.nameOrPath.localizedStandardCompare($1.object.nameOrPath) == .orderedAscending } + .map { $0.reference } let projectAttributes: [String: Any] = ["LastUpgradeCheck": project.xcodeVersion] .merged(project.attributes) diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 22249f1e..3df37908 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -559,10 +559,8 @@ G_8340618952527 /* Configs */, G_3234630030493 /* FileGroup */, G_4661500274312 /* Framework */, - G_1952740716080 /* Frameworks */, G_8268950006174 /* iMessage */, G_1646573205915 /* iMessage MessagesExtension */, - G_8620238527590 /* Products */, G_7189434949822 /* Resources */, G_6651250437419 /* StandaloneFiles */, G_3997550084026 /* StaticLibrary_ObjC */, @@ -571,6 +569,8 @@ FR_232605427418 /* Mintfile */, FR_257073931060 /* ResourceFolder */, FR_775316160345 /* SomeFile */, + G_1952740716080 /* Frameworks */, + G_8620238527590 /* Products */, ); indentWidth = 2; sourceTree = ""; diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 106f2f0d..a1d7b7d8 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -459,6 +459,25 @@ class SourceGeneratorTests: XCTestCase { try expect(sourcesBuildPhase.files.count) == 1 } + + $0.it("derived directories are sorted last") { + let directories = """ + A: + - file.swift + P: + - file.swift + S: + - file.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["A", "P", "S"], dependencies: [Dependency(type: .carthage, reference: "Alamofire")]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + let groups = try pbxProj.getMainGroup().children.compactMap { pbxProj.objects.getFileElement(reference: $0)?.nameOrPath } + try expect(groups) == ["A", "P", "S", "Frameworks", "Products"] + } } } } From e915fdf4212d0038685e004685e70393e4003e15 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Mon, 23 Jul 2018 22:17:22 +1000 Subject: [PATCH 2/4] add sorting test --- .../SourceGeneratorTests.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index a1d7b7d8..e4caa4f7 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -478,6 +478,38 @@ class SourceGeneratorTests: XCTestCase { let groups = try pbxProj.getMainGroup().children.compactMap { pbxProj.objects.getFileElement(reference: $0)?.nameOrPath } try expect(groups) == ["A", "P", "S", "Frameworks", "Products"] } + + $0.it("sorts files") { + let directories = """ + Sources: + - file3.swift + - file.swift + - 10file.a + - 1file.a + - file2.swift + - group2: + - file.swift + - group: + - file.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"], dependencies: [Dependency(type: .carthage, reference: "Alamofire")]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + + let pbxProj = try project.generatePbxProj() + let group = pbxProj.objects.group(named: "Sources", inGroup: try pbxProj.getMainGroup())!.object + let names = group.children.compactMap { pbxProj.objects.getFileElement(reference: $0)?.nameOrPath } + try expect(names) == [ + "group", + "group2", + "1file.a", + "10file.a", + "file.swift", + "file2.swift", + "file3.swift", + ] + } } } } From 91d2177f841c18adbc2f253d2a5a7ebdb36edb7e Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Tue, 24 Jul 2018 22:45:52 +1000 Subject: [PATCH 3/4] sort groups under files --- Sources/XcodeGenKit/XCProjExtensions.swift | 4 ++-- .../TestProject/Project.xcodeproj/project.pbxproj | 10 +++++----- Tests/XcodeGenKitTests/SourceGeneratorTests.swift | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/XcodeGenKit/XCProjExtensions.swift b/Sources/XcodeGenKit/XCProjExtensions.swift index 62b845e4..22440387 100644 --- a/Sources/XcodeGenKit/XCProjExtensions.swift +++ b/Sources/XcodeGenKit/XCProjExtensions.swift @@ -9,9 +9,9 @@ extension PBXFileElement { public var sortOrder: Int { if type(of: self).isa == "PBXGroup" { - return 0 - } else { return 1 + } else { + return 0 } } } diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 3df37908..e079486c 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -551,6 +551,11 @@ G_8448771205358 = { isa = PBXGroup; children = ( + FR_479281060337 /* Folder */, + FR_815403394914 /* Headers */, + FR_232605427418 /* Mintfile */, + FR_257073931060 /* ResourceFolder */, + FR_775316160345 /* SomeFile */, G_8252321105004 /* App */, G_7831228999101 /* App_iOS_Tests */, G_1235039993875 /* App_iOS_UITests */, @@ -564,11 +569,6 @@ G_7189434949822 /* Resources */, G_6651250437419 /* StandaloneFiles */, G_3997550084026 /* StaticLibrary_ObjC */, - FR_479281060337 /* Folder */, - FR_815403394914 /* Headers */, - FR_232605427418 /* Mintfile */, - FR_257073931060 /* ResourceFolder */, - FR_775316160345 /* SomeFile */, G_1952740716080 /* Frameworks */, G_8620238527590 /* Products */, ); diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index e4caa4f7..41c22bc4 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -501,13 +501,13 @@ class SourceGeneratorTests: XCTestCase { let group = pbxProj.objects.group(named: "Sources", inGroup: try pbxProj.getMainGroup())!.object let names = group.children.compactMap { pbxProj.objects.getFileElement(reference: $0)?.nameOrPath } try expect(names) == [ - "group", - "group2", "1file.a", "10file.a", "file.swift", "file2.swift", "file3.swift", + "group", + "group2", ] } } From f51280485d7f836cefa99deb7136d407b5bad77c Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Tue, 24 Jul 2018 22:59:24 +1000 Subject: [PATCH 4/4] added options.groupSortPosition --- Docs/ProjectSpec.md | 4 ++++ Sources/ProjectSpec/SpecOptions.swift | 16 +++++++++++++- Sources/XcodeGenKit/PBXProjGenerator.swift | 22 +++++++++++++++++-- Sources/XcodeGenKit/XCProjExtensions.swift | 8 ------- .../Project.xcodeproj/project.pbxproj | 10 ++++----- Tests/Fixtures/TestProject/project.yml | 1 + 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 6f4210ad..10b712d9 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -87,6 +87,10 @@ Note that target names can also be changed by adding a `name` property to a targ - [ ] **disabledValidations**: **[String]** - A list of validations that can be disabled if they're too strict for your use case. By default this is set to an empty array. Currently these are the available options: - `missingConfigs`: Disable errors for configurations in yaml files that don't exist in the project itself. This can be useful if you include the same yaml file in different projects - [ ] **defaultConfig**: **String** - The default configuration for command line builds from Xcode. If the configuration provided here doesn't match one in your [configs](#configs) key, XcodeGen will fail. If you don't set this, the first configuration alphabetically will be chosen. +- [ ] **groupSortPosition**: **String** - Where groups are sorted in relation to other files. Either: + - `top` - at the top, before files + - `bottom` - at the bottom, after other files + - `none` - sorted alphabetically with all the other files - [ ] **transitivelyLinkDependencies**: **Bool** - If this is `true` then targets will link to the dependencies of their target dependencies. If a target should embed its dependencies, such as application and test bundles, it will embed these transitive dependencies as well. Some complex setups might want to set this to `false` and explicitly specify dependencies at every level. Targets can override this with [Target](#target).transitivelyLinkDependencies. Defaults to `false`. ```yaml diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift index f476575a..cd7bbf39 100644 --- a/Sources/ProjectSpec/SpecOptions.swift +++ b/Sources/ProjectSpec/SpecOptions.swift @@ -18,6 +18,7 @@ public struct SpecOptions: Equatable { public var deploymentTarget: DeploymentTarget public var defaultConfig: String? public var transitivelyLinkDependencies: Bool + public var groupSortPosition: GroupSortPosition public enum ValidationType: String { case missingConfigs @@ -44,6 +45,16 @@ public struct SpecOptions: Equatable { } } + /// Where groups are sorted in relation to other files + public enum GroupSortPosition: String { + /// groups are at the top + case top + /// groups are at the bottom + case bottom + /// groups are sorted with the rest of the files + case none + } + public init( carthageBuildPath: String? = nil, carthageExecutablePath: String? = nil, @@ -58,7 +69,8 @@ public struct SpecOptions: Equatable { deploymentTarget: DeploymentTarget = .init(), disabledValidations: [ValidationType] = [], defaultConfig: String? = nil, - transitivelyLinkDependencies: Bool = false + transitivelyLinkDependencies: Bool = false, + groupSortPosition: GroupSortPosition = .bottom ) { self.carthageBuildPath = carthageBuildPath self.carthageExecutablePath = carthageExecutablePath @@ -74,6 +86,7 @@ public struct SpecOptions: Equatable { self.disabledValidations = disabledValidations self.defaultConfig = defaultConfig self.transitivelyLinkDependencies = transitivelyLinkDependencies + self.groupSortPosition = groupSortPosition } } @@ -94,5 +107,6 @@ extension SpecOptions: JSONObjectConvertible { disabledValidations = jsonDictionary.json(atKeyPath: "disabledValidations") ?? [] defaultConfig = jsonDictionary.json(atKeyPath: "defaultConfig") transitivelyLinkDependencies = jsonDictionary.json(atKeyPath: "transitivelyLinkDependencies") ?? false + groupSortPosition = jsonDictionary.json(atKeyPath: "groupSortPosition") ?? .bottom } } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index b796d766..e6f44f52 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -395,10 +395,13 @@ public class PBXProjGenerator { return ObjectReference(reference: reference, object: fileElement) } .sorted { child1, child2 in - if child1.object.sortOrder == child2.object.sortOrder { + let sortOrder1 = child1.object.getSortOrder(groupSortPosition: project.options.groupSortPosition) + let sortOrder2 = child2.object.getSortOrder(groupSortPosition: project.options.groupSortPosition) + + if sortOrder1 == sortOrder2 { return child1.object.nameOrPath.localizedStandardCompare(child2.object.nameOrPath) == .orderedAscending } else { - return child1.object.sortOrder < child2.object.sortOrder + return sortOrder1 < sortOrder2 } } group.object.children = children.map { $0.reference }.filter { $0 != group.reference } @@ -875,3 +878,18 @@ extension Target { return type.isApp || type.isTest } } + +extension PBXFileElement { + + public func getSortOrder(groupSortPosition: SpecOptions.GroupSortPosition) -> Int { + if type(of: self).isa == "PBXGroup" { + switch groupSortPosition { + case .top: return -1 + case .bottom: return 1 + case .none: return 0 + } + } else { + return 0 + } + } +} diff --git a/Sources/XcodeGenKit/XCProjExtensions.swift b/Sources/XcodeGenKit/XCProjExtensions.swift index 22440387..edf9fe5e 100644 --- a/Sources/XcodeGenKit/XCProjExtensions.swift +++ b/Sources/XcodeGenKit/XCProjExtensions.swift @@ -6,14 +6,6 @@ extension PBXFileElement { public var nameOrPath: String { return name ?? path ?? "" } - - public var sortOrder: Int { - if type(of: self).isa == "PBXGroup" { - return 1 - } else { - return 0 - } - } } extension PBXProj { diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index e079486c..3df37908 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -551,11 +551,6 @@ G_8448771205358 = { isa = PBXGroup; children = ( - FR_479281060337 /* Folder */, - FR_815403394914 /* Headers */, - FR_232605427418 /* Mintfile */, - FR_257073931060 /* ResourceFolder */, - FR_775316160345 /* SomeFile */, G_8252321105004 /* App */, G_7831228999101 /* App_iOS_Tests */, G_1235039993875 /* App_iOS_UITests */, @@ -569,6 +564,11 @@ G_7189434949822 /* Resources */, G_6651250437419 /* StandaloneFiles */, G_3997550084026 /* StaticLibrary_ObjC */, + FR_479281060337 /* Folder */, + FR_815403394914 /* Headers */, + FR_232605427418 /* Mintfile */, + FR_257073931060 /* ResourceFolder */, + FR_775316160345 /* SomeFile */, G_1952740716080 /* Frameworks */, G_8620238527590 /* Products */, ); diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml index f37b4f8c..bd85c939 100644 --- a/Tests/Fixtures/TestProject/project.yml +++ b/Tests/Fixtures/TestProject/project.yml @@ -8,6 +8,7 @@ options: transitivelyLinkDependencies: true deploymentTarget: watchOS: 4.0 + groupSortPosition: top fileGroups: - Configs - FileGroup