Skip to content

Commit 632ca2d

Browse files
authored
Enable adding local Swift packages to the project root (#1413)
* Enable adding local Swift packages to the project root * Update CHANGELOG.md
1 parent 1645d41 commit 632ca2d

File tree

4 files changed

+81
-5
lines changed

4 files changed

+81
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Next Version
44

5+
### Added
6+
7+
- Added support for local Swift packages at the project root #1413 @hiltonc
8+
59
## 2.39.1
610

711
### Added

Docs/ProjectSpec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Note that target names can also be changed by adding a `name` property to a targ
147147
- [ ] **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`.
148148
- [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`.
149149
- [ ] **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`.
150-
- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages`
150+
- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages`. Use `""` to specify the project root.
151151
- [ ] **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.
152152
- [ ] **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.
153153
- [ ] **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
12451245
### Local Package
12461246

12471247
- [x] **path**: **String** - the path to the package in local. The path must be directory with a `Package.swift`.
1248-
- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project.
1248+
- [ ] **group** : **String**- Optional path that specifies the location where the package will live in your xcode project. Use `""` to specify the project root.
12491249

12501250
```yml
12511251
packages:

Sources/XcodeGenKit/SourceGenerator.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class SourceGenerator {
5959
}
6060

6161
let absolutePath = project.basePath + path.normalize()
62-
62+
6363
// Get the local package's relative path from the project root
6464
let fileReferencePath = try? absolutePath.relativePath(from: projectDirectory ?? project.basePath).string
6565

@@ -72,8 +72,12 @@ class SourceGenerator {
7272
)
7373
)
7474

75-
let parentGroups = parentGroup.components(separatedBy: "/")
76-
createParentGroups(parentGroups, for: fileReference)
75+
if parentGroup == "" {
76+
rootGroups.insert(fileReference)
77+
} else {
78+
let parentGroups = parentGroup.components(separatedBy: "/")
79+
createParentGroups(parentGroups, for: fileReference)
80+
}
7781
}
7882

7983
/// Collects an array complete of all `SourceFile` objects that make up the target based on the provided `TargetSource` definitions.

Tests/XcodeGenKitTests/ProjectGeneratorTests.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,74 @@ class ProjectGeneratorTests: XCTestCase {
16421642
try expect(file.product?.productName) == "XcodeGen"
16431643
}
16441644

1645+
$0.it("generates local swift packages at the top level") {
1646+
let app = Target(
1647+
name: "MyApp",
1648+
type: .application,
1649+
platform: .iOS,
1650+
dependencies: [
1651+
Dependency(type: .package(products: []), reference: "XcodeGen"),
1652+
]
1653+
)
1654+
1655+
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: "")])
1656+
1657+
let pbxProject = try project.generatePbxProj(specValidate: false)
1658+
let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name }))
1659+
let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" }))
1660+
try expect(localPackageFile.lastKnownFileType) == "folder"
1661+
1662+
let mainGroup = try pbxProject.getMainGroup()
1663+
1664+
try expect(mainGroup.children.contains(localPackageFile)) == true
1665+
1666+
let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }
1667+
1668+
guard let frameworkPhase = frameworkPhases.first else {
1669+
return XCTFail("frameworkPhases should have more than one")
1670+
}
1671+
1672+
guard let file = frameworkPhase.files?.first else {
1673+
return XCTFail("frameworkPhase should have file")
1674+
}
1675+
1676+
try expect(file.product?.productName) == "XcodeGen"
1677+
}
1678+
1679+
$0.it("generates local swift package group at the top level") {
1680+
let app = Target(
1681+
name: "MyApp",
1682+
type: .application,
1683+
platform: .iOS,
1684+
dependencies: [
1685+
Dependency(type: .package(products: []), reference: "XcodeGen"),
1686+
]
1687+
)
1688+
1689+
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: nil)], options: .init(localPackagesGroup: ""))
1690+
1691+
let pbxProject = try project.generatePbxProj(specValidate: false)
1692+
let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name }))
1693+
let localPackageFile = try unwrap(pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" }))
1694+
try expect(localPackageFile.lastKnownFileType) == "folder"
1695+
1696+
let mainGroup = try pbxProject.getMainGroup()
1697+
1698+
try expect(mainGroup.children.contains(localPackageFile)) == true
1699+
1700+
let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }
1701+
1702+
guard let frameworkPhase = frameworkPhases.first else {
1703+
return XCTFail("frameworkPhases should have more than one")
1704+
}
1705+
1706+
guard let file = frameworkPhase.files?.first else {
1707+
return XCTFail("frameworkPhase should have file")
1708+
}
1709+
1710+
try expect(file.product?.productName) == "XcodeGen"
1711+
}
1712+
16451713
$0.it("generates info.plist") {
16461714
let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]])
16471715
let tempPath = Path.temporary + "info"

0 commit comments

Comments
 (0)