diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..7ee2c42 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "ObjectCoder", + "repositoryURL": "https://github.com/g-Off/ObjectCoder.git", + "state": { + "branch": null, + "revision": "701d70d41d065b7b6567094a45bd4b796f4eb4d2", + "version": "0.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 080ef1d..c6bc9ec 100644 --- a/Package.swift +++ b/Package.swift @@ -13,11 +13,12 @@ let package = Package( targets: ["XcodeProject"]), ], dependencies: [ + .package(url: "https://github.com/g-Off/ObjectCoder.git", .exact("0.1.0")) ], targets: [ .target( name: "XcodeProject", - dependencies: []), + dependencies: ["ObjectCoder"]), .testTarget( name: "XcodeProjectTests", dependencies: ["XcodeProject"]), diff --git a/Sources/XcodeProject/Archiving/Encoder/AnyCodingKey.swift b/Sources/XcodeProject/Archiving/Encoder/AnyCodingKey.swift index d73a6ce..534397e 100644 --- a/Sources/XcodeProject/Archiving/Encoder/AnyCodingKey.swift +++ b/Sources/XcodeProject/Archiving/Encoder/AnyCodingKey.swift @@ -21,6 +21,10 @@ struct AnyCodingKey: CodingKey, Equatable, Hashable { self.intValue = intValue } + init(index: Int) { + self.init(intValue: index)! + } + init(_ base: Key) where Key: CodingKey { if let intValue = base.intValue { self.init(intValue: intValue)! diff --git a/Sources/XcodeProject/Archiving/Encoder/PBXEncoder.swift b/Sources/XcodeProject/Archiving/Encoder/PBXEncoder.swift index 86b98ab..9520f9f 100644 --- a/Sources/XcodeProject/Archiving/Encoder/PBXEncoder.swift +++ b/Sources/XcodeProject/Archiving/Encoder/PBXEncoder.swift @@ -6,16 +6,38 @@ // import Foundation +import ObjectCoder class PBXObjectEncoder { static let objectVersionKey = CodingUserInfoKey(rawValue: "objectVersion")! var objectVersion: ObjectVersion = .xcode93 + private static func quotedKey(_ codingKeys: [CodingKey]) -> CodingKey { + let codingKey = codingKeys.last! + if codingKey.intValue != nil { return codingKey } + return AnyCodingKey(stringValue: codingKey.stringValue.quotedString)! + } + func encode(_ object: PBXObject) throws -> [String: AnyObject] { - let encoder = _PBXObjectEncoder() - encoder.userInfo = [PBXObjectEncoder.objectVersionKey: objectVersion] + let options = ObjectEncoder.Options( + keyEncodingStrategy: .custom(PBXObjectEncoder.quotedKey), + userInfo: [PBXObjectEncoder.objectVersionKey: objectVersion] + ) + let boxer = ObjectEncoder.Boxer() + boxer.addWrapper { (object: PBXObject) -> NSObject in + return NSString(string: object.plistID.description) + } + boxer.encodedString = { (value, _) in + return NSString(string: value.quotedString) + } + let encoder = ObjectEncoder(options: options, boxer: boxer) try object.encode(to: encoder) - return encoder.storage.popContainer() as? [String: AnyObject] ?? [:] + return encoder.encoded as? [String: AnyObject] ?? [:] + +// let encoder = _PBXObjectEncoder() +// encoder.userInfo = [PBXObjectEncoder.objectVersionKey: objectVersion] +// try object.encode(to: encoder) +// return encoder.storage.popContainer() as? [String: AnyObject] ?? [:] } } diff --git a/Sources/XcodeProject/Archiving/Encoder/_PBXObjectEncoder+KeyedContainer.swift b/Sources/XcodeProject/Archiving/Encoder/_PBXObjectEncoder+KeyedContainer.swift index a795580..b85501a 100644 --- a/Sources/XcodeProject/Archiving/Encoder/_PBXObjectEncoder+KeyedContainer.swift +++ b/Sources/XcodeProject/Archiving/Encoder/_PBXObjectEncoder+KeyedContainer.swift @@ -28,17 +28,17 @@ extension _PBXObjectEncoder.KeyedContainer: KeyedEncodingContainerProtocol { } func encodeNil(forKey key: Key) throws { - container[key.stringValue] = NSNull() + container[key.stringValue.quotedString] = NSNull() } func encode(_ value: String, forKey key: Key) throws { - container[key.stringValue] = NSString(string: value.quotedString) + container[key.stringValue.quotedString] = NSString(string: value.quotedString) } func encode(_ value: T, forKey key: Key) throws where T: Encodable { encoder.codingPath.append(key) defer { encoder.codingPath.removeLast() } - container[key.stringValue] = try encoder.box(value) + container[key.stringValue.quotedString] = try encoder.box(value) } func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { diff --git a/Sources/XcodeProject/Archiving/PropertyList.swift b/Sources/XcodeProject/Archiving/PropertyList.swift index 419260f..bd6ab11 100644 --- a/Sources/XcodeProject/Archiving/PropertyList.swift +++ b/Sources/XcodeProject/Archiving/PropertyList.swift @@ -21,6 +21,11 @@ struct PropertyList { return PropertyList(value) } + subscript(key: CodingKey) -> PropertyList? { + guard let dictionary = self.dictionary else { return nil } + return PropertyList(dictionary[key.stringValue]) + } + private func getTypedObject() -> T? { return object as? T } diff --git a/Sources/XcodeProject/Objects/Build Phases/PBXBuildPhase.swift b/Sources/XcodeProject/Objects/Build Phases/PBXBuildPhase.swift index f89c840..e1843ea 100644 --- a/Sources/XcodeProject/Objects/Build Phases/PBXBuildPhase.swift +++ b/Sources/XcodeProject/Objects/Build Phases/PBXBuildPhase.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -public class PBXBuildPhase: PBXObject { +public class PBXBuildPhase: PBXProjectItem { private enum CodingKeys: String, CodingKey { case name case files @@ -61,18 +61,18 @@ public class PBXBuildPhase: PBXObject { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - guard let files = plist["files"]?.array else { + guard let files = plist[CodingKeys.files]?.array else { fatalError() } - self._name = plist["name"]?.string + self._name = plist[CodingKeys.name]?.string self.files = files.compactMap { let file: PBXBuildFile? = objectCache.object(for: PBXGlobalID(rawValue: $0)) return file } - self.runOnlyForDeploymentPostprocessing = plist["runOnlyForDeploymentPostprocessing"]?.bool + self.runOnlyForDeploymentPostprocessing = plist[CodingKeys.runOnlyForDeploymentPostprocessing]?.bool - if let v = plist["buildActionMask"]?.string, let buildActionMask = Int32(v) { + if let v = plist[CodingKeys.buildActionMask]?.string, let buildActionMask = Int32(v) { self.buildActionMask = buildActionMask } } diff --git a/Sources/XcodeProject/Objects/Build Phases/PBXCopyFilesBuildPhase.swift b/Sources/XcodeProject/Objects/Build Phases/PBXCopyFilesBuildPhase.swift index a4addf3..074301b 100644 --- a/Sources/XcodeProject/Objects/Build Phases/PBXCopyFilesBuildPhase.swift +++ b/Sources/XcodeProject/Objects/Build Phases/PBXCopyFilesBuildPhase.swift @@ -39,9 +39,9 @@ public final class PBXCopyFilesBuildPhase: PBXBuildPhase { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - guard let dstPath = plist["dstPath"]?.string else { fatalError() } + guard let dstPath = plist[CodingKeys.dstPath]?.string else { fatalError() } self.dstPath = dstPath - guard let dstSubfolderSpec = Destination(string: plist["dstSubfolderSpec"]?.string) else { fatalError() } + guard let dstSubfolderSpec = Destination(string: plist[CodingKeys.dstSubfolderSpec]?.string) else { fatalError() } self.dstSubfolderSpec = dstSubfolderSpec } diff --git a/Sources/XcodeProject/Objects/Build Phases/PBXShellScriptBuildPhase.swift b/Sources/XcodeProject/Objects/Build Phases/PBXShellScriptBuildPhase.swift index 2f74255..05c550d 100644 --- a/Sources/XcodeProject/Objects/Build Phases/PBXShellScriptBuildPhase.swift +++ b/Sources/XcodeProject/Objects/Build Phases/PBXShellScriptBuildPhase.swift @@ -27,13 +27,13 @@ public final class PBXShellScriptBuildPhase: PBXBuildPhase { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.inputPaths = plist["inputPaths"]?.array ?? [] - self.outputPaths = plist["outputPaths"]?.array ?? [] - self.inputFileListPaths = plist["inputFileListPaths"]?.array ?? [] - self.outputFileListPaths = plist["outputFileListPaths"]?.array ?? [] - self.shellPath = plist["shellPath"]?.string - self.shellScript = plist["shellScript"]?.string - self.showEnvVarsInLog = plist["showEnvVarsInLog"]?.bool + self.inputPaths = plist[CodingKeys.inputPaths]?.array ?? [] + self.outputPaths = plist[CodingKeys.outputPaths]?.array ?? [] + self.inputFileListPaths = plist[CodingKeys.inputFileListPaths]?.array ?? [] + self.outputFileListPaths = plist[CodingKeys.outputFileListPaths]?.array ?? [] + self.shellPath = plist[CodingKeys.shellPath]?.string + self.shellScript = plist[CodingKeys.shellScript]?.string + self.showEnvVarsInLog = plist[CodingKeys.showEnvVarsInLog]?.bool } public override func encode(to encoder: Encoder) throws { diff --git a/Sources/XcodeProject/Objects/PBXBuildFile.swift b/Sources/XcodeProject/Objects/PBXBuildFile.swift index 8e86ef1..78f1d73 100644 --- a/Sources/XcodeProject/Objects/PBXBuildFile.swift +++ b/Sources/XcodeProject/Objects/PBXBuildFile.swift @@ -6,10 +6,11 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -public final class PBXBuildFile: PBXObject { +public final class PBXBuildFile: PBXProjectItem { private enum CodingKeys: String, CodingKey { case fileRef case settings + case productRef } public enum Attribute: String, Comparable, Encodable { public static func < (lhs: PBXBuildFile.Attribute, rhs: PBXBuildFile.Attribute) -> Bool { @@ -18,6 +19,7 @@ public final class PBXBuildFile: PBXObject { case `public` = "Public" case `private` = "Private" + case required = "Required" case `weak` = "Weak" case client = "Client" case server = "Server" @@ -61,6 +63,8 @@ public final class PBXBuildFile: PBXObject { } var settings: Settings? + private(set) var productReference: PBXProductDependency? + public convenience init(globalID: PBXGlobalID, fileReference: PBXReference) { self.init(globalID: globalID) fileRef = fileReference @@ -71,24 +75,27 @@ public final class PBXBuildFile: PBXObject { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(fileRef, forKey: .fileRef) try container.encodeIfPresent(settings, forKey: .settings) + try container.encodeIfPresent(productReference, forKey: .productRef) } override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.fileRef = objectCache.object(for: PBXGlobalID(rawValue: plist["fileRef"]?.string)) + self.fileRef = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.fileRef]?.string)) + self.productReference = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.productRef]?.string)) self.settings = Settings(plist["settings"]?.dictionary) } override var archiveComment: String { - guard let fileRef = fileRef, let parent = parent else { + guard let parent = parent, let refComment = fileRef?.archiveComment ?? productReference?.archiveComment else { return super.archiveComment } - return "\(fileRef.archiveComment) in \(parent.archiveComment)" + return "\(refComment) in \(parent.archiveComment)" } override func visit(_ visitor: ObjectVisitor) { super.visit(visitor) visitor.visit(object: fileRef) + visitor.visit(object: productReference) } override var archiveInPlistOnSingleLine: Bool { diff --git a/Sources/XcodeProject/Objects/PBXBuildRule.swift b/Sources/XcodeProject/Objects/PBXBuildRule.swift index c306db4..6636ed0 100644 --- a/Sources/XcodeProject/Objects/PBXBuildRule.swift +++ b/Sources/XcodeProject/Objects/PBXBuildRule.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -public final class PBXBuildRule: PBXObject { +public final class PBXBuildRule: PBXProjectItem { private enum CodingKeys: String, CodingKey { case compilerSpec case fileType @@ -22,11 +22,11 @@ public final class PBXBuildRule: PBXObject { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.compilerSpec = plist["compilerSpec"]?.string - self.fileType = plist["fileType"]?.string - self.isEditable = plist["isEditable"]?.bool ?? true - self.outputFiles = plist["outputFiles"]?.array ?? [] - self.script = plist["script"]?.string + self.compilerSpec = plist[CodingKeys.compilerSpec]?.string + self.fileType = plist[CodingKeys.fileType]?.string + self.isEditable = plist[CodingKeys.isEditable]?.bool ?? true + self.outputFiles = plist[CodingKeys.outputFiles]?.array ?? [] + self.script = plist[CodingKeys.script]?.string } public override func encode(to encoder: Encoder) throws { diff --git a/Sources/XcodeProject/Objects/PBXContainerItem.swift b/Sources/XcodeProject/Objects/PBXContainerItem.swift new file mode 100644 index 0000000..0b6ea3b --- /dev/null +++ b/Sources/XcodeProject/Objects/PBXContainerItem.swift @@ -0,0 +1,16 @@ +// +// PBXContainerItem.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +public class PBXContainerItem: PBXObject { + +} + +public class PBXProjectItem: PBXContainerItem { + +} diff --git a/Sources/XcodeProject/Objects/PBXContainerItemProxy.swift b/Sources/XcodeProject/Objects/PBXContainerItemProxy.swift index 671a69f..b59b1ad 100644 --- a/Sources/XcodeProject/Objects/PBXContainerItemProxy.swift +++ b/Sources/XcodeProject/Objects/PBXContainerItemProxy.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -final class PBXContainerItemProxy: PBXObject { +final class PBXContainerItemProxy: PBXContainerItem { private enum CodingKeys: String, CodingKey { case containerPortal case proxyType @@ -33,10 +33,10 @@ final class PBXContainerItemProxy: PBXObject { super.update(with: plist, objectCache: objectCache) guard - let containerPortal = objectCache.object(for: PBXGlobalID(rawValue: plist["containerPortal"]?.string)), - let proxyType = ProxyType(string: plist["proxyType"]?.string), - let remoteGlobalIDString = plist["remoteGlobalIDString"]?.string, - let remoteInfo = plist["remoteInfo"]?.string + let containerPortal = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.containerPortal]?.string)), + let proxyType = ProxyType(string: plist[CodingKeys.proxyType]?.string), + let remoteGlobalIDString = plist[CodingKeys.remoteGlobalIDString]?.string, + let remoteInfo = plist[CodingKeys.remoteInfo]?.string else { fatalError() } diff --git a/Sources/XcodeProject/Objects/PBXObject.swift b/Sources/XcodeProject/Objects/PBXObject.swift index a4f2462..505ca13 100644 --- a/Sources/XcodeProject/Objects/PBXObject.swift +++ b/Sources/XcodeProject/Objects/PBXObject.swift @@ -25,6 +25,10 @@ public class PBXObject: Encodable { self.globalID = globalID } + func awake(from decoder: Decoder) throws { + + } + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(isa, forKey: .isa) @@ -129,4 +133,7 @@ let types: [PBXObject.Type] = [ XCBuildConfiguration.self, XCConfigurationList.self, XCVersionGroup.self, + XCLocalSwiftPackageReference.self, + XCRemoteSwiftPackageReference.self, + XCSwiftPackageProductDependency.self, ] diff --git a/Sources/XcodeProject/Objects/PBXProductDependency.swift b/Sources/XcodeProject/Objects/PBXProductDependency.swift new file mode 100644 index 0000000..69807e0 --- /dev/null +++ b/Sources/XcodeProject/Objects/PBXProductDependency.swift @@ -0,0 +1,12 @@ +// +// PBXProductDependency.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +public class PBXProductDependency: PBXProjectItem { + +} diff --git a/Sources/XcodeProject/Objects/PBXProject.swift b/Sources/XcodeProject/Objects/PBXProject.swift index 3ba290d..c839ca1 100644 --- a/Sources/XcodeProject/Objects/PBXProject.swift +++ b/Sources/XcodeProject/Objects/PBXProject.swift @@ -17,6 +17,7 @@ public final class PBXProject: PBXObject, PBXContainer { case hasScannedForEncodings case knownRegions case mainGroup + case packageReferences case productRefGroup case projectDirPath case projectReferences @@ -88,6 +89,7 @@ public final class PBXProject: PBXObject, PBXContainer { case xcode8_0 = "Xcode 8.0" case xcode9_3 = "Xcode 9.3" case xcode_10 = "Xcode 10.0" + case xcode_11 = "Xcode 11.0" } var attributes = Attributes([:]) @@ -115,6 +117,7 @@ public final class PBXProject: PBXObject, PBXContainer { targets.forEach { $0.parent = self } } } + var packageReferences: [XCSwiftPackageReference]? var path: String? @@ -146,18 +149,18 @@ public final class PBXProject: PBXObject, PBXContainer { super.update(with: plist, objectCache: objectCache) guard - let attributes = plist["attributes"]?.dictionary, - let buildConfigurationListID = PBXGlobalID(rawValue: plist["buildConfigurationList"]?.string), + let attributes = plist[CodingKeys.attributes]?.dictionary, + let buildConfigurationListID = PBXGlobalID(rawValue: plist[CodingKeys.buildConfigurationList]?.string), let buildConfigurationList = objectCache.object(for: buildConfigurationListID) as? XCConfigurationList, - let compatibilityVersion = CompatibilityVersion(rawValue: plist["compatibilityVersion"]?.string ?? ""), - let developmentRegion = plist["developmentRegion"]?.string, - let hasScannedForEncodings = plist["hasScannedForEncodings"]?.string, - let knownRegions = plist["knownRegions"]?.array, - let mainGroup = objectCache.object(for: PBXGlobalID(rawValue: plist["mainGroup"]?.string)) as? PBXGroup, - let productRefGroup = objectCache.object(for: PBXGlobalID(rawValue: plist["productRefGroup"]?.string)) as? PBXGroup, - let projectDirPath = plist["projectDirPath"]?.string, - let projectRoot = plist["projectRoot"]?.string, - let targets = plist["targets"]?.array + let compatibilityVersion = CompatibilityVersion(rawValue: plist[CodingKeys.compatibilityVersion]?.string ?? ""), + let developmentRegion = plist[CodingKeys.developmentRegion]?.string, + let hasScannedForEncodings = plist[CodingKeys.hasScannedForEncodings]?.string, + let knownRegions = plist[CodingKeys.knownRegions]?.array, + let mainGroup = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.mainGroup]?.string)) as? PBXGroup, + let productRefGroup = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.productRefGroup]?.string)) as? PBXGroup, + let projectDirPath = plist[CodingKeys.projectDirPath]?.string, + let projectRoot = plist[CodingKeys.projectRoot]?.string, + let targets = plist[CodingKeys.targets]?.array else { fatalError() } @@ -172,12 +175,18 @@ public final class PBXProject: PBXObject, PBXContainer { self.productRefGroup = productRefGroup self.projectDirPath = projectDirPath - if let projectReferences = plist["projectReferences"]?.objectArray() { + if let projectReferences = plist[CodingKeys.projectReferences]?.objectArray() { self.projectReferences = Set(projectReferences.map { XCProjectReferenceInfo(with: $0, objectCache: objectCache) }) } + if let packageReferences = plist[CodingKeys.packageReferences]?.array { + self.packageReferences = packageReferences.map { PBXGlobalID(rawValue: $0) }.compactMap { + objectCache.object(for: $0) + } + } + self.projectRoot = projectRoot self.targets = targets.compactMap { let target: PBXTarget? = objectCache.object(for: PBXGlobalID(rawValue: $0)) @@ -221,6 +230,9 @@ public final class PBXProject: PBXObject, PBXContainer { visitor.visit(object: $0.productGroup) visitor.visit(object: $0.projectReference) } + packageReferences?.forEach { + visitor.visit(object: $0) + } } public override func encode(to encoder: Encoder) throws { @@ -233,6 +245,7 @@ public final class PBXProject: PBXObject, PBXContainer { try container.encode(hasScannedForEncodings, forKey: .hasScannedForEncodings) try container.encode(knownRegions, forKey: .knownRegions) try container.encode(mainGroup, forKey: .mainGroup) + try container.encodeIfPresent(packageReferences, forKey: .packageReferences) try container.encode(productRefGroup, forKey: .productRefGroup) try container.encodeIfPresent(projectDirPath, forKey: .projectDirPath) try container.encodeIfPresent(projectReferences?.sorted(by: \XCProjectReferenceInfo.projectReference.displayName, using: String.caseInsensitiveCompare), forKey: .projectReferences) diff --git a/Sources/XcodeProject/Objects/PBXTargetDependency.swift b/Sources/XcodeProject/Objects/PBXTargetDependency.swift index 9a9e5dc..024ebd6 100644 --- a/Sources/XcodeProject/Objects/PBXTargetDependency.swift +++ b/Sources/XcodeProject/Objects/PBXTargetDependency.swift @@ -6,7 +6,9 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -public class PBXTargetDependency: PBXObject { +public class PBXTargetDependency: PBXProjectItem { + //addPackageProductDependency + //removePackageProductDependency private enum CodingKeys: String, CodingKey { case name case target @@ -39,9 +41,9 @@ public class PBXTargetDependency: PBXObject { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.name = plist["name"]?.string - self.target = objectCache.object(for: plist["target"]?.globalID) - self.targetProxy = objectCache.object(for: plist["targetProxy"]?.globalID) + self.name = plist[CodingKeys.name]?.string + self.target = objectCache.object(for: plist[CodingKeys.target]?.globalID) + self.targetProxy = objectCache.object(for: plist[CodingKeys.targetProxy]?.globalID) } override func visit(_ visitor: ObjectVisitor) { diff --git a/Sources/XcodeProject/Objects/References/PBXFileReference.swift b/Sources/XcodeProject/Objects/References/PBXFileReference.swift index 03b8258..1ca65a5 100644 --- a/Sources/XcodeProject/Objects/References/PBXFileReference.swift +++ b/Sources/XcodeProject/Objects/References/PBXFileReference.swift @@ -24,9 +24,9 @@ public final class PBXFileReference: PBXReference { case unknown static func from(_ plist: PropertyList) -> FileType { - if let lastKnownFileType = plist["lastKnownFileType"]?.string { + if let lastKnownFileType = plist[CodingKeys.lastKnownFileType]?.string { return .lastKnown(lastKnownFileType) - } else if let explicitFileType = plist["explicitFileType"]?.string { + } else if let explicitFileType = plist[CodingKeys.explicitFileType]?.string { return .explicit(explicitFileType) } else { return .unknown @@ -73,14 +73,14 @@ public final class PBXFileReference: PBXReference { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - if let encoding = plist["fileEncoding"]?.uint { + if let encoding = plist[CodingKeys.fileEncoding]?.uint { self.fileEncoding = String.Encoding(rawValue: encoding) } - self.includeInIndex = plist["includeInIndex"]?.bool + self.includeInIndex = plist[CodingKeys.includeInIndex]?.bool self.fileType = FileType.from(plist) - self.xcLanguageSpecificationIdentifier = plist["xcLanguageSpecificationIdentifier"]?.string - self.wrapsLines = plist["wrapsLines"]?.bool + self.xcLanguageSpecificationIdentifier = plist[CodingKeys.xcLanguageSpecificationIdentifier]?.string + self.wrapsLines = plist[CodingKeys.wrapsLines]?.bool } public override func encode(to encoder: Encoder) throws { diff --git a/Sources/XcodeProject/Objects/References/PBXGroup.swift b/Sources/XcodeProject/Objects/References/PBXGroup.swift index 4f8e6fc..0297e7b 100644 --- a/Sources/XcodeProject/Objects/References/PBXGroup.swift +++ b/Sources/XcodeProject/Objects/References/PBXGroup.swift @@ -44,7 +44,7 @@ public class PBXGroup: PBXReference { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.children = plist["children"]?.array?.map { return PBXGlobalID(rawValue: $0) }.compactMap { objectCache.object(for: $0) as? PBXReference } ?? [] + self.children = plist[CodingKeys.children]?.array?.map { return PBXGlobalID(rawValue: $0) }.compactMap { objectCache.object(for: $0) as? PBXReference } ?? [] } override func visit(_ visitor: ObjectVisitor) { diff --git a/Sources/XcodeProject/Objects/References/PBXReference.swift b/Sources/XcodeProject/Objects/References/PBXReference.swift index 1e734b2..d4fdf4d 100644 --- a/Sources/XcodeProject/Objects/References/PBXReference.swift +++ b/Sources/XcodeProject/Objects/References/PBXReference.swift @@ -8,7 +8,7 @@ import Foundation -public class PBXReference: PBXObject { +public class PBXReference: PBXContainerItem { private enum CodingKeys: String, CodingKey { case name case path @@ -26,6 +26,7 @@ public class PBXReference: PBXObject { case builtProducts = "BUILT_PRODUCTS_DIR" case developerDir = "DEVELOPER_DIR" case sdkRoot = "SDKROOT" + case unknown = "" } public enum LineEnding: Int8, Encodable { @@ -102,15 +103,15 @@ public class PBXReference: PBXObject { // MARK: - PList Unarchiving override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.path = plist["path"]?.string - self.name = plist["name"]?.string - self.sourceTree = SourceTree(rawValue: plist["sourceTree"]?.string ?? "") - self.usesTabs = plist["usesTabs"]?.bool - self.tabWidth = plist["tabWidth"]?.int - if let lineEnding = plist["lineEnding"]?.int8 { + self.path = plist[CodingKeys.path]?.string + self.name = plist[CodingKeys.name]?.string + self.sourceTree = SourceTree(rawValue: plist[CodingKeys.sourceTree]?.string ?? "") + self.usesTabs = plist[CodingKeys.usesTabs]?.bool + self.tabWidth = plist[CodingKeys.tabWidth]?.int + if let lineEnding = plist[CodingKeys.lineEnding]?.int8 { self.lineEnding = LineEnding(rawValue: lineEnding) } - self.indentWidth = plist["indentWidth"]?.int + self.indentWidth = plist[CodingKeys.indentWidth]?.int } override var archiveComment: String { diff --git a/Sources/XcodeProject/Objects/References/PBXReferenceProxy.swift b/Sources/XcodeProject/Objects/References/PBXReferenceProxy.swift index 23f5d7e..1428b80 100644 --- a/Sources/XcodeProject/Objects/References/PBXReferenceProxy.swift +++ b/Sources/XcodeProject/Objects/References/PBXReferenceProxy.swift @@ -31,8 +31,8 @@ public final class PBXReferenceProxy: PBXReference { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.fileType = plist["fileType"]?.string - self.remoteRef = objectCache.object(for: PBXGlobalID(rawValue: plist["remoteRef"]?.string)) as? PBXContainerItemProxy + self.fileType = plist[CodingKeys.fileType]?.string + self.remoteRef = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.remoteRef]?.string)) as? PBXContainerItemProxy self.sourceTree = sourceTree } diff --git a/Sources/XcodeProject/Objects/References/XCVersionGroup.swift b/Sources/XcodeProject/Objects/References/XCVersionGroup.swift index 6126d32..1f7ece8 100644 --- a/Sources/XcodeProject/Objects/References/XCVersionGroup.swift +++ b/Sources/XcodeProject/Objects/References/XCVersionGroup.swift @@ -33,8 +33,8 @@ public final class XCVersionGroup: PBXGroup { super.update(with: plist, objectCache: objectCache) guard - let currentVersion = objectCache.object(for: PBXGlobalID(rawValue: plist["currentVersion"]?.string)) as? PBXFileReference, - let versionGroupType = plist["versionGroupType"]?.string + let currentVersion = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.currentVersion]?.string)) as? PBXFileReference, + let versionGroupType = plist[CodingKeys.versionGroupType]?.string else { fatalError() } diff --git a/Sources/XcodeProject/Objects/Swift Package/XCLocalSwiftPackageReference.swift b/Sources/XcodeProject/Objects/Swift Package/XCLocalSwiftPackageReference.swift new file mode 100644 index 0000000..9ea965e --- /dev/null +++ b/Sources/XcodeProject/Objects/Swift Package/XCLocalSwiftPackageReference.swift @@ -0,0 +1,17 @@ +// +// XCLocalSwiftPackageReference.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +final class XCLocalSwiftPackageReference: XCSwiftPackageReference { + var packageName: String? + + override var archiveComment: String { + guard let packageName = packageName else { return super.archiveComment } + return #"\#(super.archiveComment) "\#(packageName)""# + } +} diff --git a/Sources/XcodeProject/Objects/Swift Package/XCRemoteSwiftPackageReference.swift b/Sources/XcodeProject/Objects/Swift Package/XCRemoteSwiftPackageReference.swift new file mode 100644 index 0000000..856f12a --- /dev/null +++ b/Sources/XcodeProject/Objects/Swift Package/XCRemoteSwiftPackageReference.swift @@ -0,0 +1,156 @@ +// +// XCRemoteSwiftPackageReference.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +public final class XCRemoteSwiftPackageReference: XCSwiftPackageReference { + public enum Requirement: Encodable { + private enum CodingKeys: String, CodingKey { + case kind + case minimumVersion + case maximumVersion + case version + case branch + case revision + } + + public struct Version: CustomStringConvertible, Encodable { + let major: Int + let minor: Int + let patch: Int + + public init(major: Int, minor: Int, patch: Int) { + self.major = major + self.minor = minor + self.patch = patch + } + + init?(_ value: String) { + var integerValues = value.components(separatedBy: ".").compactMap { Int($0) } + guard integerValues.count == 3 else { return nil } + self.init(major: integerValues.removeFirst(), minor: integerValues.removeFirst(), patch: integerValues.removeFirst()) + } + + public var description: String { + return "\(major).\(minor).\(patch)" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } + } + + public struct GitHash: Encodable, CustomStringConvertible { + private var value: String + init?(_ value: String) { + // TODO: verify [0-9a-zA-Z] and exactly 40 characters, SHA-1 hash + //value.trimmingCharacters(in: CharacterSet.) + self.value = value + } + + public var description: String { + return value + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } + } + case upToNextMinorVersion(Version) + case upToNextMajorVersion(Version) + case versionRange(from: Version, to: Version) + case exactVersion(Version) + case branch(String) + case revision(GitHash) + + init?(plist: PropertyList) { + guard let kind = plist[CodingKeys.kind]?.string else { return nil } + switch kind { + case "upToNextMinorVersion": + guard let versionString = plist[CodingKeys.minimumVersion]?.string, let version = Version(versionString) else { return nil } + self = .upToNextMinorVersion(version) + case "upToNextMajorVersion": + guard let versionString = plist[CodingKeys.minimumVersion]?.string, let version = Version(versionString) else { return nil } + self = .upToNextMajorVersion(version) + case "versionRange": + guard let minimumVersionString = plist[CodingKeys.minimumVersion]?.string, + let minimumVersion = Version(minimumVersionString), + let maximumVersionString = plist[CodingKeys.maximumVersion]?.string, + let maximumVersion = Version(maximumVersionString) else { + return nil + } + self = .versionRange(from: minimumVersion, to: maximumVersion) + case "exactVersion": + guard let versionString = plist[CodingKeys.version]?.string, let version = Version(versionString) else { return nil } + self = .exactVersion(version) + case "branch": + guard let branch = plist[CodingKeys.branch]?.string else { return nil } + self = .branch(branch) + case "revision": + guard let revisionString = plist[CodingKeys.revision]?.string, let revision = GitHash(revisionString) else { return nil } + self = .revision(revision) + default: + return nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .upToNextMinorVersion(let version): + try container.encode("upToNextMinorVersion", forKey: .kind) + try container.encode(version, forKey: .minimumVersion) + case .upToNextMajorVersion(let version): + try container.encode("upToNextMajorVersion", forKey: .kind) + try container.encode(version, forKey: .minimumVersion) + case .versionRange(let from, let to): + try container.encode("versionRange", forKey: .kind) + try container.encode(from, forKey: .minimumVersion) + try container.encode(to, forKey: .maximumVersion) + case .exactVersion(let version): + try container.encode("exactVersion", forKey: .kind) + try container.encode(version, forKey: .version) + case .branch(let branch): + try container.encode("branch", forKey: .kind) + try container.encode(branch, forKey: .branch) + case .revision(let revision): + try container.encode("revision", forKey: .kind) + try container.encode(revision, forKey: .revision) + } + } + } + + private enum CodingKeys: String, CodingKey { + case repositoryURL + case requirement + } + + var repositoryURL: String? + var requirement: Requirement? + + override var archiveComment: String { + guard let repositoryURL = repositoryURL, let url = URL(string: repositoryURL) else { return super.archiveComment } + return #"\#(super.archiveComment) "\#(url.deletingPathExtension().lastPathComponent)""# + } + + override func update(with plist: PropertyList, objectCache: ObjectCache) { + super.update(with: plist, objectCache: objectCache) + self.repositoryURL = plist[CodingKeys.repositoryURL]?.string + if let requirementPlist = plist[CodingKeys.requirement] { + self.requirement = Requirement(plist: requirementPlist) + } + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(repositoryURL, forKey: .repositoryURL) + try container.encodeIfPresent(requirement, forKey: .requirement) + } +} diff --git a/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageProductDependency.swift b/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageProductDependency.swift new file mode 100644 index 0000000..a70f02b --- /dev/null +++ b/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageProductDependency.swift @@ -0,0 +1,35 @@ +// +// XCSwiftPackageProductDependency.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +public final class XCSwiftPackageProductDependency: PBXProductDependency { + private enum CodingKeys: String, CodingKey { + case packageReference = "package" + case productName + } + + var packageReference: XCSwiftPackageReference? + var productName: String? + + override var archiveComment: String { + return productName ?? super.archiveComment + } + + override func update(with plist: PropertyList, objectCache: ObjectCache) { + super.update(with: plist, objectCache: objectCache) + self.productName = plist[CodingKeys.productName]?.string + self.packageReference = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.packageReference]?.string)) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(packageReference, forKey: .packageReference) + try container.encodeIfPresent(productName, forKey: .productName) + } +} diff --git a/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageReference.swift b/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageReference.swift new file mode 100644 index 0000000..755bff3 --- /dev/null +++ b/Sources/XcodeProject/Objects/Swift Package/XCSwiftPackageReference.swift @@ -0,0 +1,12 @@ +// +// XCSwiftPackageReference.swift +// +// +// Created by Geoffrey Foster on 2019-07-03. +// + +import Foundation + +public class XCSwiftPackageReference: PBXProjectItem { + +} diff --git a/Sources/XcodeProject/Objects/Targets/PBXLegacyTarget.swift b/Sources/XcodeProject/Objects/Targets/PBXLegacyTarget.swift index 955bd65..3f67cc5 100644 --- a/Sources/XcodeProject/Objects/Targets/PBXLegacyTarget.swift +++ b/Sources/XcodeProject/Objects/Targets/PBXLegacyTarget.swift @@ -22,10 +22,10 @@ public final class PBXLegacyTarget: PBXTarget { override func update(with plist: PropertyList, objectCache: ObjectCache) { super.update(with: plist, objectCache: objectCache) - self.buildArguments = plist["buildArgumentsString"]?.string - self.buildToolPath = plist["buildToolPath"]?.string - self.buildWorkingDirectory = plist["buildWorkingDirectory"]?.string - if let passBuildSettingsInEnvironment = plist["passBuildSettingsInEnvironment"]?.bool { + self.buildArguments = plist[CodingKeys.buildArgumentsString]?.string + self.buildToolPath = plist[CodingKeys.buildToolPath]?.string + self.buildWorkingDirectory = plist[CodingKeys.buildWorkingDirectory]?.string + if let passBuildSettingsInEnvironment = plist[CodingKeys.passBuildSettingsInEnvironment]?.bool { self.passBuildSettingsInEnvironment = passBuildSettingsInEnvironment } } diff --git a/Sources/XcodeProject/Objects/Targets/PBXNativeTarget.swift b/Sources/XcodeProject/Objects/Targets/PBXNativeTarget.swift index 06a6a51..c778124 100644 --- a/Sources/XcodeProject/Objects/Targets/PBXNativeTarget.swift +++ b/Sources/XcodeProject/Objects/Targets/PBXNativeTarget.swift @@ -39,7 +39,7 @@ public final class PBXNativeTarget: PBXTarget { super.update(with: plist, objectCache: objectCache) guard - let productType = ProductType(rawValue: plist["productType"]?.string ?? "") + let productType = ProductType(rawValue: plist[CodingKeys.productType]?.string ?? "") else { fatalError() } diff --git a/Sources/XcodeProject/Objects/Targets/PBXTarget.swift b/Sources/XcodeProject/Objects/Targets/PBXTarget.swift index 6da370f..5e0f10e 100644 --- a/Sources/XcodeProject/Objects/Targets/PBXTarget.swift +++ b/Sources/XcodeProject/Objects/Targets/PBXTarget.swift @@ -6,13 +6,14 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -public class PBXTarget: PBXObject, PBXContainer { +public class PBXTarget: PBXProjectItem, PBXContainer { private enum CodingKeys: String, CodingKey { case buildConfigurationList case buildPhases case buildRules case dependencies case name + case packageProductDependencies case productName case productReference } @@ -53,6 +54,7 @@ public class PBXTarget: PBXObject, PBXContainer { productReference?.parent = self } } + var packageProductDependencies: [XCSwiftPackageProductDependency]? override func willMove(from: PBXObject?) { super.willMove(from: from) @@ -76,28 +78,34 @@ public class PBXTarget: PBXObject, PBXContainer { super.update(with: plist, objectCache: objectCache) guard - let buildConfigurationListID = PBXGlobalID(rawValue: plist["buildConfigurationList"]?.string), + let buildConfigurationListID = PBXGlobalID(rawValue: plist[CodingKeys.buildConfigurationList]?.string), let buildConfigurationList = objectCache.object(for: buildConfigurationListID) as? XCConfigurationList, - let buildPhases = plist["buildPhases"]?.array, - let dependencies = plist["dependencies"]?.array, - let name = plist["name"]?.string, - let productName = plist["productName"]?.string + let buildPhases = plist[CodingKeys.buildPhases]?.array, + let dependencies = plist[CodingKeys.dependencies]?.array, + let name = plist[CodingKeys.name]?.string, + let productName = plist[CodingKeys.productName]?.string else { fatalError() } self.name = name self.productName = productName - self.productReference = objectCache.object(for: PBXGlobalID(rawValue: plist["productReference"]?.string)) as? PBXFileReference + self.productReference = objectCache.object(for: PBXGlobalID(rawValue: plist[CodingKeys.productReference]?.string)) as? PBXFileReference self.buildConfigurationList = buildConfigurationList self.buildPhases = buildPhases.compactMap { return objectCache.object(for: PBXGlobalID(rawValue: $0)) as? PBXBuildPhase } - self.buildRules = plist["buildRules"]?.array?.compactMap { + self.buildRules = plist[CodingKeys.buildRules]?.array?.compactMap { return objectCache.object(for: PBXGlobalID(rawValue: $0)) as? PBXBuildRule } self.dependencies = dependencies.compactMap { return objectCache.object(for: PBXGlobalID(rawValue: $0)) as? PBXTargetDependency } + + if let packageProductDependencies = plist[CodingKeys.packageProductDependencies]?.array { + self.packageProductDependencies = packageProductDependencies.map { PBXGlobalID(rawValue: $0) }.compactMap { + objectCache.object(for: $0) + } + } } override var archiveComment: String { @@ -113,6 +121,9 @@ public class PBXTarget: PBXObject, PBXContainer { dependencies.forEach { visitor.visit(object: $0) } + packageProductDependencies?.forEach { + visitor.visit(object: $0) + } visitor.visit(object: productReference) } @@ -124,6 +135,9 @@ public class PBXTarget: PBXObject, PBXContainer { try container.encodeIfPresent(buildRules, forKey: .buildRules) try container.encode(dependencies, forKey: .dependencies) try container.encodeIfPresent(name, forKey: .name) + if encoder.objectVersion >= .xcode11 { + try container.encodeIfPresent(packageProductDependencies, forKey: .packageProductDependencies) + } try container.encodeIfPresent(productName, forKey: .productName) try container.encodeIfPresent(productReference, forKey: .productReference) } diff --git a/Sources/XcodeProject/Objects/XCBuildConfiguration.swift b/Sources/XcodeProject/Objects/XCBuildConfiguration.swift index 3ca94a2..5e6474e 100644 --- a/Sources/XcodeProject/Objects/XCBuildConfiguration.swift +++ b/Sources/XcodeProject/Objects/XCBuildConfiguration.swift @@ -29,15 +29,15 @@ final class XCBuildConfiguration: PBXObject { super.update(with: plist, objectCache: objectCache) guard - let name = plist["name"]?.string, - let buildSettings = plist["buildSettings"]?.dictionary + let name = plist[CodingKeys.name]?.string, + let buildSettings = plist[CodingKeys.buildSettings]?.dictionary else { fatalError() } self.name = name self.buildSettings = BuildSettings(buildSettings) - self.baseConfigurationReference = objectCache.object(for: plist["baseConfigurationReference"]?.globalID) + self.baseConfigurationReference = objectCache.object(for: plist[CodingKeys.baseConfigurationReference]?.globalID) } override var archiveComment: String { diff --git a/Sources/XcodeProject/Objects/XCConfigurationList.swift b/Sources/XcodeProject/Objects/XCConfigurationList.swift index 8da89cf..571b367 100644 --- a/Sources/XcodeProject/Objects/XCConfigurationList.swift +++ b/Sources/XcodeProject/Objects/XCConfigurationList.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Geoffrey Foster. All rights reserved. // -final class XCConfigurationList: PBXObject { +final class XCConfigurationList: PBXProjectItem { private enum CodingKeys: String, CodingKey { case buildConfigurations case defaultConfigurationIsVisible @@ -45,15 +45,15 @@ final class XCConfigurationList: PBXObject { super.update(with: plist, objectCache: objectCache) guard - let buildConfigurations = PBXGlobalID.ids(from: plist["buildConfigurations"]?.array), - let defaultConfigurationIsVisibleString = plist["defaultConfigurationIsVisible"]?.string + let buildConfigurations = PBXGlobalID.ids(from: plist[CodingKeys.buildConfigurations]?.array), + let defaultConfigurationIsVisibleString = plist[CodingKeys.defaultConfigurationIsVisible]?.string else { fatalError() } self.buildConfigurations = buildConfigurations.compactMap { objectCache.object(for: $0) } self.defaultConfigurationIsVisible = Int(defaultConfigurationIsVisibleString) != 0 - self.defaultConfigurationName = plist["defaultConfigurationName"]?.string + self.defaultConfigurationName = plist[CodingKeys.defaultConfigurationName]?.string } override public func visit(_ visitor: ObjectVisitor) { diff --git a/Sources/XcodeProject/Objects/XCProjectReferenceInfo.swift b/Sources/XcodeProject/Objects/XCProjectReferenceInfo.swift index 9652f1b..5b18641 100644 --- a/Sources/XcodeProject/Objects/XCProjectReferenceInfo.swift +++ b/Sources/XcodeProject/Objects/XCProjectReferenceInfo.swift @@ -7,7 +7,7 @@ import Foundation -final class XCProjectReferenceInfo: Hashable, Encodable { +final class XCProjectReferenceInfo: Hashable, Encodable { // PBXProjectItem static func == (lhs: XCProjectReferenceInfo, rhs: XCProjectReferenceInfo) -> Bool { return lhs.productGroup == rhs.productGroup && lhs.projectReference == rhs.projectReference } diff --git a/Sources/XcodeProject/ProjectFile.swift b/Sources/XcodeProject/ProjectFile.swift index cc60f2d..9c7d09a 100644 --- a/Sources/XcodeProject/ProjectFile.swift +++ b/Sources/XcodeProject/ProjectFile.swift @@ -19,6 +19,7 @@ public enum ObjectVersion: UInt, Comparable, CaseIterable { case xcode8 = 48 case xcode93 = 50 case xcode10 = 51 + case xcode11 = 52 } public final class ProjectFile { diff --git a/Tests/XcodeProjectTests/XcodeProjectTests.swift b/Tests/XcodeProjectTests/XcodeProjectTests.swift index fa12f96..410cc51 100644 --- a/Tests/XcodeProjectTests/XcodeProjectTests.swift +++ b/Tests/XcodeProjectTests/XcodeProjectTests.swift @@ -41,7 +41,7 @@ class XcodeProjectTests: XCTestCase { try streamWriter.string.write(to: failureURL, atomically: true, encoding: .utf8) add(XCTAttachment(contentsOfFile: failureURL)) - XCTFail("Failed to generate matching output for \(url.lastPathComponent). Run ksdiff \(url.path) \(failureURL.path)") + XCTFail(#"Failed to generate matching output for \#(url.lastPathComponent). Run ksdiff "\#(url.path)" "\#(failureURL.path)""#) } }