Skip to content

Commit 2fae634

Browse files
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.
1 parent 1c7ef35 commit 2fae634

File tree

10 files changed

+68
-26
lines changed

10 files changed

+68
-26
lines changed

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: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ 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 expectedSignature() -> 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+
27+
1028
public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
1129
public enum PackageType: String, Equatable, Hashable, Codable, Sendable {
1230
case runtime
@@ -18,7 +36,7 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
1836
case target(name: String, status: LinkingStatus = .required, condition: PlatformCondition? = nil)
1937
case project(target: String, path: AbsolutePath, status: LinkingStatus = .required, condition: PlatformCondition? = nil)
2038
case framework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil)
21-
case xcframework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil)
39+
case xcframework(path: AbsolutePath, expectedSignature: XCFrameworkSignature?, status: LinkingStatus, condition: PlatformCondition? = nil)
2240
case library(
2341
path: AbsolutePath,
2442
publicHeaders: AbsolutePath,
@@ -37,7 +55,7 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
3755
condition
3856
case .framework(path: _, status: _, condition: let condition):
3957
condition
40-
case .xcframework(path: _, status: _, condition: let condition):
58+
case .xcframework(path: _, expectedSignature: _, status: _, condition: let condition):
4159
condition
4260
case .library(path: _, publicHeaders: _, swiftModuleMap: _, condition: let condition):
4361
condition
@@ -57,8 +75,8 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable {
5775
return .project(target: target, path: path, status: status, condition: condition)
5876
case .framework(path: let path, status: let status, condition: _):
5977
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)
78+
case .xcframework(path: let path, expectedSignature: let expectedSignature, status: let status, condition: _):
79+
return .xcframework(path: path, expectedSignature:expectedSignature, status: status, condition: condition)
6280
case .library(path: let path, publicHeaders: let headers, swiftModuleMap: let moduleMap, condition: _):
6381
return .library(path: path, publicHeaders: headers, swiftModuleMap: moduleMap, condition: condition)
6482
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: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping {
5454
case .nativeTarget:
5555
return try mapNativeTargetProxy(targetProxy, condition: condition, xcodeProj: xcodeProj)
5656
case .reference:
57-
return try mapReferenceProxy(targetProxy, condition: condition, xcodeProj: xcodeProj)
57+
return try mapReferenceProxy(targetProxy, condition: condition, xcodeProj: xcodeProj)
5858
case .other, .none:
5959
throw TargetDependencyMappingError.unsupportedProxyType(dependency.name)
6060
}
@@ -107,13 +107,15 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping {
107107
// File-based dependency
108108
if let fileRef = object as? PBXFileReference {
109109
return try mapFileDependency(
110-
pathString: fileRef.path,
111-
condition: condition,
112-
xcodeProj: xcodeProj
110+
pathString: fileRef.path,
111+
expectedSignature: nil,
112+
condition: condition,
113+
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
)
@@ -127,24 +129,25 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping {
127129
)
128130
}
129131
}
130-
131132
/// Maps file-based dependencies (e.g., frameworks, libraries) into `TargetDependency` models.
132133
/// - Parameters:
133134
/// - pathString: The path string for the file-based dependency (relative or absolute).
135+
/// - expectedSignature: The expected signature if `path` is of a signed XCFramework, `nil` otherwise.
134136
/// - condition: An optional platform condition.
135137
/// - xcodeProj: The Xcode project reference for resolving the directory structure.
136138
/// - Returns: A `TargetDependency` reflecting the file’s extension (framework, library, etc.).
137139
/// - Throws: If the path is missing or invalid.
138140
private func mapFileDependency(
139141
pathString: String?,
142+
expectedSignature:XCFrameworkSignature?,
140143
condition: PlatformCondition?,
141144
xcodeProj: XcodeProj
142145
) throws -> TargetDependency {
143146
let pathString = try pathString.throwing(
144147
TargetDependencyMappingError.missingFileReference("Path string is nil in file dependency.")
145148
)
146149
let path = xcodeProj.srcPath.appending(try RelativePath(validating: pathString))
147-
return try pathMapper.map(path: path, condition: condition)
150+
return try pathMapper.map(path: path, expectedSignature:expectedSignature, condition: condition)
148151
}
149152
}
150153

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?.expectedSignature()
6671
)
6772
)
6873

Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,23 @@ 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 -> TargetDependency
1516
}
1617

1718
/// A mapper that converts file paths (like `.framework`, `.xcframework`, or libraries) to `TargetDependency` models.
1819
struct PathDependencyMapper: PathDependencyMapping {
19-
func map(path: AbsolutePath, condition: PlatformCondition?) throws -> TargetDependency {
20+
func map(path: AbsolutePath, expectedSignature: XCFrameworkSignature? = nil, condition: PlatformCondition?) throws -> TargetDependency {
2021
let status: LinkingStatus = .required
2122

2223
switch path.fileExtension {
2324
case .framework:
2425
return .framework(path: path, status: status, condition: condition)
2526
case .xcframework:
26-
return .xcframework(path: path, status: status, condition: condition)
27+
return .xcframework(path: path, expectedSignature: expectedSignature ?? .unsigned, status: status, condition: condition)
2728
case .dynamicLibrary, .textBasedDynamicLibrary, .staticLibrary:
2829
return .library(
2930
path: path,

Sources/XcodeMetadata/Providers/XCFrameworkMetadataProvider.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
4949
/// - Parameter binaryPath: Path to the binary.
5050
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID>
5151

52-
/// Loads all the metadata associated with an XCFramework at the specified path
52+
/// Loads all the metadata associated with an XCFramework at the specified path, and expected signature if signed
5353
/// - Note: This performs various shell calls and disk operations
54-
func loadMetadata(at path: AbsolutePath, status: LinkingStatus) async throws
54+
func loadMetadata(at path: AbsolutePath, expectedSignature: XCFrameworkSignature?, status: LinkingStatus) async throws
5555
-> XCFrameworkMetadata
5656

5757
/// Returns the info.plist of the xcframework at the given path.
@@ -75,6 +75,7 @@ public final class XCFrameworkMetadataProvider: PrecompiledMetadataProvider,
7575

7676
public func loadMetadata(
7777
at path: AbsolutePath,
78+
expectedSignature: XCFrameworkSignature?,
7879
status: LinkingStatus
7980
) async throws -> XCFrameworkMetadata {
8081
guard try await fileSystem.exists(path) else {
@@ -93,7 +94,8 @@ public final class XCFrameworkMetadataProvider: PrecompiledMetadataProvider,
9394
status: status,
9495
macroPath: try await macroPath(xcframeworkPath: path),
9596
swiftModules: try await fileSystem.glob(directory: path, include: ["**/*.swiftmodule"]).collect().sorted(),
96-
moduleMaps: try await fileSystem.glob(directory: path, include: ["**/*.modulemap"]).collect().sorted()
97+
moduleMaps: try await fileSystem.glob(directory: path, include: ["**/*.modulemap"]).collect().sorted(),
98+
expectedSignature: expectedSignature
9799
)
98100
}
99101

0 commit comments

Comments
 (0)