Skip to content

Commit 388db8d

Browse files
authored
Merge pull request #1192 from ddunbar/stable-ids
[Xcodeproj] Assign stable object IDs to critical objects.
2 parents c4f010c + a081b0e commit 388db8d

File tree

3 files changed

+49
-13
lines changed

3 files changed

+49
-13
lines changed

Sources/Xcodeproj/XcodeProjectModel.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ public struct Xcode {
7575

7676
/// Creates and adds a new target (which does not initially have any
7777
/// build phases).
78-
public func addTarget(productType: Target.ProductType?, name: String) -> Target {
79-
let target = Target(productType: productType, name: name)
78+
public func addTarget(objectID: String? = nil, productType: Target.ProductType?, name: String) -> Target {
79+
let target = Target(objectID: objectID, productType: productType, name: name)
8080
targets.append(target)
8181
return target
8282
}
@@ -119,10 +119,12 @@ public struct Xcode {
119119

120120
/// A reference to a file system entity (a file, folder, etc).
121121
public class FileReference: Reference {
122+
var objectID: String?
122123
var fileType: String?
123124

124-
init(path: String, pathBase: RefPathBase = .groupDir, name: String? = nil, fileType: String? = nil) {
125+
init(path: String, pathBase: RefPathBase = .groupDir, name: String? = nil, fileType: String? = nil, objectID: String? = nil) {
125126
super.init(path: path, pathBase: pathBase, name: name)
127+
self.objectID = objectID
126128
self.fileType = fileType
127129
}
128130
}
@@ -150,16 +152,18 @@ public struct Xcode {
150152
path: String,
151153
pathBase: RefPathBase = .groupDir,
152154
name: String? = nil,
153-
fileType: String? = nil
155+
fileType: String? = nil,
156+
objectID: String? = nil
154157
) -> FileReference {
155-
let fref = FileReference(path: path, pathBase: pathBase, name: name, fileType: fileType)
158+
let fref = FileReference(path: path, pathBase: pathBase, name: name, fileType: fileType, objectID: objectID)
156159
subitems.append(fref)
157160
return fref
158161
}
159162
}
160163

161164
/// An Xcode target, representing a single entity to build.
162165
public class Target {
166+
var objectID: String?
163167
var name: String
164168
var productName: String
165169
var productType: ProductType?
@@ -176,7 +180,8 @@ public struct Xcode {
176180
case unitTest = "com.apple.product-type.bundle.unit-test"
177181
var asString: String { return rawValue }
178182
}
179-
init(productType: ProductType?, name: String) {
183+
init(objectID: String?, productType: ProductType?, name: String) {
184+
self.objectID = objectID
180185
self.name = name
181186
self.productType = productType
182187
self.productName = name

Sources/Xcodeproj/XcodeProjectModelSerialization.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,19 +470,19 @@ fileprivate class PropertyListSerializer {
470470
/// dictionary is what gets written to the property list.
471471
var idsToDicts = [String: PropertyList]()
472472

473-
/// Returns the identifier for the object, assigning one if needed.
473+
/// Returns the quoted identifier for the object, assigning one if needed.
474474
func id(of object: PropertyListSerializable) -> String {
475475
// We need a "serialized object ref" wrapper for the `objsToIds` map.
476476
let serObjRef = SerializedObjectRef(object)
477477
if let id = objsToIds[serObjRef] {
478-
return id
478+
return "\"\(id)\""
479479
}
480480
// We currently always assign identifiers starting at 1 and going up.
481481
// FIXME: This is a suboptimal format for object identifier strings;
482482
// for debugging purposes they should at least sort in numeric order.
483-
let id = "OBJ_\(objsToIds.count + 1)"
483+
let id = object.objectID ?? "OBJ_\(objsToIds.count + 1)"
484484
objsToIds[serObjRef] = id
485-
return id
485+
return "\"\(id)\""
486486
}
487487

488488
/// Serializes `object` by asking it to construct a plist dictionary and
@@ -546,13 +546,24 @@ fileprivate protocol PropertyListSerializable: class {
546546
/// matters. So this is acceptable for now in the interest of getting it
547547
/// done.
548548

549+
/// A custom ID to use for the instance, if enabled.
550+
///
551+
/// This ID must be unique across the entire serialized graph.
552+
var objectID: String? { get }
553+
549554
/// Should create and return a property list dictionary of the object's
550555
/// attributes. This function may also use the serializer's `serialize()`
551556
/// function to serialize other objects, and may use `id(of:)` to access
552557
/// ids of objects that either have or will be serialized.
553558
func serialize(to serializer: PropertyListSerializer) -> [String: PropertyList]
554559
}
555560

561+
extension PropertyListSerializable {
562+
var objectID: String? {
563+
return nil
564+
}
565+
}
566+
556567
/// A very simple representation of a property list. Note that the `identifier`
557568
/// enum is not strictly necessary, but useful to semantically distinguish the
558569
/// strings that represents object identifiers from those that are just data.

Sources/Xcodeproj/pbxproj().swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ func xcodeProject(
194194
// Determine the list of external package dependencies, if any.
195195
let externalPackages = graph.packages.filter({ !graph.rootPackages.contains($0) })
196196

197+
// Build a backmap of targets and products to packages.
198+
var packagesByTarget = [ResolvedTarget: ResolvedPackage]()
199+
for package in graph.packages {
200+
for target in package.targets {
201+
packagesByTarget[target] = package
202+
}
203+
}
204+
var packagesByProduct = [ResolvedProduct: ResolvedPackage]()
205+
for package in graph.packages {
206+
for product in package.products {
207+
packagesByProduct[product] = package
208+
}
209+
}
210+
197211
// To avoid creating multiple groups for the same path, we keep a mapping
198212
// of the paths we've seen and the corresponding groups we've created.
199213
var srcPathsToGroups: [AbsolutePath: Xcode.Group] = [:]
@@ -351,7 +365,10 @@ func xcodeProject(
351365
}
352366

353367
// Create a Xcode target for the target.
354-
let xcodeTarget = project.addTarget(productType: productType, name: target.name)
368+
let package = packagesByTarget[target]!
369+
let xcodeTarget = project.addTarget(
370+
objectID: "\(package.name)::\(target.name)",
371+
productType: productType, name: target.name)
355372

356373
// Set the product name to the C99-mangled form of the target name.
357374
xcodeTarget.productName = target.c99name
@@ -427,7 +444,7 @@ func xcodeProject(
427444
targetSettings.common.FRAMEWORK_SEARCH_PATHS = ["$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks"]
428445

429446
// Add a file reference for the target's product.
430-
let productRef = productsGroup.addFileReference(path: target.productPath.asString, pathBase: .buildDir)
447+
let productRef = productsGroup.addFileReference(path: target.productPath.asString, pathBase: .buildDir, objectID: "\(package.name)::\(target.name)::Product")
431448

432449
// Set that file reference as the target's product reference.
433450
xcodeTarget.productReference = productRef
@@ -556,7 +573,10 @@ func xcodeProject(
556573
// Go on to next product if we already have a target with the same name.
557574
if targetNames.contains(product.name) { continue }
558575
// Otherwise, create an aggreate target.
559-
let aggregateTarget = project.addTarget(productType: nil, name: product.name)
576+
let package = packagesByProduct[product]!
577+
let aggregateTarget = project.addTarget(
578+
objectID: "\(package.name)::\(product.name)::ProductTarget",
579+
productType: nil, name: product.name)
560580
// Add dependencies on the targets created for each of the dependencies.
561581
for target in product.targets {
562582
// Find the target that corresponds to the target. There might not

0 commit comments

Comments
 (0)