From 15e615300efd2fc6902e7e65e8a489da55133cca Mon Sep 17 00:00:00 2001 From: Steven Roebert Date: Mon, 22 Jul 2019 13:37:51 +0200 Subject: [PATCH 1/4] Added new group parameter to target sources, allowing to add them to a custom parent group (resolving #478) --- Docs/ProjectSpec.md | 1 + Sources/ProjectSpec/TargetSource.swift | 5 + Sources/XcodeGenKit/SourceGenerator.swift | 109 ++++++++++++++---- .../SourceGeneratorTests.swift | 46 +++++++- 4 files changed, 134 insertions(+), 27 deletions(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index b265edfc..fa8ccc6f 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -309,6 +309,7 @@ A source can be provided via a string (the path) or an object of the form: - [x] **path**: **String** - The path to the source file or directory. - [ ] **name**: **String** - Can be used to override the name of the source file or directory. By default the last component of the path is used for the name +- [ ] **group**: **String** - Can be used to override the parent group of the source file or directory. By default a group is created at the root with the name of this source file or directory or intermediate groups are created if `createIntermediateGroups` is set to `true`. Multiple groups can be created by separating each one using a `/`. If multiple target sources share the same `group`, they will be put together in the same parent group. - [ ] **compilerFlags**: **[String]** or **String** - A list of compilerFlags to add to files under this specific path provided as a list or a space delimitted string. Defaults to empty. - [ ] **excludes**: **[String]** - A list of [global patterns](https://en.wikipedia.org/wiki/Glob_(programming)) representing the files to exclude. These rules are relative to `path` and _not the directory where `project.yml` resides_. XcodeGen uses Bash 4's Glob behaviors where globstar (**) is enabled. - [ ] **includes**: **[String]** - A list of global patterns in the same format as `excludes` representing the files to include. These rules are relative to `path` and _not the directory where `project.yml` resides_. If **excludes** is present and file conflicts with **includes**, **excludes** will override the **includes** behavior. diff --git a/Sources/ProjectSpec/TargetSource.swift b/Sources/ProjectSpec/TargetSource.swift index 60d3f669..0a3cdbfe 100644 --- a/Sources/ProjectSpec/TargetSource.swift +++ b/Sources/ProjectSpec/TargetSource.swift @@ -9,6 +9,7 @@ public struct TargetSource: Equatable { public var path: String public var name: String? + public var group: String? public var compilerFlags: [String] public var excludes: [String] public var includes: [String] @@ -124,6 +125,7 @@ public struct TargetSource: Equatable { public init( path: String, name: String? = nil, + group: String? = nil, compilerFlags: [String] = [], excludes: [String] = [], includes: [String] = [], @@ -136,6 +138,7 @@ public struct TargetSource: Equatable { ) { self.path = path self.name = name + self.group = group self.compilerFlags = compilerFlags self.excludes = excludes self.includes = includes @@ -168,6 +171,7 @@ extension TargetSource: JSONObjectConvertible { public init(jsonDictionary: JSONDictionary) throws { path = try jsonDictionary.json(atKeyPath: "path") name = jsonDictionary.json(atKeyPath: "name") + group = jsonDictionary.json(atKeyPath: "group") let maybeCompilerFlagsString: String? = jsonDictionary.json(atKeyPath: "compilerFlags") let maybeCompilerFlagsArray: [String]? = jsonDictionary.json(atKeyPath: "compilerFlags") @@ -198,6 +202,7 @@ extension TargetSource: JSONEncodable { "excludes": excludes, "includes": includes, "name": name, + "group": group, "headerVisibility": headerVisibility?.rawValue, "type": type?.rawValue, "buildPhase": buildPhase?.toJSONValue(), diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index b7659d2e..468a1039 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -143,6 +143,7 @@ class SourceGenerator { path: parentPath, mergingChildren: [fileReference], createIntermediateGroups: createIntermediateGroups, + hasCustomParent: false, isBaseGroup: true ) @@ -270,7 +271,7 @@ class SourceGenerator { /// Create a group or return an existing one at the path. /// Any merged children are added to a new group or merged into an existing one. - private func getGroup(path: Path, name: String? = nil, mergingChildren children: [PBXFileElement], createIntermediateGroups: Bool, isBaseGroup: Bool) -> PBXGroup { + private func getGroup(path: Path, name: String? = nil, mergingChildren children: [PBXFileElement], createIntermediateGroups: Bool, hasCustomParent: Bool, isBaseGroup: Bool) -> PBXGroup { let groupReference: PBXGroup if let cachedGroup = groupsByPath[path] { @@ -293,10 +294,11 @@ class SourceGenerator { let isRootPath = (isBaseGroup && isOutOfBasePath) || path.parent() == project.basePath // is a top level group in the project - let isTopLevelGroup = (isBaseGroup && !createIntermediateGroups) || isRootPath + let isTopLevelGroup = !hasCustomParent && ((isBaseGroup && !createIntermediateGroups) || isRootPath) let groupName = name ?? path.lastComponent - let groupPath = resolveGroupPath(path, isTopLevelGroup: isTopLevelGroup) + + let groupPath = resolveGroupPath(path, isTopLevelGroup: hasCustomParent || isTopLevelGroup) let group = PBXGroup( children: children, @@ -387,8 +389,16 @@ class SourceGenerator { } /// creates all the source files and groups they belong to for a given targetSource - private func getGroupSources(targetType: PBXProductType, targetSource: TargetSource, path: Path, isBaseGroup: Bool, excludePaths: Set, includePaths: Set) - throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) { + private func getGroupSources( + targetType: PBXProductType, + targetSource: TargetSource, + path: Path, + isBaseGroup: Bool, + createIntermediateGroups: Bool, + hasCustomParent: Bool, + excludePaths: Set, + includePaths: Set + ) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) { let children = try getSourceChildren(targetSource: targetSource, dirPath: path, excludePaths: excludePaths, includePaths: includePaths) @@ -408,12 +418,17 @@ class SourceGenerator { var groups: [PBXGroup] = [] for path in directories { - let subGroups = try getGroupSources(targetType: targetType, - targetSource: targetSource, - path: path, - isBaseGroup: false, - excludePaths: excludePaths, - includePaths: includePaths) + + let subGroups = try getGroupSources( + targetType: targetType, + targetSource: targetSource, + path: path, + isBaseGroup: false, + createIntermediateGroups: createIntermediateGroups, + hasCustomParent: false, + excludePaths: excludePaths, + includePaths: includePaths + ) guard !subGroups.sourceFiles.isEmpty || project.options.generateEmptyDirectories else { continue @@ -499,12 +514,11 @@ class SourceGenerator { } } - let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups - let group = getGroup( path: path, mergingChildren: groupChildren, createIntermediateGroups: createIntermediateGroups, + hasCustomParent: hasCustomParent, isBaseGroup: isBaseGroup ) if createIntermediateGroups { @@ -524,6 +538,10 @@ class SourceGenerator { let includePaths = getSourceMatches(targetSource: targetSource, patterns: targetSource.includes) let type = targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group) + + let customParentGroups = (targetSource.group ?? "").split(separator: "/") + let hasCustomParent = !customParentGroups.isEmpty + let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups var sourceFiles: [SourceFile] = [] @@ -540,7 +558,7 @@ class SourceGenerator { lastKnownFileType: "folder" ) - if !createIntermediateGroups || path.parent() == project.basePath { + if !(createIntermediateGroups || hasCustomParent) || path.parent() == project.basePath { rootGroups.insert(fileReference) } @@ -566,7 +584,13 @@ class SourceGenerator { sourceReference = fileReference rootGroups.insert(fileReference) } else { - let parentGroup = getGroup(path: parentPath, mergingChildren: [fileReference], createIntermediateGroups: createIntermediateGroups, isBaseGroup: true) + let parentGroup = getGroup( + path: parentPath, + mergingChildren: [fileReference], + createIntermediateGroups: createIntermediateGroups, + hasCustomParent: hasCustomParent, + isBaseGroup: true + ) sourcePath = parentPath sourceReference = parentGroup } @@ -577,12 +601,18 @@ class SourceGenerator { // This group is missing, so if's optional just return an empty array return [] } - let (groupSourceFiles, groups) = try getGroupSources(targetType: targetType, - targetSource: targetSource, - path: path, - isBaseGroup: true, - excludePaths: excludePaths, - includePaths: includePaths) + + let (groupSourceFiles, groups) = try getGroupSources( + targetType: targetType, + targetSource: targetSource, + path: path, + isBaseGroup: true, + createIntermediateGroups: createIntermediateGroups, + hasCustomParent: hasCustomParent, + excludePaths: excludePaths, + includePaths: includePaths + ) + let group = groups.first! if let name = targetSource.name { group.name = name @@ -592,13 +622,40 @@ class SourceGenerator { sourceReference = group } - if createIntermediateGroups { + if hasCustomParent { + createParentGroups(customParentGroups, for: sourceReference) + } else if createIntermediateGroups { createIntermediaGroups(for: sourceReference, at: sourcePath) } return sourceFiles } + private func createParentGroups(_ parentGroups: [String.SubSequence], for fileElement: PBXFileElement) { + guard let parentName = parentGroups.last else { + return + } + + let parentPath = project.basePath + Path(parentGroups.joined(separator: "/")) + + let hasParentGroup = groupsByPath[parentPath] != nil + let parentGroup = getGroup( + path: parentPath, + mergingChildren: [fileElement], + createIntermediateGroups: false, + hasCustomParent: false, + isBaseGroup: false + ) + + // As this path is a custom group, remove the path reference + parentGroup.name = String(parentName) + parentGroup.path = nil + + if !hasParentGroup { + createParentGroups(parentGroups.dropLast(), for: parentGroup) + } + } + // Add groups for all parents recursively private func createIntermediaGroups(for fileElement: PBXFileElement, at path: Path) { @@ -609,7 +666,13 @@ class SourceGenerator { } let hasParentGroup = groupsByPath[parentPath] != nil - let parentGroup = getGroup(path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: true, isBaseGroup: false) + let parentGroup = getGroup( + path: parentPath, + mergingChildren: [fileElement], + createIntermediateGroups: true, + hasCustomParent: false, + isBaseGroup: false + ) if !hasParentGroup { createIntermediaGroups(for: parentGroup, at: parentPath) diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 1e903ab3..3f2e587e 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -469,6 +469,38 @@ class SourceGeneratorTests: XCTestCase { try pbxProj.expectFile(paths: ["../OtherDirectory/C/D", "e.swift"], names: ["D", "e.swift"], buildPhase: .sources) try pbxProj.expectFile(paths: ["Sources/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources) } + + $0.it("generates custom groups") { + + let directories = """ + Sources: + A: + - b.swift + F: + - G: + - h.swift + B: + - b.swift + - C: + - c.swift + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + TargetSource(path: "Sources/A/b.swift", group: "CustomGroup1"), + TargetSource(path: "Sources/F/G/h.swift", group: "CustomGroup1"), + TargetSource(path: "Sources/B", group: "CustomGroup2", createIntermediateGroups: false), + ]) + + let options = SpecOptions(createIntermediateGroups: true) + let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options) + + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/A", "b.swift"], names: ["CustomGroup1", "A", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/F/G", "h.swift"], names: ["CustomGroup1", "G", "h.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup2", "Sources/B", "b.swift"], names: ["CustomGroup2", "B", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup2", "Sources/B", "C", "c.swift"], names: ["CustomGroup2", "B", "C", "c.swift"], buildPhase: .sources) + } $0.it("generates folder references") { let directories = """ @@ -953,18 +985,24 @@ extension PBXProj { } private func getFileReference(group: PBXGroup, paths: [String], names: [String]) -> PBXFileReference? { - - guard !paths.isEmpty else { return nil } + guard !paths.isEmpty else { + return nil + } + let path = paths.first! let name = names.first! let restOfPath = Array(paths.dropFirst()) let restOfName = Array(names.dropFirst()) if restOfPath.isEmpty { let fileReferences: [PBXFileReference] = group.children.compactMap { $0 as? PBXFileReference } - return fileReferences.first { $0.path == path && $0.nameOrPath == name } + fileReferences.forEach { print("path: \($0.path ?? "nil"), name: \($0.name ?? "nil")") } + return fileReferences.first { ($0.path == nil || $0.path == path) && $0.nameOrPath == name } } else { let groups = group.children.compactMap { $0 as? PBXGroup } - guard let group = groups.first(where: { $0.path == path && $0.nameOrPath == name }) else { return nil } + groups.forEach { print("path: \($0.path ?? "nil"), name: \($0.name ?? "nil")") } + guard let group = groups.first(where: { ($0.path == nil || $0.path == path) && $0.nameOrPath == name }) else { + return nil + } return getFileReference(group: group, paths: restOfPath, names: restOfName) } } From 82c86c1fbc1e888873d4fe951f61ab1213bcfb02 Mon Sep 17 00:00:00 2001 From: Steven Roebert Date: Mon, 22 Jul 2019 13:45:46 +0200 Subject: [PATCH 2/4] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad0bdec5..3dfe4822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add Carthage static framework dependencies support. [#688](https://github.com/yonaskolb/XcodeGen/pull/688) @giginet - Added `xcodegen dump` command [#710](https://github.com/yonaskolb/XcodeGen/pull/710) @yonaskolb - Added `--no-env` option to disable environment variables expansion [#704](https://github.com/yonaskolb/XcodeGen/pull/704) @rcari +- Added custom group support for target sources [#621](https://github.com/yonaskolb/XcodeGen/pull/621) @sroebert @rcari #### Fixed - Improved variable expansion runtime [#704](https://github.com/yonaskolb/XcodeGen/pull/704) @rcari From de9c15ea1ddf96298b649db56a12a3d8a6b7c280 Mon Sep 17 00:00:00 2001 From: Romuald CARI Date: Wed, 7 Aug 2019 11:59:37 -0400 Subject: [PATCH 3/4] Fix @sroebert PR based on @yonaskolb comments --- Sources/XcodeGenKit/SourceGenerator.swift | 6 ++++-- Tests/XcodeGenKitTests/SourceGeneratorTests.swift | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 468a1039..932a0d4c 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -402,6 +402,8 @@ class SourceGenerator { let children = try getSourceChildren(targetSource: targetSource, dirPath: path, excludePaths: excludePaths, includePaths: includePaths) + let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups + let directories = children .filter { $0.isDirectory && $0.extension == nil && $0.extension != "lproj" } @@ -539,7 +541,7 @@ class SourceGenerator { let type = targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group) - let customParentGroups = (targetSource.group ?? "").split(separator: "/") + let customParentGroups = (targetSource.group ?? "").split(separator: "/").map{ String($0) } let hasCustomParent = !customParentGroups.isEmpty let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups @@ -631,7 +633,7 @@ class SourceGenerator { return sourceFiles } - private func createParentGroups(_ parentGroups: [String.SubSequence], for fileElement: PBXFileElement) { + private func createParentGroups(_ parentGroups: [String], for fileElement: PBXFileElement) { guard let parentName = parentGroups.last else { return } diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 3f2e587e..76af3b62 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -995,11 +995,9 @@ extension PBXProj { let restOfName = Array(names.dropFirst()) if restOfPath.isEmpty { let fileReferences: [PBXFileReference] = group.children.compactMap { $0 as? PBXFileReference } - fileReferences.forEach { print("path: \($0.path ?? "nil"), name: \($0.name ?? "nil")") } return fileReferences.first { ($0.path == nil || $0.path == path) && $0.nameOrPath == name } } else { let groups = group.children.compactMap { $0 as? PBXGroup } - groups.forEach { print("path: \($0.path ?? "nil"), name: \($0.name ?? "nil")") } guard let group = groups.first(where: { ($0.path == nil || $0.path == path) && $0.nameOrPath == name }) else { return nil } From 0df37029cce2f14264e5852a7055c5dead855ce4 Mon Sep 17 00:00:00 2001 From: Romuald CARI Date: Wed, 7 Aug 2019 12:03:44 -0400 Subject: [PATCH 4/4] Fix TargetSource group behavior When targeting a file, a folder should not be created for its directory, only the file should appear in the specified custom group. Moreover, if the custom groups contains actual filesystem folders, they should map to these. --- Sources/XcodeGenKit/SourceGenerator.swift | 49 +++++++++++++++---- .../SourceGeneratorTests.swift | 30 +++++++----- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 932a0d4c..3a409b53 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -281,6 +281,7 @@ class SourceGenerator { // Check equality by path and sourceTree because XcodeProj.PBXObject.== is very slow. if !cachedGroupChildren.contains(where: { $0.name == child.name && $0.path == child.path && $0.sourceTree == child.sourceTree }) { cachedGroupChildren.append(child) + child.parent = cachedGroup } } cachedGroup.children = cachedGroupChildren @@ -394,7 +395,6 @@ class SourceGenerator { targetSource: TargetSource, path: Path, isBaseGroup: Bool, - createIntermediateGroups: Bool, hasCustomParent: Bool, excludePaths: Set, includePaths: Set @@ -426,7 +426,6 @@ class SourceGenerator { targetSource: targetSource, path: path, isBaseGroup: false, - createIntermediateGroups: createIntermediateGroups, hasCustomParent: false, excludePaths: excludePaths, includePaths: includePaths @@ -581,7 +580,10 @@ class SourceGenerator { let sourceFile = generateSourceFile(targetType: targetType, targetSource: targetSource, path: path) - if parentPath == project.basePath { + if hasCustomParent { + sourcePath = path + sourceReference = fileReference + } else if parentPath == project.basePath { sourcePath = path sourceReference = fileReference rootGroups.insert(fileReference) @@ -609,7 +611,6 @@ class SourceGenerator { targetSource: targetSource, path: path, isBaseGroup: true, - createIntermediateGroups: createIntermediateGroups, hasCustomParent: hasCustomParent, excludePaths: excludePaths, includePaths: includePaths @@ -626,6 +627,7 @@ class SourceGenerator { if hasCustomParent { createParentGroups(customParentGroups, for: sourceReference) + try makePathRelative(for: sourceReference, at: path) } else if createIntermediateGroups { createIntermediaGroups(for: sourceReference, at: sourcePath) } @@ -639,21 +641,24 @@ class SourceGenerator { } let parentPath = project.basePath + Path(parentGroups.joined(separator: "/")) + let parentPathExists = parentPath.exists + let parentGroupAlreadyExists = groupsByPath[parentPath] != nil - let hasParentGroup = groupsByPath[parentPath] != nil let parentGroup = getGroup( path: parentPath, mergingChildren: [fileElement], createIntermediateGroups: false, hasCustomParent: false, - isBaseGroup: false + isBaseGroup: parentGroups.count == 1 ) // As this path is a custom group, remove the path reference - parentGroup.name = String(parentName) - parentGroup.path = nil + if !parentPathExists { + parentGroup.name = String(parentName) + parentGroup.path = nil + } - if !hasParentGroup { + if !parentGroupAlreadyExists { createParentGroups(parentGroups.dropLast(), for: parentGroup) } } @@ -681,6 +686,32 @@ class SourceGenerator { } } + // Make the fileElement path and name relative to its parents aggregated paths + private func makePathRelative(for fileElement: PBXFileElement, at path: Path) throws { + // This makes the fileElement path relative to its parent and not to the project. Xcode then rebuilds the actual + // path for the file based on the hierarchy this fileElement lives in. + var paths: [String] = [] + var element: PBXFileElement = fileElement + while true { + guard let parent = element.parent else { break } + + if let path = parent.path { + paths.insert(path, at: 0) + } + + element = parent + } + + let completePath = project.basePath + Path(paths.joined(separator: "/")) + let relativePath = try path.relativePath(from: completePath) + let relativePathString = relativePath.string + + if relativePathString != fileElement.path { + fileElement.path = relativePathString + fileElement.name = relativePath.lastComponent + } + } + private func findCurrentCoreDataModelVersionPath(using versionedModels: [Path]) -> Path? { // Find and parse the current version model stored in the .xccurrentversion file guard diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 76af3b62..49458675 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -469,35 +469,41 @@ class SourceGeneratorTests: XCTestCase { try pbxProj.expectFile(paths: ["../OtherDirectory/C/D", "e.swift"], names: ["D", "e.swift"], buildPhase: .sources) try pbxProj.expectFile(paths: ["Sources/B", "b.swift"], names: ["B", "b.swift"], buildPhase: .sources) } - + $0.it("generates custom groups") { - + let directories = """ - Sources: - A: + - Sources: + - a.swift + - A: - b.swift - F: + - F: - G: - h.swift - B: + - i.swift + - B: - b.swift - C: - c.swift """ try createDirectories(directories) - + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [ + TargetSource(path: "Sources/a.swift", group: "CustomGroup1"), TargetSource(path: "Sources/A/b.swift", group: "CustomGroup1"), TargetSource(path: "Sources/F/G/h.swift", group: "CustomGroup1"), TargetSource(path: "Sources/B", group: "CustomGroup2", createIntermediateGroups: false), + TargetSource(path: "Sources/F/G/i.swift", group: "Sources/F/G/CustomGroup3"), ]) - + let options = SpecOptions(createIntermediateGroups: true) let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options) - + let pbxProj = try project.generatePbxProj() - try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/A", "b.swift"], names: ["CustomGroup1", "A", "b.swift"], buildPhase: .sources) - try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/F/G", "h.swift"], names: ["CustomGroup1", "G", "h.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/a.swift"], names: ["CustomGroup1", "a.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/A/b.swift"], names: ["CustomGroup1", "b.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["CustomGroup1", "Sources/F/G/h.swift"], names: ["CustomGroup1", "h.swift"], buildPhase: .sources) + try pbxProj.expectFile(paths: ["Sources", "F", "G", "CustomGroup3", "i.swift"], names: ["Sources", "F", "G", "CustomGroup3", "i.swift"], buildPhase: .sources) try pbxProj.expectFile(paths: ["CustomGroup2", "Sources/B", "b.swift"], names: ["CustomGroup2", "B", "b.swift"], buildPhase: .sources) try pbxProj.expectFile(paths: ["CustomGroup2", "Sources/B", "C", "c.swift"], names: ["CustomGroup2", "B", "C", "c.swift"], buildPhase: .sources) } @@ -988,7 +994,7 @@ extension PBXProj { guard !paths.isEmpty else { return nil } - + let path = paths.first! let name = names.first! let restOfPath = Array(paths.dropFirst())