diff --git a/Example/XcodeEdit-Example/main.swift b/Example/XcodeEdit-Example/main.swift index a9aba38..637970c 100644 --- a/Example/XcodeEdit-Example/main.swift +++ b/Example/XcodeEdit-Example/main.swift @@ -29,7 +29,112 @@ for obj in proj.allObjects.objects.values { } // Write out a new pbxproj file -try! proj.write(to: xcodeproj, format: .plist(.openStep)) +//try! proj.write(to: xcodeproj, format: .plist(.openStep)) + + +public struct Xcodeproj { + static public let supportedExtensions: Set = ["xcodeproj"] + + private let projectFile: XCProjectFile + + public let developmentRegion: String + public let knownAssetTags: [String]? + + public init(url: URL, warning: (String) -> Void) throws { +// try Xcodeproj.throwIfUnsupportedExtension(url) + let projectFile: XCProjectFile + + // Parse project file + do { + do { + projectFile = try XCProjectFile(xcodeprojURL: url, ignoreReferenceErrors: false) + } + catch let error as ProjectFileError { + warning(error.localizedDescription) + + projectFile = try XCProjectFile(xcodeprojURL: url, ignoreReferenceErrors: true) + } + } + catch { + fatalError("Project file at '\(url)' could not be parsed, is this a valid Xcode project file ending in *.xcodeproj?\n\(error.localizedDescription)") + } + + self.projectFile = projectFile + self.developmentRegion = projectFile.project.developmentRegion + self.knownAssetTags = projectFile.project.knownAssetTags + } + + public var allTargets: [PBXTarget] { + projectFile.project.targets.compactMap { $0.value } + } + + private func findTarget(name: String) throws -> PBXTarget { + // Look for target in project file + let allTargets = projectFile.project.targets.compactMap { $0.value } + guard let target = allTargets.filter({ $0.name == name }).first else { + let availableTargets = allTargets.compactMap { $0.name }.joined(separator: ", ") + fatalError("Target '\(name)' not found in project file, available targets are: \(availableTargets)") + } + + return target + } + + public func resourcePaths(forTarget targetName: String) throws -> [Path] { + let target = try findTarget(name: targetName) + + let resourcesFileRefs = target.buildPhases + .compactMap { $0.value as? PBXResourcesBuildPhase } + .flatMap { $0.files } + .compactMap { $0.value?.fileRef } + + let fileRefPaths = resourcesFileRefs + .compactMap { $0.value as? PBXFileReference } + .compactMap { $0.fullPath } + + let variantGroupPaths = resourcesFileRefs + .compactMap { $0.value as? PBXVariantGroup } + .flatMap { $0.fileRefs } + .compactMap { $0.value?.fullPath } + + return fileRefPaths + variantGroupPaths + } + + public func buildConfigurations(forTarget targetName: String) throws -> [XCBuildConfiguration] { + let target = try findTarget(name: targetName) + + guard let buildConfigurationList = target.buildConfigurationList.value else { return [] } + + let buildConfigurations = buildConfigurationList.buildConfigurations + .compactMap { $0.value } + + return buildConfigurations + } +} + +extension PBXReference { + func fileSystemSynchronizedGroups(path: URL) -> [(URL, PBXFileSystemSynchronizedRootGroup)] { + if let root = self as? PBXFileSystemSynchronizedRootGroup { + let newPath = root.path.map { path.appending(path: $0) } ?? path + return [(newPath, root)] + } else if let group = self as? PBXGroup { + let newPath = group.path.map { path.appending(path: $0) } ?? path + + let children = group.children.compactMap(\.value) + + return children.flatMap { $0.fileSystemSynchronizedGroups(path: newPath) } + + } else { + return [] + } + } +} + + +let the = try Xcodeproj(url: xcodeproj, warning: { fatalError($0)}) + +print(proj.project.mainGroup.value!.fileSystemSynchronizedGroups(path: xcodeproj.deletingLastPathComponent())) + +print(try the.resourcePaths(forTarget: "RswiftUIAppClip")) let time = Date().timeIntervalSince(start) print("Timeinterval: \(time)") diff --git a/README.md b/README.md index 39c9cd6..44eada2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ try! proj.write(to: xcodeproj, format: PropertyListSerialization.PropertyListFor Releases -------- - - 2.10.0 - 2024-09-19 - Add support for file system synchronized projects in Xcode 16 + - 2.10.1 - 2024-09-19 - Serialization fix for file system synchronized directories + - 2.10.0 - 2024-09-19 - Add support for file system synchronized directories in Xcode 16 - 2.9.2 - 2023-09-20 - Fix warnings in Xcode 15 - 2.9.1 - 2023-09-10 - Add support for local Swift packages - 2.9.0 - 2022-11-07 - Add removePackage function to XCSwiftPackageProductDependency diff --git a/Sources/XcodeEdit/PBXObject.swift b/Sources/XcodeEdit/PBXObject.swift index bb72f5d..4cb256f 100644 --- a/Sources/XcodeEdit/PBXObject.swift +++ b/Sources/XcodeEdit/PBXObject.swift @@ -181,6 +181,7 @@ public /* abstract */ class PBXTarget : PBXProjectItem { public let productName: String? private var _buildPhases: [Reference] public let dependencies: [Reference] + public let fileSystemSynchronizedGroups: [Reference]? public let packageProductDependencies: [Reference]? public required init(id: Guid, fields: Fields, allObjects: AllObjects) throws { @@ -189,6 +190,7 @@ public /* abstract */ class PBXTarget : PBXProjectItem { self.productName = try fields.optionalString("productName") self._buildPhases = allObjects.createReferences(ids: try fields.ids("buildPhases")) self.dependencies = allObjects.createReferences(ids: try fields.ids("dependencies")) + self.fileSystemSynchronizedGroups = allObjects.createOptionalReferences(ids: try fields.optionalIds("fileSystemSynchronizedGroups")) self.packageProductDependencies = allObjects.createOptionalReferences(ids: try fields.optionalIds("packageProductDependencies")) try super.init(id: id, fields: fields, allObjects: allObjects)