Skip to content
This repository was archived by the owner on Feb 26, 2026. It is now read-only.

Commit 81df161

Browse files
feat(PBXFileReference): support XCFramework expectedSignature property (#156)
* feat(PBXFileReference): support XCFramework expectedSignature property Support XCFrameworks expectedSignature property, and add the expected expectedSignature to the target dependency, to be verified against the actual signature. * Fix linting issues * Make the tests compile and pass --------- Co-authored-by: Pedro <pedro@pepicrft.me> Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
1 parent acff279 commit 81df161

14 files changed

Lines changed: 102 additions & 35 deletions

File tree

Package.resolved

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@ public struct DependenciesGraph: Equatable, Codable, Sendable {
3333
name: String = "Test",
3434
// swiftlint:disable:next force_try
3535
path: AbsolutePath = AbsolutePath.root.appending(try! RelativePath(validating: "Test.xcframework")),
36+
expectedSignature: XCFrameworkSignature = .unsigned,
3637
status: LinkingStatus = .required
3738
) -> DependenciesGraph {
38-
let externalDependencies = [name: [TargetDependency.xcframework(path: path, status: status)]]
39+
let externalDependencies = [name: [TargetDependency.xcframework(
40+
path: path,
41+
expectedSignature: expectedSignature,
42+
status: status
43+
)]]
3944

4045
return .init(
4146
externalDependencies: externalDependencies,

Sources/XcodeGraph/Graph/GraphDependency.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda
1010
public let status: LinkingStatus
1111
public let swiftModules: [AbsolutePath]
1212
public let moduleMaps: [AbsolutePath]
13+
public let expectedSignature: String?
1314

1415
public init(
1516
path: AbsolutePath,
@@ -19,7 +20,8 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda
1920
status: LinkingStatus,
2021
macroPath _: AbsolutePath?,
2122
swiftModules: [AbsolutePath],
22-
moduleMaps: [AbsolutePath]
23+
moduleMaps: [AbsolutePath],
24+
expectedSignature: String? = nil
2325
) {
2426
self.path = path
2527
self.infoPlist = infoPlist
@@ -28,6 +30,7 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda
2830
self.status = status
2931
self.swiftModules = swiftModules
3032
self.moduleMaps = moduleMaps
33+
self.expectedSignature = expectedSignature
3134
}
3235

3336
public var description: String {

Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public struct XCFrameworkMetadata: Equatable {
1313
public var swiftModules: [AbsolutePath]
1414
/// All `.modulemap` files present in the `.xcframework`
1515
public var moduleMaps: [AbsolutePath]
16+
public var expectedSignature: XCFrameworkSignature?
1617

1718
public init(
1819
path: AbsolutePath,
@@ -22,7 +23,8 @@ public struct XCFrameworkMetadata: Equatable {
2223
status: LinkingStatus,
2324
macroPath: AbsolutePath?,
2425
swiftModules: [AbsolutePath] = [],
25-
moduleMaps: [AbsolutePath] = []
26+
moduleMaps: [AbsolutePath] = [],
27+
expectedSignature: XCFrameworkSignature? = nil
2628
) {
2729
self.path = path
2830
self.infoPlist = infoPlist
@@ -32,6 +34,7 @@ public struct XCFrameworkMetadata: Equatable {
3234
self.macroPath = macroPath
3335
self.swiftModules = swiftModules
3436
self.moduleMaps = moduleMaps
37+
self.expectedSignature = expectedSignature
3538
}
3639
}
3740

@@ -46,7 +49,8 @@ public struct XCFrameworkMetadata: Equatable {
4649
status: LinkingStatus = .required,
4750
macroPath: AbsolutePath? = nil,
4851
swiftModules: [AbsolutePath] = [],
49-
moduleMaps: [AbsolutePath] = []
52+
moduleMaps: [AbsolutePath] = [],
53+
expectedSignature: XCFrameworkSignature? = nil
5054
) -> XCFrameworkMetadata {
5155
XCFrameworkMetadata(
5256
path: path,
@@ -56,7 +60,8 @@ public struct XCFrameworkMetadata: Equatable {
5660
status: status,
5761
macroPath: macroPath,
5862
swiftModules: swiftModules,
59-
moduleMaps: moduleMaps
63+
moduleMaps: moduleMaps,
64+
expectedSignature: expectedSignature
6065
)
6166
}
6267
}

Sources/XcodeGraph/Models/TargetDependency.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ public enum LinkingStatus: String, Hashable, Codable, Sendable {
77
case none
88
}
99

10+
public enum XCFrameworkSignature: Equatable, Hashable, Codable, Sendable {
11+
case unsigned
12+
case signedWithAppleCertificate(teamIdentifier: String, teamName: String)
13+
case selfSigned(fingerprint: String)
14+
15+
public func signatureString() -> String? {
16+
switch self {
17+
case .unsigned:
18+
return nil
19+
case let .selfSigned(fingerprint: fingerprint):
20+
return "SelfSigned:\(fingerprint)"
21+
case let .signedWithAppleCertificate(teamIdentifier: teamIdentifier, teamName: teamName):
22+
return "AppleDeveloperProgram:\(teamIdentifier):\(teamName)"
23+
}
24+
}
25+
}
26+
1027
public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
1128
public enum PackageType: String, Equatable, Hashable, Codable, Sendable {
1229
case runtime
@@ -18,7 +35,12 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
1835
case target(name: String, status: LinkingStatus = .required, condition: PlatformCondition? = nil)
1936
case project(target: String, path: AbsolutePath, status: LinkingStatus = .required, condition: PlatformCondition? = nil)
2037
case framework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil)
21-
case xcframework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil)
38+
case xcframework(
39+
path: AbsolutePath,
40+
expectedSignature: XCFrameworkSignature?,
41+
status: LinkingStatus,
42+
condition: PlatformCondition? = nil
43+
)
2244
case library(
2345
path: AbsolutePath,
2446
publicHeaders: AbsolutePath,
@@ -37,7 +59,7 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
3759
condition
3860
case .framework(path: _, status: _, condition: let condition):
3961
condition
40-
case .xcframework(path: _, status: _, condition: let condition):
62+
case .xcframework(path: _, expectedSignature: _, status: _, condition: let condition):
4163
condition
4264
case .library(path: _, publicHeaders: _, swiftModuleMap: _, condition: let condition):
4365
condition
@@ -57,8 +79,8 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
5779
return .project(target: target, path: path, status: status, condition: condition)
5880
case .framework(path: let path, status: let status, condition: _):
5981
return .framework(path: path, status: status, condition: condition)
60-
case .xcframework(path: let path, status: let status, condition: _):
61-
return .xcframework(path: path, status: status, condition: condition)
82+
case .xcframework(path: let path, expectedSignature: let expectedSignature, status: let status, condition: _):
83+
return .xcframework(path: path, expectedSignature: expectedSignature, status: status, condition: condition)
6284
case .library(path: let path, publicHeaders: let headers, swiftModuleMap: let moduleMap, condition: _):
6385
return .library(path: path, publicHeaders: headers, swiftModuleMap: moduleMap, condition: condition)
6486
case .package(product: let product, type: let type, condition: _):

Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension TargetDependency {
1414
return product
1515
case let .framework(path, _, _):
1616
return path.basenameWithoutExt
17-
case let .xcframework(path, _, _):
17+
case let .xcframework(path, _, _, _):
1818
return path.basenameWithoutExt
1919
case let .library(path, _, _, _):
2020
return path.basenameWithoutExt

Sources/XcodeGraphMapper/Mappers/Phases/PBXFrameworksBuildPhaseMapper.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ struct PBXFrameworksBuildPhaseMapper: PBXFrameworksBuildPhaseMapping {
9797
.throwing(PBXFrameworksBuildPhaseMappingError.missingFilePath(name: fileRef.name))
9898

9999
let absolutePath = try AbsolutePath(validating: filePathString)
100-
return try pathMapper.map(path: absolutePath, condition: nil)
100+
return try pathMapper.map(path: absolutePath, expectedSignature: nil, condition: nil)
101101
}
102102
}
103103

Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependencyMapper.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,14 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping {
108108
if let fileRef = object as? PBXFileReference {
109109
return try mapFileDependency(
110110
pathString: fileRef.path,
111+
expectedSignature: nil,
111112
condition: condition,
112113
xcodeProj: xcodeProj
113114
)
114115
} else if let refProxy = object as? PBXReferenceProxy {
115116
return try mapFileDependency(
116117
pathString: refProxy.path,
118+
expectedSignature: nil,
117119
condition: condition,
118120
xcodeProj: xcodeProj
119121
)
@@ -131,20 +133,22 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping {
131133
/// Maps file-based dependencies (e.g., frameworks, libraries) into `TargetDependency` models.
132134
/// - Parameters:
133135
/// - pathString: The path string for the file-based dependency (relative or absolute).
136+
/// - expectedSignature: The expected signature if `path` is of a signed XCFramework, `nil` otherwise.
134137
/// - condition: An optional platform condition.
135138
/// - xcodeProj: The Xcode project reference for resolving the directory structure.
136139
/// - Returns: A `TargetDependency` reflecting the file’s extension (framework, library, etc.).
137140
/// - Throws: If the path is missing or invalid.
138141
private func mapFileDependency(
139142
pathString: String?,
143+
expectedSignature: XCFrameworkSignature?,
140144
condition: PlatformCondition?,
141145
xcodeProj: XcodeProj
142146
) throws -> TargetDependency {
143147
let pathString = try pathString.throwing(
144148
TargetDependencyMappingError.missingFileReference("Path string is nil in file dependency.")
145149
)
146150
let path = xcodeProj.srcPath.appending(try RelativePath(validating: pathString))
147-
return try pathMapper.map(path: path, condition: condition)
151+
return try pathMapper.map(path: path, expectedSignature: expectedSignature, condition: condition)
148152
}
149153
}
150154

Sources/XcodeGraphMapper/Mappers/Targets/TargetDependency+GraphMapping.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ extension TargetDependency {
5151
status: status
5252
)
5353

54-
case let .xcframework(path, status, _):
55-
let metadata = try await xcframeworkMetadataProvider.loadMetadata(at: path, status: status)
54+
case let .xcframework(path, expectedSignature, status, _):
55+
let metadata = try await xcframeworkMetadataProvider.loadMetadata(
56+
at: path,
57+
expectedSignature: expectedSignature,
58+
status: status
59+
)
5660
return .xcframework(
5761
.init(
5862
path: path,
@@ -62,7 +66,8 @@ extension TargetDependency {
6266
status: status,
6367
macroPath: metadata.macroPath,
6468
swiftModules: metadata.swiftModules,
65-
moduleMaps: metadata.moduleMaps
69+
moduleMaps: metadata.moduleMaps,
70+
expectedSignature: metadata.expectedSignature?.signatureString()
6671
)
6772
)
6873

Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,33 @@ protocol PathDependencyMapping {
88
///
99
/// - Parameters:
1010
/// - path: The file path to map.
11+
/// - expectedSignature: The expected signature if `path` is of a signed XCFramework, `nil` otherwise.
1112
/// - condition: An optional platform condition (e.g., iOS only).
1213
/// - Returns: The corresponding `TargetDependency`, if the path extension is recognized.
1314
/// - Throws: `PathDependencyError.invalidExtension` if the file extension is not supported.
14-
func map(path: AbsolutePath, condition: PlatformCondition?) throws -> TargetDependency
15+
func map(path: AbsolutePath, expectedSignature: XCFrameworkSignature?, condition: PlatformCondition?) throws
16+
-> TargetDependency
1517
}
1618

1719
/// A mapper that converts file paths (like `.framework`, `.xcframework`, or libraries) to `TargetDependency` models.
1820
struct PathDependencyMapper: PathDependencyMapping {
19-
func map(path: AbsolutePath, condition: PlatformCondition?) throws -> TargetDependency {
21+
func map(
22+
path: AbsolutePath,
23+
expectedSignature: XCFrameworkSignature? = nil,
24+
condition: PlatformCondition?
25+
) throws -> TargetDependency {
2026
let status: LinkingStatus = .required
2127

2228
switch path.fileExtension {
2329
case .framework:
2430
return .framework(path: path, status: status, condition: condition)
2531
case .xcframework:
26-
return .xcframework(path: path, status: status, condition: condition)
32+
return .xcframework(
33+
path: path,
34+
expectedSignature: expectedSignature ?? .unsigned,
35+
status: status,
36+
condition: condition
37+
)
2738
case .dynamicLibrary, .textBasedDynamicLibrary, .staticLibrary:
2839
return .library(
2940
path: path,

0 commit comments

Comments
 (0)