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

feat: support PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet #894

1 change: 1 addition & 0 deletions Fixtures/SynchronizedRootGroups/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.xcodeproj/xcuserdata
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 73;
objects = {

/* Begin PBXCopyFilesBuildPhase section */
F841A9CA2D63AFBB00059ED6 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
6CF05B8C2C53F5F200EF267F /* SynchronizedRootGroups.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SynchronizedRootGroups.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
Expand All @@ -20,19 +32,23 @@
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */,
);
explicitFileTypes = {
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
XPCService.xpc = (
RemoveHeadersOnCopy,
);
};
explicitFolders = (
buildPhase = F841A9CA2D63AFBB00059ED6 /* CopyFiles */;
membershipExceptions = (
XPCService.xpc,
);
path = SynchronizedRootGroups;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -83,6 +99,7 @@
6CF05B882C53F5F200EF267F /* Sources */,
6CF05B892C53F5F200EF267F /* Frameworks */,
6CF05B8A2C53F5F200EF267F /* Resources */,
F841A9CA2D63AFBB00059ED6 /* CopyFiles */,
);
buildRules = (
);
Expand Down Expand Up @@ -114,14 +131,14 @@
};
};
buildConfigurationList = 6CF05B862C53F5F200EF267F /* Build configuration list for PBXProject "SynchronizedRootGroups" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 6CF05B822C53F5F200EF267F;
preferredProjectObjectVersion = 60;
productRefGroup = 6CF05B8D2C53F5F200EF267F /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Class representing an element that may contain other elements.
public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSerializable {
public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXFileSystemSynchronizedExceptionSet, PlistSerializable {
// MARK: - Attributes

/// A list of relative paths to children subfolders for which exceptions are applied.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Foundation

/// Common class for exception sets, such as `PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet` and `PBXFileSystemSynchronizedBuildFileExceptionSet`
public class PBXFileSystemSynchronizedExceptionSet: PBXObject {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Foundation

public class PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: PBXFileSystemSynchronizedExceptionSet, PlistSerializable {
// MARK: - Attributes

/// A list of relative paths to children subfolders for which exceptions are applied.
public var membershipExceptions: [String]?

/// Build phase that this exception set applies to.
public var buildPhase: PBXBuildPhase! {
get {
buildPhaseReference.getObject() as? PBXBuildPhase
}
set {
buildPhaseReference = newValue.reference
}
}

/// Attributes by relative path.
/// Every item in the list is the relative path inside the root synchronized group.
/// For example `RemoveHeadersOnCopy` and `CodeSignOnCopy`.
public var attributesByRelativePath: [String: [String]]?

var buildPhaseReference: PBXObjectReference

// MARK: - Init

public init(
buildPhase: PBXBuildPhase,
membershipExceptions: [String]?,
attributesByRelativePath: [String: [String]]?
) {
buildPhaseReference = buildPhase.reference
self.membershipExceptions = membershipExceptions
self.attributesByRelativePath = attributesByRelativePath
super.init()
}

// MARK: - Decodable

fileprivate enum CodingKeys: String, CodingKey {
case buildPhase
case membershipExceptions
case attributesByRelativePath
}

public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let referenceRepository = decoder.context.objectReferenceRepository
let objects = decoder.context.objects
let buildPhaseReference: String = try container.decode(.buildPhase)
self.buildPhaseReference = referenceRepository.getOrCreate(reference: buildPhaseReference, objects: objects)
membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
attributesByRelativePath = try container.decodeIfPresent(.attributesByRelativePath)
try super.init(from: decoder)
}

// MARK: - Equatable

override func isEqual(to object: Any?) -> Bool {
guard let rhs = object as? PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet else {
return false
}
return isEqual(to: rhs)
}

// MARK: - PlistSerializable

func plistKeyAndValue(proj _: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
var dictionary: [CommentedString: PlistValue] = [:]
dictionary["isa"] = .string(CommentedString(type(of: self).isa))
if let membershipExceptions {
dictionary["membershipExceptions"] = .array(membershipExceptions.map { .string(CommentedString($0)) })
}
if let attributesByRelativePath {
dictionary["attributesByRelativePath"] = .dictionary(Dictionary(uniqueKeysWithValues: attributesByRelativePath.map { key, value in
(CommentedString(key), .array(value.map { .string(CommentedString($0)) }))
}))
}
dictionary["buildPhase"] = .string(CommentedString(buildPhase.reference.value, comment: buildPhase.name() ?? "CopyFiles"))
return (key: CommentedString(reference, comment: "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet"), value: .dictionary(dictionary))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {

/// It returns a list of exception objects that override the configuration for some children
/// in the synchronized root group.
public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? {
public var exceptions: [PBXFileSystemSynchronizedExceptionSet]? {
set {
exceptionsReferences = newValue?.references()
}
Expand Down Expand Up @@ -47,7 +47,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
tabWidth: UInt? = nil,
wrapsLines: Bool? = nil,
explicitFileTypes: [String: String] = [:],
exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
exceptions: [PBXFileSystemSynchronizedExceptionSet] = [],
explicitFolders: [String] = []) {
self.explicitFileTypes = explicitFileTypes
exceptionsReferences = exceptions.references()
Expand Down Expand Up @@ -83,14 +83,14 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {

// MARK: - PlistSerializable

override var multiline: Bool { true }
override var multiline: Bool { (exceptions?.count ?? 0) < 2 }

override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
dictionary["isa"] = .string(CommentedString(type(of: self).isa))
if let exceptionsReferences, !exceptionsReferences.isEmpty {
dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
.string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
if let exceptions, !exceptions.isEmpty {
dictionary["exceptions"] = .array(exceptions.map { exception in
.string(CommentedString(exception.reference.value, comment: type(of: exception).isa))
})
}
if let explicitFileTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct PBXObjectDictionaryEntry: Decodable {
case XCSwiftPackageProductDependency.isa: try XCSwiftPackageProductDependency(from: decoder)
case PBXFileSystemSynchronizedRootGroup.isa: try PBXFileSystemSynchronizedRootGroup(from: decoder)
case PBXFileSystemSynchronizedBuildFileExceptionSet.isa: try PBXFileSystemSynchronizedBuildFileExceptionSet(from: decoder)
case PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.isa: try PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet(from: decoder)
default:
throw PBXObjectError.unknownElement(isa)
}
Expand Down
15 changes: 14 additions & 1 deletion Sources/XcodeProj/Objects/Project/PBXObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ class PBXObjects: Equatable {
lock.whileLocked { _fileSystemSynchronizedBuildFileExceptionSets }
}

private var _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: [PBXObjectReference: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet] = [:]
var fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet: [PBXObjectReference: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet] {
lock.whileLocked { _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet }
}

// XCSwiftPackageProductDependency

/// Initializes the project objects container
Expand Down Expand Up @@ -185,7 +190,8 @@ class PBXObjects: Equatable {
lhs.swiftPackageProductDependencies == rhs._swiftPackageProductDependencies &&
lhs.remoteSwiftPackageReferences == rhs.remoteSwiftPackageReferences &&
lhs.fileSystemSynchronizedRootGroups == rhs.fileSystemSynchronizedRootGroups &&
lhs.fileSystemSynchronizedBuildFileExceptionSets == rhs.fileSystemSynchronizedBuildFileExceptionSets
lhs.fileSystemSynchronizedBuildFileExceptionSets == rhs.fileSystemSynchronizedBuildFileExceptionSets &&
lhs.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet == rhs.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet
}

// MARK: - Helpers
Expand Down Expand Up @@ -232,6 +238,8 @@ class PBXObjects: Equatable {
case let object as XCSwiftPackageProductDependency: _swiftPackageProductDependencies[objectReference] = object
case let object as PBXFileSystemSynchronizedRootGroup: _fileSystemSynchronizedRootGroups[objectReference] = object
case let object as PBXFileSystemSynchronizedBuildFileExceptionSet: _fileSystemSynchronizedBuildFileExceptionSets[objectReference] = object
case let object as PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet:
_fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet[objectReference] = object
default: fatalError("Unhandled PBXObject type for \(object), this is likely a bug / todo")
}
}
Expand Down Expand Up @@ -296,6 +304,8 @@ class PBXObjects: Equatable {
return _fileSystemSynchronizedRootGroups.remove(at: index).value
} else if let index = fileSystemSynchronizedBuildFileExceptionSets.index(forKey: reference) {
return _fileSystemSynchronizedBuildFileExceptionSets.remove(at: index).value
} else if let index = fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.index(forKey: reference) {
return _fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.remove(at: index).value
}

return nil
Expand Down Expand Up @@ -363,6 +373,8 @@ class PBXObjects: Equatable {
object
} else if let object = fileSystemSynchronizedBuildFileExceptionSets[reference] {
object
} else if let object = fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet[reference] {
object
} else {
nil
}
Expand Down Expand Up @@ -456,5 +468,6 @@ extension PBXObjects {
swiftPackageProductDependencies.values.forEach(closure)
fileSystemSynchronizedRootGroups.values.forEach(closure)
fileSystemSynchronizedBuildFileExceptionSets.values.forEach(closure)
fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet.values.forEach(closure)
}
}
6 changes: 6 additions & 0 deletions Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ final class PBXProjEncoder {
outputSettings: outputSettings,
stateHolder: &stateHolder,
to: &output)
try write(section: "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet",
proj: proj,
objects: proj.objects.fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet,
outputSettings: outputSettings,
stateHolder: &stateHolder,
to: &output)
try write(section: "PBXFileSystemSynchronizedRootGroup",
proj: proj,
objects: proj.objects.fileSystemSynchronizedRootGroups,
Expand Down
9 changes: 9 additions & 0 deletions Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,12 @@ extension PBXFileSystemSynchronizedBuildFileExceptionSet {
return super.isEqual(to: rhs)
}
}

extension PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet {
/// :nodoc:
func isEqual(to rhs: PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet) -> Bool {
if membershipExceptions != rhs.membershipExceptions { return false }
if buildPhaseReference != rhs.buildPhaseReference { return false }
return super.isEqual(to: rhs)
}
}
37 changes: 25 additions & 12 deletions Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,18 +294,7 @@ class PBXProjEncoderTests: XCTestCase {
let lines = lines(fromFile: encodeProject(settings: settings))

let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedRootGroup section */")
var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {", after: beginGroup)
line = lines.validate(line: "isa = PBXFileSystemSynchronizedRootGroup;", after: line)
line = lines.validate(line: "exceptions = (", after: line)
line = lines.validate(line: "6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */,", after: line)
line = lines.validate(line: ");", after: line)
line = lines.validate(line: "explicitFileTypes = {", after: line)
line = lines.validate(line: "};", after: line)
line = lines.validate(line: "explicitFolders = (", after: line)
line = lines.validate(line: ");", after: line)
line = lines.validate(line: "path = SynchronizedRootGroups;", after: line)
line = lines.validate(line: "sourceTree = \"<group>\";", after: line)
line = lines.validate(line: "};", after: line)
var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = \"<group>\"; };", after: beginGroup)
line = lines.validate(line: "/* End PBXFileSystemSynchronizedRootGroup section */", after: line)
}

Expand All @@ -328,6 +317,30 @@ class PBXProjEncoderTests: XCTestCase {
line = lines.validate(line: "/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */", after: line)
}

// MARK: - File system synchronized group build phase membership exception set

func test_fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSets_when_projectWithFileSystemSynchronizedRootGroups() throws {
// Given
try loadSynchronizedRootGroups()
let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst)
let lines = lines(fromFile: encodeProject(settings: settings))

let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */")
var line = lines.validate(line: "F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {", after: beginGroup)
line = lines.validate(line: "isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;", after: line)
line = lines.validate(line: "attributesByRelativePath = {", after: line)
line = lines.validate(line: "XPCService.xpc = (", after: line)
line = lines.validate(line: "RemoveHeadersOnCopy,", after: line)
line = lines.validate(line: ");", after: line)
line = lines.validate(line: "};", after: line)
line = lines.validate(line: "buildPhase = F841A9CA2D63AFBB00059ED6 /* CopyFiles */;", after: line)
line = lines.validate(line: "membershipExceptions = (", after: line)
line = lines.validate(line: "XPCService.xpc,", after: line)
line = lines.validate(line: ");", after: line)
line = lines.validate(line: "};", after: line)
line = lines.validate(line: "/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */", after: line)
}

// MARK: - Build phases

func test_build_phase_sources_unsorted_when_iOSProject() throws {
Expand Down