From e48045da9e6600df50f6291d02ea4cd78a7bdaa8 Mon Sep 17 00:00:00 2001 From: Brandon Kase Date: Wed, 25 Oct 2017 15:30:06 -0700 Subject: [PATCH] Optionally make intermediate filler groups This commit adds a new option `createIntermediateGroups` that defaults to false. When it is false, the behavior of XcodeGen is the same as before. When it is true, we make intermediate groups recursively until we reach the basePath. In practice that means if you've chosen `Platform/PINFoundation/Sources` as one of your sourcePaths, you get a top-level group of `Platform` and under that `PINFoundation` and under that `Sources`. This is instead of the default behavior of just making `Sources` a top-level group (which is confusing when your directory is called `Sources` for example). --- .../TestProject/NestedFiles/Foo/Nested.swift | 3 + .../Project.xcodeproj/project.pbxproj | 12 ++++ Sources/ProjectSpec/ProjectSpec.swift | 3 + Sources/XcodeGenKit/PBXProjGenerator.swift | 60 ++++++++++++++++--- docs/ProjectSpec.md | 1 + 5 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 Fixtures/TestProject/NestedFiles/Foo/Nested.swift diff --git a/Fixtures/TestProject/NestedFiles/Foo/Nested.swift b/Fixtures/TestProject/NestedFiles/Foo/Nested.swift new file mode 100644 index 00000000..b5c22056 --- /dev/null +++ b/Fixtures/TestProject/NestedFiles/Foo/Nested.swift @@ -0,0 +1,3 @@ +func nested() -> String { + return "Nested" +} diff --git a/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 3af1b493..2a3c370c 100644 --- a/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -165,6 +165,15 @@ FR7078510801 /* FrameworkFile.swift */, FR1345298503 /* Info.plist */, FR7740960501 /* MyFramework.h */, + FR7078510801 /* FrameworkFile.swift */, + FR1345298503 /* Info.plist */, + FR7740960501 /* MyFramework.h */, + FR7078510801 /* FrameworkFile.swift */, + FR1345298503 /* Info.plist */, + FR7740960501 /* MyFramework.h */, + FR7078510801 /* FrameworkFile.swift */, + FR1345298503 /* Info.plist */, + FR7740960501 /* MyFramework.h */, ); name = Framework; path = Framework; @@ -258,6 +267,9 @@ G82523211001 /* App_iOS */, G78312289901 /* App_iOS_Tests */, G46615002701 /* Framework */, + G46615002701 /* Framework */, + G46615002701 /* Framework */, + G46615002701 /* Framework */, G86202385201 /* Products */, G19527407101 /* Frameworks */, ); diff --git a/Sources/ProjectSpec/ProjectSpec.swift b/Sources/ProjectSpec/ProjectSpec.swift index 1cf01830..ad69f678 100644 --- a/Sources/ProjectSpec/ProjectSpec.swift +++ b/Sources/ProjectSpec/ProjectSpec.swift @@ -29,6 +29,7 @@ public struct ProjectSpec { public struct Options { public var carthageBuildPath: String? + public var createIntermediateGroups: Bool public var bundleIdPrefix: String? public var settingPresets: SettingPresets = .all @@ -54,6 +55,7 @@ public struct ProjectSpec { } public init() { + createIntermediateGroups = false } } @@ -160,5 +162,6 @@ extension ProjectSpec.Options: JSONObjectConvertible { carthageBuildPath = jsonDictionary.json(atKeyPath: "carthageBuildPath") bundleIdPrefix = jsonDictionary.json(atKeyPath: "bundleIdPrefix") settingPresets = jsonDictionary.json(atKeyPath: "settingPresets") ?? .all + createIntermediateGroups = jsonDictionary.json(atKeyPath: "createIntermediateGroups") ?? false } } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 4992666a..31b4bb01 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -512,6 +512,47 @@ public class PBXProjGenerator { return (fromFiles + fromDirs).flatMap { $0.sourceFiles } } + func getSingleGroup(path: Path, mergingChildren children: [String], depth: Int = 0) -> PBXGroup { + let group: PBXGroup + if let cachedGroup = groupsByPath[path] { + cachedGroup.children += children + group = cachedGroup + } else { + group = PBXGroup( + reference: generateUUID(PBXGroup.self, path.lastComponent), + children: children, + sourceTree: .group, + name: path.lastComponent, + path: depth == 0 && !spec.options.createIntermediateGroups ? + path.byRemovingBase(path: spec.basePath).string : + path.lastComponent + ) + addObject(group) + groupsByPath[path] = group + } + return group + } + + // Add groups for all parents recursively + // ex: path/foo/bar/baz/Hello.swift -> path:[foo:[bar:[baz:[Hello.swift]]]] + func getIntermediateGroups(path: Path, group: PBXGroup) -> PBXGroup { + // verify path is a subpath of spec.basePath + guard Path(components: zip(path.components, spec.basePath.components).map{ $0.0 }) == spec.basePath else { + return group + } + + // base case + if path == spec.basePath { + return group + } + + // recursive case + return getIntermediateGroups( + path: path.parent(), + group: getSingleGroup(path: path, mergingChildren: [group.reference]) + ) + } + func getSources(path: Path, children: [Path]? = nil, depth: Int = 0) throws -> (sourceFiles: [SourceFile], groups: [PBXGroup]) { let children = try children ?? (try path.children()) let excludedFiles: [String] = [".DS_Store"] @@ -605,17 +646,18 @@ public class PBXProjGenerator { } } - let groupPath: String = depth == 0 ? path.byRemovingBase(path: spec.basePath).string : path.lastComponent let group: PBXGroup - if let cachedGroup = groupsByPath[path] { - group = cachedGroup + if spec.options.createIntermediateGroups { + group = getIntermediateGroups( + path: path.parent(), + group: getSingleGroup(path: path, mergingChildren: groupChildren, depth: depth) + ) } else { - group = PBXGroup(reference: generateUUID(PBXGroup.self, path.lastComponent), children: groupChildren, sourceTree: .group, name: path.lastComponent, path: groupPath) - addObject(group) - if depth == 0 { - topLevelGroups.append(group) - } - groupsByPath[path] = group + group = getSingleGroup(path: path, mergingChildren: groupChildren, depth: depth) + } + + if depth == 0 { + topLevelGroups.append(group) } groups.insert(group, at: 0) return (allSourceFiles, groups) diff --git a/docs/ProjectSpec.md b/docs/ProjectSpec.md index 05001f7a..46001d34 100644 --- a/docs/ProjectSpec.md +++ b/docs/ProjectSpec.md @@ -63,6 +63,7 @@ Note that target names can also be changed by adding a `name` property to a targ ### Options - ⚪️ **carthageBuildPath**: `String` - The path to the carthage build directory. Defaults to `Carthage/Build`. This is used when specifying target carthage dependencies +- ⚪️ **createIntermediateGroups**: `String` - If this is specified and set to `true`, then intermediate groups will be created for every path component between the folder containing the source and the base path. For example, when enabled if a source path is specified as `Vendor/Foo/Hello.swift`, the group `Vendor` will created as a parent of the `Foo` group. - ⚪️ **bundleIdPrefix**: `String` - If this is specified then any target that doesn't have an `PRODUCT_BUNDLE_IDENTIFIER` (via all levels of build settings) will get an autogenerated one by combining `bundleIdPrefix` and the target name: `bundleIdPrefix.name`. The target name will be stripped of all characters that aren't alphanumerics, hyphens, or periods. Underscores will be replace with hyphens. - ⚪️ **settingPresets**: `String` - This controls the settings that are automatically applied to the project and its targets. These are the same build settings that Xcode would add when creating a new project. Project settings are applied by config type. Target settings are applied by the product type and platform. By default this is set to `all` - `all`: project and target settings