diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda76be5f..dce7abef5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,8 @@ jobs: name: Xcode ${{ matrix.xcode }} strategy: matrix: - xcode: ["15.4", "16.0"] + xcode: ["16.0"] include: - - xcode: "15.4" - macos: macos-15 - xcode: "16.0" macos: macos-15 env: diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 83166e9c4..5a4aa0dde 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -153,6 +153,10 @@ Note that target names can also be changed by adding a `name` property to a targ - [ ] **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. - [ ] **useBaseInternationalization**: **Bool** If this is `false` and your project does not include resources located in a **Base.lproj** directory then `Base` will not be included in the projects 'known regions'. The default value is `true`. - [ ] **schemePathPrefix**: **String** - A path prefix for relative paths in schemes, such as StoreKitConfiguration. The default is `"../../"`, which is suitable for non-workspace projects. For use in workspaces, use `"../"`. +- [ ] **defaultSourceDirectoryType**: **String** - When a [Target source](#target-source) doesn't specify a type and is a directory, this is the type that will be used. If nothing is specified for either then `group` will be used. + - `group` (default) + - `folder` + - `syncedFolder` ```yaml options: @@ -542,6 +546,7 @@ A source can be provided via a string (the path) or an object of the form: - `file`: a file reference with a parent group will be created (Default for files or directories with extensions) - `group`: a group with all it's containing files. (Default for directories without extensions) - `folder`: a folder reference. + - `syncedFolder`: Xcode 16's synchronized folders, also knows as buildable folders - [ ] **headerVisibility**: **String** - The visibility of any headers. This defaults to `public`, but can be either: - `public` - `private` diff --git a/Package.resolved b/Package.resolved index 237f873fa..671959c8b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tadija/AEXML.git", "state" : { - "revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3", - "version" : "4.6.1" + "revision" : "db806756c989760b35108146381535aec231092b", + "version" : "4.7.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/XcodeProj.git", "state" : { - "revision" : "dc3b87a4e69f9cd06c6cb16199f5d0472e57ef6b", - "version" : "8.24.3" + "revision" : "b1caa062d4aaab3e3d2bed5fe0ac5f8ce9bf84f4", + "version" : "8.27.7" } }, { diff --git a/Package.swift b/Package.swift index 99c7649c0..ab86ad008 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"), .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.2"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "4.0.0"), - .package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.24.3"), + .package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.27.7"), .package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.3"), .package(url: "https://github.com/mxcl/Version", from: "2.0.0"), .package(url: "https://github.com/freddi-kit/ArtifactBundleGen", exact: "0.0.6") diff --git a/Sources/ProjectSpec/SourceType.swift b/Sources/ProjectSpec/SourceType.swift index 77ce4ffe7..9fe009321 100644 --- a/Sources/ProjectSpec/SourceType.swift +++ b/Sources/ProjectSpec/SourceType.swift @@ -11,4 +11,5 @@ public enum SourceType: String { case group case file case folder + case syncedFolder } diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift index a5df4e28d..c10c34698 100644 --- a/Sources/ProjectSpec/SpecOptions.swift +++ b/Sources/ProjectSpec/SpecOptions.swift @@ -37,6 +37,7 @@ public struct SpecOptions: Equatable { public var postGenCommand: String? public var useBaseInternationalization: Bool public var schemePathPrefix: String + public var defaultSourceDirectoryType: SourceType? public enum ValidationType: String { case missingConfigs @@ -100,7 +101,8 @@ public struct SpecOptions: Equatable { preGenCommand: String? = nil, postGenCommand: String? = nil, useBaseInternationalization: Bool = useBaseInternationalizationDefault, - schemePathPrefix: String = schemePathPrefixDefault + schemePathPrefix: String = schemePathPrefixDefault, + defaultSourceDirectoryType: SourceType? = nil ) { self.minimumXcodeGenVersion = minimumXcodeGenVersion self.carthageBuildPath = carthageBuildPath @@ -127,6 +129,7 @@ public struct SpecOptions: Equatable { self.postGenCommand = postGenCommand self.useBaseInternationalization = useBaseInternationalization self.schemePathPrefix = schemePathPrefix + self.defaultSourceDirectoryType = defaultSourceDirectoryType } } @@ -160,6 +163,7 @@ extension SpecOptions: JSONObjectConvertible { postGenCommand = jsonDictionary.json(atKeyPath: "postGenCommand") useBaseInternationalization = jsonDictionary.json(atKeyPath: "useBaseInternationalization") ?? SpecOptions.useBaseInternationalizationDefault schemePathPrefix = jsonDictionary.json(atKeyPath: "schemePathPrefix") ?? SpecOptions.schemePathPrefixDefault + defaultSourceDirectoryType = jsonDictionary.json(atKeyPath: "defaultSourceDirectoryType") if jsonDictionary["fileTypes"] != nil { fileTypes = try jsonDictionary.json(atKeyPath: "fileTypes") } else { diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index c7f5f2d1f..cd683091a 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -1454,6 +1454,12 @@ public class PBXProjGenerator { if !target.isLegacy { targetObject.productType = target.type } + + // add fileSystemSynchronizedGroups + let synchronizedRootGroups = sourceFiles.compactMap { $0.synchronizedRootGroup } + if !synchronizedRootGroups.isEmpty { + targetObject.fileSystemSynchronizedGroups = synchronizedRootGroups + } } private func makePlatformFilter(for filter: Dependency.PlatformFilter) -> String? { diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 0b8f93b70..4da2e0608 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -9,6 +9,7 @@ struct SourceFile { let fileReference: PBXFileElement let buildFile: PBXBuildFile let buildPhase: BuildPhaseSpec? + var synchronizedRootGroup: PBXFileSystemSynchronizedRootGroup? } class SourceGenerator { @@ -687,6 +688,33 @@ class SourceGenerator { sourceFiles += groupSourceFiles sourceReference = group + case .syncedFolder: + + let relativePath = (try? path.relativePath(from: project.basePath)) ?? path + + let syncedRootGroup = PBXFileSystemSynchronizedRootGroup( + sourceTree: .group, + path: relativePath.string, + name: targetSource.name, + explicitFileTypes: [:], + exceptions: [], + explicitFolders: [] + ) + addObject(syncedRootGroup) + sourceReference = syncedRootGroup + + // TODO: adjust if hasCustomParent == true + rootGroups.insert(syncedRootGroup) + + var sourceFile = generateSourceFile( + targetType: targetType, + targetSource: targetSource, + path: path, + fileReference: syncedRootGroup, + buildPhases: buildPhases + ) + sourceFile.synchronizedRootGroup = syncedRootGroup + sourceFiles.append(sourceFile) } if hasCustomParent { @@ -703,7 +731,17 @@ class SourceGenerator { /// /// While `TargetSource` declares `type`, its optional and in the event that the value is not defined then we must resolve a sensible default based on the path of the source. private func resolvedTargetSourceType(for targetSource: TargetSource, at path: Path) -> SourceType { - return targetSource.type ?? (path.isFile || path.extension != nil ? .file : .group) + if let chosenType = targetSource.type { + return chosenType + } else { + if path.isFile || path.extension != nil { + return .file + } else if let sourceType = project.options.defaultSourceDirectoryType { + return sourceType + } else { + return .group + } + } } private func createParentGroups(_ parentGroups: [String], for fileElement: PBXFileElement) { diff --git a/Sources/XcodeGenKit/Version.swift b/Sources/XcodeGenKit/Version.swift index 57618046d..007d26f64 100644 --- a/Sources/XcodeGenKit/Version.swift +++ b/Sources/XcodeGenKit/Version.swift @@ -16,7 +16,7 @@ extension Project { } var objectVersion: UInt { - 54 + 70 } var minimizedProjectReferenceProxies: Int { diff --git a/Sources/XcodeGenKit/XCProjExtensions.swift b/Sources/XcodeGenKit/XCProjExtensions.swift index ec3f2763b..33d3f5b67 100644 --- a/Sources/XcodeGenKit/XCProjExtensions.swift +++ b/Sources/XcodeGenKit/XCProjExtensions.swift @@ -38,6 +38,8 @@ extension PBXProj { string += "\n 🌎 " + variantGroup.nameOrPath } else if let versionGroup = child as? XCVersionGroup { string += "\n 🔢 " + versionGroup.nameOrPath + } else if let syncedFolder = child as? PBXFileSystemSynchronizedRootGroup { + string += "\n 📁 " + syncedFolder.nameOrPath } } return string diff --git a/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj index eb6cf67fe..3289e3bdd 100644 --- a/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/CarthageProject/Project.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -322,8 +322,6 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; - TargetAttributes = { - }; }; buildConfigurationList = D91E14E36EC0B415578456F2 /* Build configuration list for PBXProject "Project" */; compatibilityVersion = "Xcode 14.0"; @@ -335,7 +333,7 @@ ); mainGroup = 293D0FF827366B513839236A; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 54; + preferredProjectObjectVersion = 70; projectDirPath = ""; projectRoot = ""; targets = ( diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj index 59311f725..7918465d3 100644 --- a/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXAggregateTarget section */ @@ -239,8 +239,6 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; - TargetAttributes = { - }; }; buildConfigurationList = 425866ADA259DB93FC4AF1E3 /* Build configuration list for PBXProject "SPM" */; compatibilityVersion = "Xcode 14.0"; @@ -259,7 +257,7 @@ 630A8CE9F2BE39704ED9D461 /* XCLocalSwiftPackageReference "FooFeature" */, C6539B364583AE96C18CE377 /* XCLocalSwiftPackageReference "../../.." */, ); - preferredProjectObjectVersion = 54; + preferredProjectObjectVersion = 70; projectDirPath = ""; projectRoot = ""; targets = ( diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme index 353d320e0..298045bf8 100644 --- a/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -40,7 +40,8 @@ + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> Bool { - // Override point for customization after application launch. + + // file from a framework _ = FrameworkStruct() + // Standalone files added to project by path-to-file. _ = standaloneHello() + + // file in a synced folder + _ = SyncedStruct() + return true } } diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index 1898ccc66..ce031a8c8 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXAggregateTarget section */ @@ -830,6 +830,18 @@ FED40A89162E446494DDE7C7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + AE2AB2772F70DFFF402AA02B /* SyncedFolder */ = { + isa = PBXFileSystemSynchronizedRootGroup; + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = SyncedFolder; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 117840B4DBC04099F6779D00 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -1050,6 +1062,7 @@ 2E1E747C7BC434ADB80CC269 /* Headers */, 6B1603BA83AA0C7B94E45168 /* ResourceFolder */, 6BBE762F36D94AB6FFBFE834 /* SomeFile */, + AE2AB2772F70DFFF402AA02B /* SyncedFolder */, 79DC4A1E4D2E0D3A215179BC /* Bundles */, FC1515684236259C50A7747F /* Frameworks */, AC523591AC7BE9275003D2DB /* Products */, @@ -1686,6 +1699,9 @@ E8C078B0A2A2B0E1D35694D5 /* PBXTargetDependency */, 981D116D40DBA0407D0E0E94 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + AE2AB2772F70DFFF402AA02B /* SyncedFolder */, + ); name = App_iOS; packageProductDependencies = ( D7917D10F77DA9D69937D493 /* Swinject */, @@ -2443,7 +2459,7 @@ packageReferences = ( 4EDA79334592CBBA0E507AD2 /* XCRemoteSwiftPackageReference "Swinject" */, ); - preferredProjectObjectVersion = 54; + preferredProjectObjectVersion = 70; projectDirPath = ""; projectReferences = ( { diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Clip.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Clip.xcscheme index b5a5eda3a..6959dff88 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Clip.xcscheme +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Clip.xcscheme @@ -40,7 +40,8 @@ + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO"> + skipped = "NO" + parallelizable = "NO">