diff --git a/CHANGELOG.md b/CHANGELOG.md index a80e7321..a84fc140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next Version +### Added + +- Added support for local Swift packages at the project root #1413 @hiltonc + ## 2.39.1 ### Added diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index dd7f08d6..fd821a6e 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -147,7 +147,7 @@ Note that target names can also be changed by adding a `name` property to a targ - [ ] **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`. - [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`. - [ ] **findCarthageFrameworks**: **Bool** - When this is set to `true`, all the individual frameworks for Carthage framework dependencies will automatically be found. This property can be overridden individually for each carthage dependency - for more details see See **findFrameworks** in the [Dependency](#dependency) section. Defaults to `false`. -- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages` +- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages`. Use `""` to specify the project root. - [ ] **fileTypes**: **[String: [FileType](#filetype)]** - A list of default file options for specific file extensions across the project. Values in [Sources](#sources) will overwrite these settings. - [ ] **preGenCommand**: **String** - A bash command to run before the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like generating resources files before the project is regenerated. - [ ] **postGenCommand**: **String** - A bash command to run after the project has been generated. If the project isn't generated due to no changes when using the cache then this won't run. This is useful for running things like `pod install` only if the project is actually regenerated. @@ -1245,7 +1245,7 @@ Swift packages are defined at a project level, and then linked to individual tar ### Local Package - [x] **path**: **String** - the path to the package in local. The path must be directory with a `Package.swift`. -- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. +- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. Use `""` to specify the project root. ```yml packages: diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 3abc303e..7ed90b3d 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -59,7 +59,7 @@ class SourceGenerator { } let absolutePath = project.basePath + path.normalize() - + // Get the local package's relative path from the project root let fileReferencePath = try? absolutePath.relativePath(from: projectDirectory ?? project.basePath).string @@ -72,8 +72,12 @@ class SourceGenerator { ) ) - let parentGroups = parentGroup.components(separatedBy: "/") - createParentGroups(parentGroups, for: fileReference) + if parentGroup == "" { + rootGroups.insert(fileReference) + } else { + let parentGroups = parentGroup.components(separatedBy: "/") + createParentGroups(parentGroups, for: fileReference) + } } /// Collects an array complete of all `SourceFile` objects that make up the target based on the provided `TargetSource` definitions. diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 5203cbc1..44046460 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -1642,6 +1642,74 @@ class ProjectGeneratorTests: XCTestCase { try expect(file.product?.productName) == "XcodeGen" } + $0.it("generates local swift packages at the top level") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .package(products: []), reference: "XcodeGen"), + ] + ) + + let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: "")]) + + let pbxProject = try project.generatePbxProj(specValidate: false) + let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name })) + let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" })) + try expect(localPackageFile.lastKnownFileType) == "folder" + + let mainGroup = try pbxProject.getMainGroup() + + try expect(mainGroup.children.contains(localPackageFile)) == true + + let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase } + + guard let frameworkPhase = frameworkPhases.first else { + return XCTFail("frameworkPhases should have more than one") + } + + guard let file = frameworkPhase.files?.first else { + return XCTFail("frameworkPhase should have file") + } + + try expect(file.product?.productName) == "XcodeGen" + } + + $0.it("generates local swift package group at the top level") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .package(products: []), reference: "XcodeGen"), + ] + ) + + let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: nil)], options: .init(localPackagesGroup: "")) + + let pbxProject = try project.generatePbxProj(specValidate: false) + let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name })) + let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" })) + try expect(localPackageFile.lastKnownFileType) == "folder" + + let mainGroup = try pbxProject.getMainGroup() + + try expect(mainGroup.children.contains(localPackageFile)) == true + + let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase } + + guard let frameworkPhase = frameworkPhases.first else { + return XCTFail("frameworkPhases should have more than one") + } + + guard let file = frameworkPhase.files?.first else { + return XCTFail("frameworkPhase should have file") + } + + try expect(file.product?.productName) == "XcodeGen" + } + $0.it("generates info.plist") { let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]]) let tempPath = Path.temporary + "info"