Skip to content

Commit 84ba92d

Browse files
committed
[xcodegen] Add serialization support for buildable folders
1 parent 29cfe90 commit 84ba92d

File tree

2 files changed

+83
-57
lines changed

2 files changed

+83
-57
lines changed

utils/swift-xcodegen/Sources/Xcodeproj/XcodeProjectModel.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,12 @@ public struct Xcode {
135135
public var objectID: String?
136136
public var fileType: String?
137137
public var isDirectory: Bool
138-
139-
init(path: String, isDirectory: Bool, pathBase: RefPathBase = .groupDir, name: String? = nil, fileType: String? = nil, objectID: String? = nil) {
138+
public fileprivate(set) var isBuildableFolder: Bool = false
139+
140+
init(
141+
path: String, isDirectory: Bool, pathBase: RefPathBase = .groupDir,
142+
name: String? = nil, fileType: String? = nil, objectID: String? = nil
143+
) {
140144
self.isDirectory = isDirectory
141145
super.init(path: path, pathBase: pathBase, name: name)
142146
self.objectID = objectID
@@ -189,6 +193,7 @@ public struct Xcode {
189193
public var buildPhases: [BuildPhase]
190194
public var productReference: FileReference?
191195
public var dependencies: [TargetDependency]
196+
public private(set) var buildableFolders: [FileReference]
192197
public enum ProductType: String {
193198
case application = "com.apple.product-type.application"
194199
case staticArchive = "com.apple.product-type.library.static"
@@ -205,6 +210,7 @@ public struct Xcode {
205210
self.buildSettings = BuildSettingsTable()
206211
self.buildPhases = []
207212
self.dependencies = []
213+
self.buildableFolders = []
208214
}
209215

210216
// FIXME: There's a lot repetition in these methods; using generics to
@@ -266,7 +272,14 @@ public struct Xcode {
266272
public func addDependency(on target: Target) {
267273
dependencies.append(TargetDependency(target: target))
268274
}
269-
275+
276+
/// Turn a given folder reference into a buildable folder for this target.
277+
public func addBuildableFolder(_ fileRef: FileReference) {
278+
precondition(fileRef.isDirectory)
279+
fileRef.isBuildableFolder = true
280+
buildableFolders.append(fileRef)
281+
}
282+
270283
/// A simple wrapper to prevent ownership cycles in the `dependencies`
271284
/// property.
272285
public struct TargetDependency {

utils/swift-xcodegen/Sources/Xcodeproj/XcodeProjectModelSerialization.swift

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,33 @@
1515
is only enough functionality to allow serialization of Xcode projects.
1616
*/
1717

18+
extension Xcode.Project {
19+
fileprivate enum MinVersion {
20+
case xcode8, xcode16
21+
22+
var objectVersion: Int {
23+
switch self {
24+
case .xcode8: 46
25+
case .xcode16: 77
26+
}
27+
}
28+
}
29+
30+
fileprivate var hasBuildableFolders: Bool {
31+
var worklist: [Xcode.Reference] = []
32+
worklist.append(mainGroup)
33+
while let ref = worklist.popLast() {
34+
if let fileRef = ref as? Xcode.FileReference, fileRef.isBuildableFolder {
35+
return true
36+
}
37+
if let group = ref as? Xcode.Group {
38+
worklist += group.subitems
39+
}
40+
}
41+
return false
42+
}
43+
}
44+
1845
extension Xcode.Project: PropertyListSerializable {
1946

2047
/// Generates and returns the contents of a `project.pbxproj` plist. Does
@@ -31,9 +58,13 @@ extension Xcode.Project: PropertyListSerializable {
3158
// the serialized object dictionaries.
3259
let serializer = PropertyListSerializer()
3360
try serializer.serialize(object: self)
61+
var minVersion = MinVersion.xcode8
62+
if hasBuildableFolders {
63+
minVersion = .xcode16
64+
}
3465
return .dictionary([
3566
"archiveVersion": .string("1"),
36-
"objectVersion": .string("46"), // Xcode 8.0
67+
"objectVersion": .string(String(minVersion.objectVersion)),
3768
"rootObject": .identifier(serializer.id(of: self)),
3869
"objects": .dictionary(serializer.idsToDicts),
3970
])
@@ -76,61 +107,38 @@ extension Xcode.Project: PropertyListSerializable {
76107
}
77108
}
78109

79-
/// Private helper function that constructs and returns a partial property list
80-
/// dictionary for references. The caller can add to the returned dictionary.
81-
/// FIXME: It would be nicer to be able to use inheritance to serialize the
82-
/// attributes inherited from Reference, but but in Swift 3.0 we get an error
83-
/// that "declarations in extensions cannot override yet".
84-
fileprivate func makeReferenceDict(
85-
reference: Xcode.Reference,
86-
serializer: PropertyListSerializer,
87-
xcodeClassName: String
88-
) -> [String: PropertyList] {
89-
var dict = [String: PropertyList]()
90-
dict["isa"] = .string(xcodeClassName)
91-
dict["path"] = .string(reference.path)
92-
if let name = reference.name {
93-
dict["name"] = .string(name)
94-
}
95-
dict["sourceTree"] = .string(reference.pathBase.rawValue)
96-
return dict
97-
}
98-
99-
extension Xcode.Group: PropertyListSerializable {
100-
101-
/// Called by the Serializer to serialize the Group.
102-
fileprivate func serialize(to serializer: PropertyListSerializer) throws -> [String: PropertyList] {
103-
// Create a `PBXGroup` plist dictionary.
104-
// FIXME: It would be nicer to be able to use inheritance for the code
105-
// inherited from Reference, but but in Swift 3.0 we get an error that
106-
// "declarations in extensions cannot override yet".
107-
var dict = makeReferenceDict(reference: self, serializer: serializer, xcodeClassName: "PBXGroup")
108-
dict["children"] = try .array(subitems.map({ reference in
109-
// For the same reason, we have to cast as `PropertyListSerializable`
110-
// here; as soon as we try to make Reference conform to the protocol,
111-
// we get the problem of not being able to override `serialize(to:)`.
112-
try .identifier(serializer.serialize(object: reference as! PropertyListSerializable))
113-
}))
114-
return dict
115-
}
116-
}
117-
118-
extension Xcode.FileReference: PropertyListSerializable {
119-
120-
/// Called by the Serializer to serialize the FileReference.
121-
fileprivate func serialize(to serializer: PropertyListSerializer) -> [String: PropertyList] {
122-
// Create a `PBXFileReference` plist dictionary.
123-
// FIXME: It would be nicer to be able to use inheritance for the code
124-
// inherited from Reference, but but in Swift 3.0 we get an error that
125-
// "declarations in extensions cannot override yet".
126-
var dict = makeReferenceDict(reference: self, serializer: serializer, xcodeClassName: "PBXFileReference")
127-
if let fileType = fileType {
128-
dict["explicitFileType"] = .string(fileType)
110+
extension Xcode.Reference: PropertyListSerializable {
111+
fileprivate dynamic func serialize(
112+
to serializer: PropertyListSerializer
113+
) throws -> [String : PropertyList] {
114+
var dict = [String: PropertyList]()
115+
dict["path"] = .string(path)
116+
if let name = name {
117+
dict["name"] = .string(name)
129118
}
130-
// FileReferences don't need to store a name if it's the same as the path.
131-
if name == path {
132-
dict["name"] = nil
119+
dict["sourceTree"] = .string(pathBase.rawValue)
120+
121+
let xcodeClassName: String
122+
switch self {
123+
case let group as Xcode.Group:
124+
xcodeClassName = "PBXGroup"
125+
dict["children"] = try .array(group.subitems.map({ reference in
126+
try .identifier(serializer.serialize(object: reference))
127+
}))
128+
case let fileRef as Xcode.FileReference:
129+
xcodeClassName = fileRef.isBuildableFolder
130+
? "PBXFileSystemSynchronizedRootGroup" : "PBXFileReference"
131+
if let fileType = fileRef.fileType {
132+
dict["explicitFileType"] = .string(fileType)
133+
}
134+
// FileReferences don't need to store a name if it's the same as the path.
135+
if name == path {
136+
dict["name"] = nil
137+
}
138+
default:
139+
fatalError("Unhandled subclass")
133140
}
141+
dict["isa"] = .string(xcodeClassName)
134142
return dict
135143
}
136144
}
@@ -178,6 +186,11 @@ extension Xcode.Target: PropertyListSerializable {
178186
// so we need a helper class here.
179187
try .identifier(serializer.serialize(object: TargetDependency(target: dep.target)))
180188
}))
189+
if !buildableFolders.isEmpty {
190+
dict["fileSystemSynchronizedGroups"] = .array(
191+
buildableFolders.map { .identifier(serializer.id(of: $0)) }
192+
)
193+
}
181194
dict["productName"] = .string(productName)
182195
if let productType = productType {
183196
dict["productType"] = .string(productType.rawValue)

0 commit comments

Comments
 (0)