Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ObjectReference #218

Merged
merged 3 commits into from
Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## next version

### Changed
- **Breaking:** Changed the return type of some helper functions that create or fetch PBXObjects to be `ObjectReference`, which includes the reference as well as the object https://github.com/xcodeswift/xcproj/pull/218 by @yonaskolb

## 3.0.0

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions Carthage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
BF1993286802 /* PBXHeadersBuildPhase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR4384942301 /* PBXHeadersBuildPhase.swift */; };
BF2033307201 /* PBXFileReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR8483796101 /* PBXFileReference.swift */; };
BF2033307202 /* PBXFileReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR8483796101 /* PBXFileReference.swift */; };
BF2203003301 /* ObjectReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR5708343501 /* ObjectReference.swift */; };
BF2203003302 /* ObjectReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR5708343501 /* ObjectReference.swift */; };
BF2241362001 /* Dictionary+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR6006730201 /* Dictionary+Extras.swift */; };
BF2241362002 /* Dictionary+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR6006730201 /* Dictionary+Extras.swift */; };
BF2311546501 /* Bool+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FR3255351901 /* Bool+Extras.swift */; };
Expand Down Expand Up @@ -165,6 +167,7 @@
FR5296652601 /* PBXProductType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PBXProductType.swift; sourceTree = "<group>"; };
FR5395076701 /* XCWorkspaceDataGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCWorkspaceDataGroup.swift; sourceTree = "<group>"; };
FR5460675201 /* PBXCopyFilesBuildPhase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PBXCopyFilesBuildPhase.swift; sourceTree = "<group>"; };
FR5708343501 /* ObjectReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectReference.swift; sourceTree = "<group>"; };
FR6006730201 /* Dictionary+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extras.swift"; sourceTree = "<group>"; };
FR6189630601 /* XCWorkspaceDataFileRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCWorkspaceDataFileRef.swift; sourceTree = "<group>"; };
FR6357036901 /* KeyedDecodingContainer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyedDecodingContainer+Additions.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -267,6 +270,7 @@
FR6006730201 /* Dictionary+Extras.swift */,
FR8599247801 /* JSONDecoding.swift */,
FR6357036901 /* KeyedDecodingContainer+Additions.swift */,
FR5708343501 /* ObjectReference.swift */,
FR3952162601 /* PBXAggregateTarget.swift */,
FR6980748501 /* PBXBuildFile.swift */,
FR4128075701 /* PBXBuildPhase.swift */,
Expand Down Expand Up @@ -446,6 +450,7 @@
BF2241362001 /* Dictionary+Extras.swift in Sources */,
BF6518149701 /* JSONDecoding.swift in Sources */,
BF1286225401 /* KeyedDecodingContainer+Additions.swift in Sources */,
BF2203003301 /* ObjectReference.swift in Sources */,
BF3873193301 /* PBXAggregateTarget.swift in Sources */,
BF4432284801 /* PBXBuildFile.swift in Sources */,
BF6638647201 /* PBXBuildPhase.swift in Sources */,
Expand Down Expand Up @@ -510,6 +515,7 @@
BF2241362002 /* Dictionary+Extras.swift in Sources */,
BF6518149702 /* JSONDecoding.swift in Sources */,
BF1286225402 /* KeyedDecodingContainer+Additions.swift in Sources */,
BF2203003302 /* ObjectReference.swift in Sources */,
BF3873193302 /* PBXAggregateTarget.swift in Sources */,
BF4432284802 /* PBXBuildFile.swift in Sources */,
BF6638647202 /* PBXBuildPhase.swift in Sources */,
Expand Down
26 changes: 26 additions & 0 deletions Sources/xcproj/ObjectReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

/// contains a PBXObject as well as it's reference
public class ObjectReference<T: PBXObject>: Equatable {

public let reference: String
public let object: T

public init(reference: String, object: T) {
self.reference = reference
self.object = object
}

public static func == (lhs: ObjectReference,
rhs: ObjectReference) -> Bool {
return lhs.reference == rhs.reference &&
lhs.object == rhs.object
}
}

extension Dictionary where Key == String, Value: PBXObject {

public var objectReferences: [ObjectReference<Value>] {
return self.map(ObjectReference.init)
}
}
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXBuildFile.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This element indicates a file reference that is used in a PBXBuildPhase (either as an include or resource).
final public class PBXBuildFile: PBXObject, Equatable {
final public class PBXBuildFile: PBXObject {

// MARK: - Attributes

Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXBuildPhase.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// An absctract class for all the build phase objects
public class PBXBuildPhase: PBXObject, Equatable {
public class PBXBuildPhase: PBXObject {

/// Default build action mask.
public static let defaultBuildActionMask: UInt = 2147483647
Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXBuildRule.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// A PBXBuildRule is used to specify a method for transforming an input file in to an output file(s).
final public class PBXBuildRule: PBXObject, Equatable {
final public class PBXBuildRule: PBXObject {

// MARK: - Attributes

Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXContainerItemProxy.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This is the element to decorate a target item.
final public class PBXContainerItemProxy: PBXObject, Equatable {
final public class PBXContainerItemProxy: PBXObject {

public enum ProxyType: UInt, Decodable {
case nativeTarget = 1
Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXFileElement.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This element is an abstract parent for file and group elements.
public class PBXFileElement: PBXObject, Equatable, PlistSerializable {
public class PBXFileElement: PBXObject, PlistSerializable {

// MARK: - Attributes

Expand Down
7 changes: 6 additions & 1 deletion Sources/xcproj/PBXObject.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Class that represents a project element.
public class PBXObject: Decodable {
public class PBXObject: Decodable, Equatable {

// MARK: - Init

Expand All @@ -19,6 +19,11 @@ public class PBXObject: Decodable {
return String(describing: self)
}

public static func == (lhs: PBXObject,
rhs: PBXObject) -> Bool {
return true
}

//swiftlint:disable function_body_length
public static func parse(reference: String,
dictionary: [String: Any]) throws -> PBXObject {
Expand Down
67 changes: 33 additions & 34 deletions Sources/xcproj/PBXProjObjects+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ public extension PBXProj.Objects {
///
/// - Parameter name: target name.
/// - Returns: all existing targets with the given name.
public func targets(named name: String) -> [PBXTarget] {
var targets: [PBXTarget] = []
targets.append(contentsOf: Array(nativeTargets.values) as [PBXTarget])
targets.append(contentsOf: Array(legacyTargets.values) as [PBXTarget])
targets.append(contentsOf: Array(aggregateTargets.values) as [PBXTarget])
return targets.filter { $0.name == name }
public func targets(named name: String) -> [ObjectReference<PBXTarget>] {
var targets: [ObjectReference<PBXTarget>] = []
targets.append(contentsOf: nativeTargets.map(ObjectReference.init))
targets.append(contentsOf: legacyTargets.map(ObjectReference.init))
targets.append(contentsOf: aggregateTargets.map(ObjectReference.init))
return targets.filter { $0.object.name == name }
}

/// Retruns target's sources build phase.
Expand All @@ -29,10 +29,10 @@ public extension PBXProj.Objects {
///
/// - Parameter target: target object.
/// - Returns: all files in target's sources build phase, or empty array if sources build phase is not found.
public func sourceFiles(target: PBXTarget) -> [(reference: String, file: PBXFileElement)] {
public func sourceFiles(target: PBXTarget) -> [ObjectReference<PBXFileElement>] {
return sourcesBuildPhase(target: target)?.files
.flatMap({ buildFiles[$0]?.fileRef })
.flatMap({ fileRef in getFileElement(reference: fileRef).map({ (fileRef, $0) })})
.flatMap { buildFiles[$0]?.fileRef }
.flatMap { fileRef in getFileElement(reference: fileRef).map { ObjectReference(reference: fileRef, object: $0) }}
?? []
}

Expand All @@ -41,11 +41,11 @@ public extension PBXProj.Objects {
/// - Parameter groupName: group name.
/// - Parameter inGroup: parent group.
/// - Returns: group with the given name contained in the given parent group and its reference.
public func group(named groupName: String, inGroup: PBXGroup) -> (reference: String, group: PBXGroup)? {
public func group(named groupName: String, inGroup: PBXGroup) -> ObjectReference<PBXGroup>? {
let children = inGroup.children
return groups.first(where: {
children.contains($0.key) && ($0.value.name == groupName || $0.value.path == groupName)
}).map({ ($0.0, $0.1) })
return groups.objectReferences.first {
children.contains($0.reference) && ($0.object.name == groupName || $0.object.path == groupName)
}
}

/// Adds new group with the give name to the given parent group.
Expand All @@ -58,17 +58,17 @@ public extension PBXProj.Objects {
/// - toGroup: parent group
/// - options: additional options, default is empty set.
/// - Returns: all new or existing groups in the path and their references.
public func addGroup(named groupName: String, to toGroup: PBXGroup, options: GroupAddingOptions = []) -> [(reference: String, group: PBXGroup)] {
public func addGroup(named groupName: String, to toGroup: PBXGroup, options: GroupAddingOptions = []) -> [ObjectReference<PBXGroup>] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, just thinking. This could either be [String: PBXGroup] or [ObjectReference<PBXGroup>]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think array is better here, to make it is easier to inspect all created groups in order that will match their path order. Also I think with dictionary it will be impossible to get the last created group which is likely to be used in other methods as input parameter.

return addGroups(groupName.components(separatedBy: "/"), to: toGroup, options: options)
}

private func addGroups(_ groupNames: [String], to toGroup: PBXGroup, options: GroupAddingOptions) -> [(reference: String, group: PBXGroup)] {
private func addGroups(_ groupNames: [String], to toGroup: PBXGroup, options: GroupAddingOptions) -> [ObjectReference<PBXGroup>] {
guard !groupNames.isEmpty else { return [] }
let newGroup = createOrGetGroup(named: groupNames[0], in: toGroup, options: options)
return [newGroup] + addGroups(Array(groupNames.dropFirst()), to: newGroup.1, options: options)
return [newGroup] + addGroups(Array(groupNames.dropFirst()), to: newGroup.object, options: options)
}

private func createOrGetGroup(named groupName: String, in parentGroup: PBXGroup, options: GroupAddingOptions) -> (reference: String, group: PBXGroup) {
private func createOrGetGroup(named groupName: String, in parentGroup: PBXGroup, options: GroupAddingOptions) -> ObjectReference<PBXGroup> {
if let existingGroup = group(named: groupName, inGroup: parentGroup) {
return existingGroup
}
Expand All @@ -82,7 +82,7 @@ public extension PBXProj.Objects {
let reference = generateReference(newGroup, groupName)
addObject(newGroup, reference: reference)
parentGroup.children.append(reference)
return (reference, newGroup)
return ObjectReference(reference: reference, object: newGroup)
}

/// Adds file at the give path to the project and given group or returns existing file and its reference.
Expand All @@ -97,7 +97,7 @@ public extension PBXProj.Objects {
at filePath: Path,
toGroup: PBXGroup,
sourceTree: PBXSourceTree = .group,
sourceRoot: Path) throws -> (reference: String, file: PBXFileReference) {
sourceRoot: Path) throws -> ObjectReference<PBXFileReference> {

guard filePath.isFile else {
throw XCodeProjEditingError.notAFile(path: filePath)
Expand All @@ -108,15 +108,14 @@ public extension PBXProj.Objects {
}
let groupPath = fullPath(fileElement: toGroup, reference: groupReference, sourceRoot: sourceRoot)

if let existingFileReference = fileReferences.first(where: {
filePath == fullPath(fileElement: $0.value, reference: $0.key, sourceRoot: sourceRoot)
if let existingFileReference = fileReferences.objectReferences.first(where: {
filePath == fullPath(fileElement: $0.object, reference: $0.reference, sourceRoot: sourceRoot)
}) {
let file = (existingFileReference.key, existingFileReference.value)
if !toGroup.children.contains(file.0) {
file.1.path = groupPath.flatMap({ filePath.relativeTo($0) })?.string
toGroup.children.append(file.0)
if !toGroup.children.contains(existingFileReference.reference) {
existingFileReference.object.path = groupPath.flatMap { filePath.relativeTo($0) }?.string
toGroup.children.append(existingFileReference.reference)
}
return file
return existingFileReference
}

let path: Path?
Expand Down Expand Up @@ -145,7 +144,7 @@ public extension PBXProj.Objects {
toGroup.children.append(reference)
}

return (reference, fileReference)
return ObjectReference(reference: reference, object: fileReference)
}

/// Adds file to the given target's sources build phase or returns existing build file and its reference.
Expand All @@ -154,17 +153,17 @@ public extension PBXProj.Objects {
/// - Parameter target: target object
/// - Parameter reference: file reference
/// - Returns: new or existing build file and its reference
public func addBuildFile(toTarget target: PBXTarget, reference: String) -> (reference: String, file: PBXBuildFile)? {
public func addBuildFile(toTarget target: PBXTarget, reference: String) -> ObjectReference<PBXBuildFile>? {
guard let sourcesBuildPhase = sourcesBuildPhase(target: target) else { return nil }
if let existingBuildFile = buildFiles.first(where: { $0.value.fileRef == reference }) {
return (existingBuildFile.key, existingBuildFile.value)
if let existingBuildFile = buildFiles.objectReferences.first(where: { $0.object.fileRef == reference }) {
return existingBuildFile
}

let buildFile = PBXBuildFile(fileRef: reference)
let reference = generateReference(buildFile, reference)
addObject(buildFile, reference: reference)
sourcesBuildPhase.files.append(reference)
return (reference, buildFile)
return ObjectReference(reference: reference, object: buildFile)
}

/// Returns full path of the file element.
Expand Down Expand Up @@ -361,11 +360,11 @@ extension PBXProj.Objects {
///
/// - Parameter reference: configuration list reference.
/// - Returns: target or project with the given configuration list.
func objectWithConfigurationList(reference: String) -> PBXObject? {
if let project = projects.first(where: { $0.value.buildConfigurationList == reference})?.value {
func objectWithConfigurationList(reference: String) -> ObjectReference<PBXObject>? {
if let project: ObjectReference<PBXObject> = projects.first(where: { $0.value.buildConfigurationList == reference}).map(ObjectReference.init) {
return project
}
return nativeTargets.first(where: { $0.value.buildConfigurationList == reference})?.value
return nativeTargets.first(where: { $0.value.buildConfigurationList == reference}).map(ObjectReference.init)
}

}
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXProject.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

final public class PBXProject: PBXObject, Equatable {
final public class PBXProject: PBXObject {

// MARK: - Attributes

Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXReferenceProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
/// A proxy for another object which might belong to another project
/// contained in the same workspace of the document.
/// This class is referenced by PBXTargetDependency.
final public class PBXReferenceProxy: PBXObject, Equatable {
final public class PBXReferenceProxy: PBXObject {

// MARK: - Attributes

Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXTarget.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This element is an abstract parent for specialized targets.
public class PBXTarget: PBXObject, Equatable {
public class PBXTarget: PBXObject {

/// Target build configuration list.
public var buildConfigurationList: String?
Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/PBXTargetDependency.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This is the element for referencing other targets through content proxies.
final public class PBXTargetDependency: PBXObject, Equatable {
final public class PBXTargetDependency: PBXObject {

// MARK: - Attributes

Expand Down
2 changes: 1 addition & 1 deletion Sources/xcproj/XCBuildConfiguration.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This is the element for listing build configurations.
final public class XCBuildConfiguration: PBXObject, Equatable {
final public class XCBuildConfiguration: PBXObject {

// MARK: - Attributes

Expand Down
8 changes: 4 additions & 4 deletions Sources/xcproj/XCConfigurationList.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// This is the element for listing build configurations.
final public class XCConfigurationList: PBXObject, Equatable {
final public class XCConfigurationList: PBXObject {

// MARK: - Attributes

Expand Down Expand Up @@ -75,10 +75,10 @@ extension XCConfigurationList: PlistSerializable {
}

private func plistComment(proj: PBXProj, reference: String) -> String? {
let object = proj.objects.objectWithConfigurationList(reference: reference)
if let project = object as? PBXProject {
let objectReference = proj.objects.objectWithConfigurationList(reference: reference)
if let project = objectReference?.object as? PBXProject {
return "Build configuration list for PBXProject \"\(project.name)\""
} else if let target = object as? PBXNativeTarget {
} else if let target = objectReference?.object as? PBXNativeTarget {
return "Build configuration list for PBXNativeTarget \"\(target.name)\""
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion Tests/xcprojTests/PBXProjObjectsHelpersSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class PBXProjObjectsHelpersSpec: XCTestCase {
subject.addObject(nativeTarget, reference: "1")
subject.addObject(legacyTarget, reference: "2")
subject.addObject(aggregateTarget, reference: "3")
let got = subject.targets(named: "test")
let got = subject.targets(named: "test").map { $0.object }
XCTAssertTrue(got.contains(nativeTarget))
XCTAssertTrue(got.contains(legacyTarget))
XCTAssertTrue(got.contains(aggregateTarget))
Expand Down
Loading