Skip to content

Commit

Permalink
Add Support for Build Carbon Resources Build Phase
Browse files Browse the repository at this point in the history
Some macOS apps still build legacy Carbon resources. Fortunately the Xcode project file models this in the same way as the abstract PBXBuildPhase so implementation of the build phase is as easy as adding a subclass, updating the BuildPhase enum, updating PBXProj, and adding decoding/encoding support.

In addition to updating the tests, verified xcproj correctly round trips a project with this build phase.
  • Loading branch information
briantkelley committed Dec 19, 2017
1 parent e10f90e commit fb7c56b
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Added
- Add breakpoint `condition` parameter by [@alexruperez](https://github.com/alexruperez).
- Support Xcode Extension product type https://github.com/xcodeswift/xcproj/pull/190 by @briantkelley
- Support for the legacy Build Carbon Resources build phase https://github.com/xcodeswift/xcproj/pull/196 by @briantkelley

## 1.7.0

Expand Down
2 changes: 2 additions & 0 deletions Sources/xcproj/BuildPhase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import Foundation
/// - copyFiles: files.
/// - runScript: scripts.
/// - headers: headers.
/// - carbonResources: build legacy Carbon resources.
public enum BuildPhase: String {
case sources = "Sources"
case frameworks = "Frameworks"
case resources = "Resources"
case copyFiles = "CopyFiles"
case runScript = "Run Script"
case headers = "Headers"
case carbonResources = "Rez"
}
2 changes: 2 additions & 0 deletions Sources/xcproj/PBXObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public class PBXObject: Referenceable, Decodable {
return try decoder.decode(PBXReferenceProxy.self, from: data)
case XCVersionGroup.isa:
return try decoder.decode(XCVersionGroup.self, from: data)
case PBXRezBuildPhase.isa:
return try decoder.decode(PBXRezBuildPhase.self, from: data)
default:
throw PBXObjectError.unknownElement(isa)
}
Expand Down
8 changes: 7 additions & 1 deletion Sources/xcproj/PBXProj.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final public class PBXProj: Decodable {
public var frameworksBuildPhases: ReferenceableCollection<PBXFrameworksBuildPhase> = [:]
public var headersBuildPhases: ReferenceableCollection<PBXHeadersBuildPhase> = [:]
public var sourcesBuildPhases: ReferenceableCollection<PBXSourcesBuildPhase> = [:]
public var carbonResourcesBuildPhases: ReferenceableCollection<PBXRezBuildPhase> = [:]

// MARK: - Computed Properties
public var buildPhases: ReferenceableCollection<PBXBuildPhase> {
Expand All @@ -37,6 +38,7 @@ final public class PBXProj: Decodable {
phases += self.resourcesBuildPhases.referenceValues as [PBXBuildPhase]
phases += self.frameworksBuildPhases.referenceValues as [PBXBuildPhase]
phases += self.headersBuildPhases.referenceValues as [PBXBuildPhase]
phases += self.carbonResourcesBuildPhases.referenceValues as [PBXBuildPhase]
return Dictionary(references: phases)
}

Expand Down Expand Up @@ -68,7 +70,8 @@ final public class PBXProj: Decodable {
lhs.fileReferences == rhs.fileReferences &&
lhs.projects == rhs.projects &&
lhs.versionGroups == rhs.versionGroups &&
lhs.referenceProxies == rhs.referenceProxies
lhs.referenceProxies == rhs.referenceProxies &&
lhs.carbonResourcesBuildPhases == rhs.carbonResourcesBuildPhases
}

// MARK: - Public Methods
Expand Down Expand Up @@ -96,6 +99,7 @@ final public class PBXProj: Decodable {
case let object as PBXProject: projects.append(object)
case let object as XCVersionGroup: versionGroups.append(object)
case let object as PBXReferenceProxy: referenceProxies.append(object)
case let object as PBXRezBuildPhase: carbonResourcesBuildPhases.append(object)
default: fatalError("Unhandled PBXObject type for \(object), this is likely a bug / todo")
}
}
Expand Down Expand Up @@ -158,6 +162,8 @@ final public class PBXProj: Decodable {
return object
} else if let object = sourcesBuildPhases[reference] {
return object
} else if let object = carbonResourcesBuildPhases[reference] {
return object
} else {
return nil
}
Expand Down
1 change: 1 addition & 0 deletions Sources/xcproj/PBXProjEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final class PBXProjEncoder {
write(section: "PBXLegacyTarget", proj: proj, object: proj.objects.legacyTargets)
write(section: "PBXProject", proj: proj, object: proj.objects.projects)
write(section: "PBXResourcesBuildPhase", proj: proj, object: proj.objects.resourcesBuildPhases)
write(section: "PBXRezBuildPhase", proj: proj, object: proj.objects.carbonResourcesBuildPhases)
write(section: "PBXShellScriptBuildPhase", proj: proj, object: proj.objects.shellScriptBuildPhases)
write(section: "PBXSourcesBuildPhase", proj: proj, object: proj.objects.sourcesBuildPhases)
write(section: "PBXTargetDependency", proj: proj, object: proj.objects.targetDependencies)
Expand Down
6 changes: 6 additions & 0 deletions Sources/xcproj/PBXProjObjects+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ extension PBXProj.Objects {
return .copyFiles
} else if headersBuildPhases.contains(where: { _, val in val.files.contains(buildFileReference)}) {
return .headers
} else if carbonResourcesBuildPhases.contains(where: { _, val in val.files.contains(buildFileReference)}) {
return .carbonResources
}
return nil
}
Expand All @@ -89,6 +91,8 @@ extension PBXProj.Objects {
return .runScript
} else if headersBuildPhases.contains(reference: buildPhaseReference) {
return .headers
} else if carbonResourcesBuildPhases.contains(reference: buildPhaseReference) {
return .carbonResources
}
return nil
}
Expand All @@ -110,6 +114,8 @@ extension PBXProj.Objects {
return shellScriptBuildPhase.name ?? "ShellScript"
} else if headersBuildPhases.contains(reference: buildPhaseReference) {
return "Headers"
} else if carbonResourcesBuildPhases.contains(reference: buildPhaseReference) {
return "Rez"
}
return nil
}
Expand Down
29 changes: 29 additions & 0 deletions Sources/xcproj/PBXRezBuildPhase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

/// This is the element for the Build Carbon Resources build phase.
final public class PBXRezBuildPhase: PBXBuildPhase {

public override var buildPhase: BuildPhase {
return .carbonResources
}

public static func == (lhs: PBXRezBuildPhase,
rhs: PBXRezBuildPhase) -> Bool {
return lhs.reference == rhs.reference &&
lhs.buildActionMask == rhs.buildActionMask &&
lhs.files == rhs.files &&
lhs.runOnlyForDeploymentPostprocessing == rhs.runOnlyForDeploymentPostprocessing
}
}

// MARK: - PBXRezBuildPhase Extension (PlistSerializable)

extension PBXRezBuildPhase: PlistSerializable {

func plistKeyAndValue(proj: PBXProj) -> (key: CommentedString, value: PlistValue) {
var dictionary: [CommentedString: PlistValue] = plistValues(proj: proj)
dictionary["isa"] = .string(CommentedString(PBXRezBuildPhase.isa))
return (key: CommentedString(self.reference, comment: "Rez"), value: .dictionary(dictionary))
}

}
8 changes: 8 additions & 0 deletions Tests/xcprojTests/BuildPhaseSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class BuildPhaseSpecs: XCTestCase {
XCTAssertEqual(BuildPhase.headers.rawValue, "Headers")
}

func test_carbonResources_hasTheCorrectRawValue() {
XCTAssertEqual(BuildPhase.carbonResources.rawValue, "Rez")
}

func test_sources_hasTheCorrectBuildPhase() {
XCTAssertEqual(BuildPhase.sources, PBXSourcesBuildPhase(reference: "").buildPhase)
}
Expand All @@ -52,4 +56,8 @@ class BuildPhaseSpecs: XCTestCase {
XCTAssertEqual(BuildPhase.headers, PBXHeadersBuildPhase(reference: "").buildPhase)
}

func test_carbonResources_hasTheCorrectBuildPhase() {
XCTAssertEqual(BuildPhase.carbonResources, PBXRezBuildPhase(reference: "").buildPhase)
}

}
3 changes: 2 additions & 1 deletion Tests/xcprojTests/PBXProjSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ final class PBXProjSpec: XCTestCase {
subject.objects.addObject(PBXResourcesBuildPhase(reference: "ref4"))
subject.objects.addObject(PBXFrameworksBuildPhase(reference: "ref5"))
subject.objects.addObject(PBXHeadersBuildPhase(reference: "ref6"))
XCTAssertEqual(subject.objects.buildPhases.count, 6)
subject.objects.addObject(PBXRezBuildPhase(reference: "ref7"))
XCTAssertEqual(subject.objects.buildPhases.count, 7)
}
}

Expand Down
32 changes: 32 additions & 0 deletions Tests/xcprojTests/PBXRezBuildPhaseSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import XCTest
import xcproj

final class PBXRezBuildPhaseSpec: XCTestCase {

var subject: PBXRezBuildPhase!

override func setUp() {
super.setUp()
subject = PBXRezBuildPhase(reference: "ref",
files: ["123"],
runOnlyForDeploymentPostprocessing: 0)
}

func test_init_initializesTheBuildPhaseWithTheRightValues() {
XCTAssertEqual(subject.reference, "ref")
XCTAssertEqual(subject.files, ["123"])
XCTAssertEqual(subject.runOnlyForDeploymentPostprocessing, 0)
}

func test_isa_returnsTheCorrectValue() {
XCTAssertEqual(PBXRezBuildPhase.isa, "PBXRezBuildPhase")
}

func test_equals_returnsTheCorrectValue() {
let another = PBXResourcesBuildPhase(reference: "ref",
files: ["123"],
runOnlyForDeploymentPostprocessing: 0)
XCTAssertEqual(subject, another)
}
}

0 comments on commit fb7c56b

Please sign in to comment.