diff --git a/Package.resolved b/Package.resolved index 08a456d1..363cdd53 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "6e9c67eb028efd58882f60208b868c006b6ab0a4c7006e49085a4c346df383b0", + "originHash" : "d4f68e752993519aee6ad2c355139fa210271f229b5503b0a029f07f61d7b15c", "pins" : [ { "identity" : "aexml", @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/FileSystem.git", "state" : { - "revision" : "1bff6d54e90a79706a4a9d77cd081104de430361", - "version" : "0.7.7" + "revision" : "eb33ba606c2d988a6f7247e3fc23faa681d75028", + "version" : "0.7.16" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Kolos65/Mockable.git", "state" : { - "revision" : "203336d0ccb7ff03a8a03db54a4fa18fc2b0c771", - "version" : "0.3.0" + "revision" : "68f3ed6c4b62afab27a84425494cb61421a61ac1", + "version" : "0.3.1" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log", "state" : { - "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", - "version" : "1.6.2" + "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", + "version" : "1.6.3" } }, { @@ -132,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio", "state" : { - "revision" : "c51907a839e63ebf0ba2076bba73dd96436bd1b9", - "version" : "2.81.0" + "revision" : "0f54d58bb5db9e064f332e8524150de379d1e51c", + "version" : "2.82.1" } }, { diff --git a/Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift b/Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift index 639bd16b..b90e332a 100644 --- a/Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift +++ b/Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift @@ -33,9 +33,14 @@ public struct DependenciesGraph: Equatable, Codable, Sendable { name: String = "Test", // swiftlint:disable:next force_try path: AbsolutePath = AbsolutePath.root.appending(try! RelativePath(validating: "Test.xcframework")), + expectedSignature: XCFrameworkSignature = .unsigned, status: LinkingStatus = .required ) -> DependenciesGraph { - let externalDependencies = [name: [TargetDependency.xcframework(path: path, status: status)]] + let externalDependencies = [name: [TargetDependency.xcframework( + path: path, + expectedSignature: expectedSignature, + status: status + )]] return .init( externalDependencies: externalDependencies, diff --git a/Sources/XcodeGraph/Graph/GraphDependency.swift b/Sources/XcodeGraph/Graph/GraphDependency.swift index 37988426..35c514d9 100644 --- a/Sources/XcodeGraph/Graph/GraphDependency.swift +++ b/Sources/XcodeGraph/Graph/GraphDependency.swift @@ -10,6 +10,7 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda public let status: LinkingStatus public let swiftModules: [AbsolutePath] public let moduleMaps: [AbsolutePath] + public let expectedSignature: String? public init( path: AbsolutePath, @@ -19,7 +20,8 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda status: LinkingStatus, macroPath _: AbsolutePath?, swiftModules: [AbsolutePath], - moduleMaps: [AbsolutePath] + moduleMaps: [AbsolutePath], + expectedSignature: String? = nil ) { self.path = path self.infoPlist = infoPlist @@ -28,6 +30,7 @@ public enum GraphDependency: Hashable, CustomStringConvertible, Comparable, Coda self.status = status self.swiftModules = swiftModules self.moduleMaps = moduleMaps + self.expectedSignature = expectedSignature } public var description: String { diff --git a/Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift b/Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift index 72e24715..c0318077 100644 --- a/Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift +++ b/Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift @@ -13,6 +13,7 @@ public struct XCFrameworkMetadata: Equatable { public var swiftModules: [AbsolutePath] /// All `.modulemap` files present in the `.xcframework` public var moduleMaps: [AbsolutePath] + public var expectedSignature: XCFrameworkSignature? public init( path: AbsolutePath, @@ -22,7 +23,8 @@ public struct XCFrameworkMetadata: Equatable { status: LinkingStatus, macroPath: AbsolutePath?, swiftModules: [AbsolutePath] = [], - moduleMaps: [AbsolutePath] = [] + moduleMaps: [AbsolutePath] = [], + expectedSignature: XCFrameworkSignature? = nil ) { self.path = path self.infoPlist = infoPlist @@ -32,6 +34,7 @@ public struct XCFrameworkMetadata: Equatable { self.macroPath = macroPath self.swiftModules = swiftModules self.moduleMaps = moduleMaps + self.expectedSignature = expectedSignature } } @@ -46,7 +49,8 @@ public struct XCFrameworkMetadata: Equatable { status: LinkingStatus = .required, macroPath: AbsolutePath? = nil, swiftModules: [AbsolutePath] = [], - moduleMaps: [AbsolutePath] = [] + moduleMaps: [AbsolutePath] = [], + expectedSignature: XCFrameworkSignature? = nil ) -> XCFrameworkMetadata { XCFrameworkMetadata( path: path, @@ -56,7 +60,8 @@ public struct XCFrameworkMetadata: Equatable { status: status, macroPath: macroPath, swiftModules: swiftModules, - moduleMaps: moduleMaps + moduleMaps: moduleMaps, + expectedSignature: expectedSignature ) } } diff --git a/Sources/XcodeGraph/Models/TargetDependency.swift b/Sources/XcodeGraph/Models/TargetDependency.swift index 1c60d0bb..54ddb418 100644 --- a/Sources/XcodeGraph/Models/TargetDependency.swift +++ b/Sources/XcodeGraph/Models/TargetDependency.swift @@ -7,6 +7,23 @@ public enum LinkingStatus: String, Hashable, Codable, Sendable { case none } +public enum XCFrameworkSignature: Equatable, Hashable, Codable, Sendable { + case unsigned + case signedWithAppleCertificate(teamIdentifier: String, teamName: String) + case selfSigned(fingerprint: String) + + public func signatureString() -> String? { + switch self { + case .unsigned: + return nil + case let .selfSigned(fingerprint: fingerprint): + return "SelfSigned:\(fingerprint)" + case let .signedWithAppleCertificate(teamIdentifier: teamIdentifier, teamName: teamName): + return "AppleDeveloperProgram:\(teamIdentifier):\(teamName)" + } + } +} + public enum TargetDependency: Equatable, Hashable, Codable, Sendable { public enum PackageType: String, Equatable, Hashable, Codable, Sendable { case runtime @@ -18,7 +35,12 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable { case target(name: String, status: LinkingStatus = .required, condition: PlatformCondition? = nil) case project(target: String, path: AbsolutePath, status: LinkingStatus = .required, condition: PlatformCondition? = nil) case framework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil) - case xcframework(path: AbsolutePath, status: LinkingStatus, condition: PlatformCondition? = nil) + case xcframework( + path: AbsolutePath, + expectedSignature: XCFrameworkSignature?, + status: LinkingStatus, + condition: PlatformCondition? = nil + ) case library( path: AbsolutePath, publicHeaders: AbsolutePath, @@ -37,7 +59,7 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable { condition case .framework(path: _, status: _, condition: let condition): condition - case .xcframework(path: _, status: _, condition: let condition): + case .xcframework(path: _, expectedSignature: _, status: _, condition: let condition): condition case .library(path: _, publicHeaders: _, swiftModuleMap: _, condition: let condition): condition @@ -57,8 +79,8 @@ public enum TargetDependency: Equatable, Hashable, Codable, Sendable { return .project(target: target, path: path, status: status, condition: condition) case .framework(path: let path, status: let status, condition: _): return .framework(path: path, status: status, condition: condition) - case .xcframework(path: let path, status: let status, condition: _): - return .xcframework(path: path, status: status, condition: condition) + case .xcframework(path: let path, expectedSignature: let expectedSignature, status: let status, condition: _): + return .xcframework(path: path, expectedSignature: expectedSignature, status: status, condition: condition) case .library(path: let path, publicHeaders: let headers, swiftModuleMap: let moduleMap, condition: _): return .library(path: path, publicHeaders: headers, swiftModuleMap: moduleMap, condition: condition) case .package(product: let product, type: let type, condition: _): diff --git a/Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift b/Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift index c92c1fce..6642bac8 100644 --- a/Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift +++ b/Sources/XcodeGraphMapper/Extensions/TargetDependency+Extensions.swift @@ -14,7 +14,7 @@ extension TargetDependency { return product case let .framework(path, _, _): return path.basenameWithoutExt - case let .xcframework(path, _, _): + case let .xcframework(path, _, _, _): return path.basenameWithoutExt case let .library(path, _, _, _): return path.basenameWithoutExt diff --git a/Sources/XcodeGraphMapper/Mappers/Phases/PBXFrameworksBuildPhaseMapper.swift b/Sources/XcodeGraphMapper/Mappers/Phases/PBXFrameworksBuildPhaseMapper.swift index 0317421f..27d1211f 100644 --- a/Sources/XcodeGraphMapper/Mappers/Phases/PBXFrameworksBuildPhaseMapper.swift +++ b/Sources/XcodeGraphMapper/Mappers/Phases/PBXFrameworksBuildPhaseMapper.swift @@ -97,7 +97,7 @@ struct PBXFrameworksBuildPhaseMapper: PBXFrameworksBuildPhaseMapping { .throwing(PBXFrameworksBuildPhaseMappingError.missingFilePath(name: fileRef.name)) let absolutePath = try AbsolutePath(validating: filePathString) - return try pathMapper.map(path: absolutePath, condition: nil) + return try pathMapper.map(path: absolutePath, expectedSignature: nil, condition: nil) } } diff --git a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependencyMapper.swift b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependencyMapper.swift index f06e4eb1..dc90edb5 100644 --- a/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependencyMapper.swift +++ b/Sources/XcodeGraphMapper/Mappers/Targets/PBXTargetDependencyMapper.swift @@ -108,12 +108,14 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping { if let fileRef = object as? PBXFileReference { return try mapFileDependency( pathString: fileRef.path, + expectedSignature: nil, condition: condition, xcodeProj: xcodeProj ) } else if let refProxy = object as? PBXReferenceProxy { return try mapFileDependency( pathString: refProxy.path, + expectedSignature: nil, condition: condition, xcodeProj: xcodeProj ) @@ -131,12 +133,14 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping { /// Maps file-based dependencies (e.g., frameworks, libraries) into `TargetDependency` models. /// - Parameters: /// - pathString: The path string for the file-based dependency (relative or absolute). + /// - expectedSignature: The expected signature if `path` is of a signed XCFramework, `nil` otherwise. /// - condition: An optional platform condition. /// - xcodeProj: The Xcode project reference for resolving the directory structure. /// - Returns: A `TargetDependency` reflecting the file’s extension (framework, library, etc.). /// - Throws: If the path is missing or invalid. private func mapFileDependency( pathString: String?, + expectedSignature: XCFrameworkSignature?, condition: PlatformCondition?, xcodeProj: XcodeProj ) throws -> TargetDependency { @@ -144,7 +148,7 @@ struct PBXTargetDependencyMapper: PBXTargetDependencyMapping { TargetDependencyMappingError.missingFileReference("Path string is nil in file dependency.") ) let path = xcodeProj.srcPath.appending(try RelativePath(validating: pathString)) - return try pathMapper.map(path: path, condition: condition) + return try pathMapper.map(path: path, expectedSignature: expectedSignature, condition: condition) } } diff --git a/Sources/XcodeGraphMapper/Mappers/Targets/TargetDependency+GraphMapping.swift b/Sources/XcodeGraphMapper/Mappers/Targets/TargetDependency+GraphMapping.swift index 46434e64..adede7ef 100644 --- a/Sources/XcodeGraphMapper/Mappers/Targets/TargetDependency+GraphMapping.swift +++ b/Sources/XcodeGraphMapper/Mappers/Targets/TargetDependency+GraphMapping.swift @@ -51,8 +51,12 @@ extension TargetDependency { status: status ) - case let .xcframework(path, status, _): - let metadata = try await xcframeworkMetadataProvider.loadMetadata(at: path, status: status) + case let .xcframework(path, expectedSignature, status, _): + let metadata = try await xcframeworkMetadataProvider.loadMetadata( + at: path, + expectedSignature: expectedSignature, + status: status + ) return .xcframework( .init( path: path, @@ -62,7 +66,8 @@ extension TargetDependency { status: status, macroPath: metadata.macroPath, swiftModules: metadata.swiftModules, - moduleMaps: metadata.moduleMaps + moduleMaps: metadata.moduleMaps, + expectedSignature: metadata.expectedSignature?.signatureString() ) ) diff --git a/Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift b/Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift index 417c1f6d..b8e9f758 100644 --- a/Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift +++ b/Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift @@ -8,22 +8,33 @@ protocol PathDependencyMapping { /// /// - Parameters: /// - path: The file path to map. + /// - expectedSignature: The expected signature if `path` is of a signed XCFramework, `nil` otherwise. /// - condition: An optional platform condition (e.g., iOS only). /// - Returns: The corresponding `TargetDependency`, if the path extension is recognized. /// - Throws: `PathDependencyError.invalidExtension` if the file extension is not supported. - func map(path: AbsolutePath, condition: PlatformCondition?) throws -> TargetDependency + func map(path: AbsolutePath, expectedSignature: XCFrameworkSignature?, condition: PlatformCondition?) throws + -> TargetDependency } /// A mapper that converts file paths (like `.framework`, `.xcframework`, or libraries) to `TargetDependency` models. struct PathDependencyMapper: PathDependencyMapping { - func map(path: AbsolutePath, condition: PlatformCondition?) throws -> TargetDependency { + func map( + path: AbsolutePath, + expectedSignature: XCFrameworkSignature? = nil, + condition: PlatformCondition? + ) throws -> TargetDependency { let status: LinkingStatus = .required switch path.fileExtension { case .framework: return .framework(path: path, status: status, condition: condition) case .xcframework: - return .xcframework(path: path, status: status, condition: condition) + return .xcframework( + path: path, + expectedSignature: expectedSignature ?? .unsigned, + status: status, + condition: condition + ) case .dynamicLibrary, .textBasedDynamicLibrary, .staticLibrary: return .library( path: path, diff --git a/Sources/XcodeMetadata/Providers/XCFrameworkMetadataProvider.swift b/Sources/XcodeMetadata/Providers/XCFrameworkMetadataProvider.swift index 8cc8149e..da38de01 100644 --- a/Sources/XcodeMetadata/Providers/XCFrameworkMetadataProvider.swift +++ b/Sources/XcodeMetadata/Providers/XCFrameworkMetadataProvider.swift @@ -49,9 +49,9 @@ public protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding { /// - Parameter binaryPath: Path to the binary. func uuids(binaryPath: AbsolutePath) throws -> Set - /// Loads all the metadata associated with an XCFramework at the specified path + /// Loads all the metadata associated with an XCFramework at the specified path, and expected signature if signed /// - Note: This performs various shell calls and disk operations - func loadMetadata(at path: AbsolutePath, status: LinkingStatus) async throws + func loadMetadata(at path: AbsolutePath, expectedSignature: XCFrameworkSignature?, status: LinkingStatus) async throws -> XCFrameworkMetadata /// Returns the info.plist of the xcframework at the given path. @@ -75,6 +75,7 @@ public final class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, public func loadMetadata( at path: AbsolutePath, + expectedSignature: XCFrameworkSignature?, status: LinkingStatus ) async throws -> XCFrameworkMetadata { guard try await fileSystem.exists(path) else { @@ -93,7 +94,8 @@ public final class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, status: status, macroPath: try await macroPath(xcframeworkPath: path), swiftModules: try await fileSystem.glob(directory: path, include: ["**/*.swiftmodule"]).collect().sorted(), - moduleMaps: try await fileSystem.glob(directory: path, include: ["**/*.modulemap"]).collect().sorted() + moduleMaps: try await fileSystem.glob(directory: path, include: ["**/*.modulemap"]).collect().sorted(), + expectedSignature: expectedSignature ) } diff --git a/Tests/XcodeGraphMapperTests/MapperTests/Target/TargetDependencyExtensionsTests.swift b/Tests/XcodeGraphMapperTests/MapperTests/Target/TargetDependencyExtensionsTests.swift index 636ee8a8..9f1336bc 100644 --- a/Tests/XcodeGraphMapperTests/MapperTests/Target/TargetDependencyExtensionsTests.swift +++ b/Tests/XcodeGraphMapperTests/MapperTests/Target/TargetDependencyExtensionsTests.swift @@ -83,7 +83,12 @@ struct TargetDependencyExtensionsTests { func testTargetGraphDependency_XCFramework() async throws { // Given let xcframeworkPath = sourceDirectory.appending(component: "MyFramework.xcframework") - let dependency = TargetDependency.xcframework(path: xcframeworkPath, status: .required, condition: nil) + let dependency = TargetDependency.xcframework( + path: xcframeworkPath, + expectedSignature: nil, + status: .required, + condition: nil + ) // When let graphDep = try await dependency.graphDependency( diff --git a/Tests/XcodeGraphTests/Models/TargetDependencyTests.swift b/Tests/XcodeGraphTests/Models/TargetDependencyTests.swift index 0cf1d5d9..e3a070a7 100644 --- a/Tests/XcodeGraphTests/Models/TargetDependencyTests.swift +++ b/Tests/XcodeGraphTests/Models/TargetDependencyTests.swift @@ -50,7 +50,12 @@ final class TargetDependencyTests: XCTestCase { .sdk(name: "", status: .required, condition: expected), .package(product: "", type: .plugin, condition: expected), .target(name: "", condition: expected), - .xcframework(path: try! AbsolutePath(validating: "/"), status: .required, condition: expected), + .xcframework( + path: try! AbsolutePath(validating: "/"), + expectedSignature: nil, + status: .required, + condition: expected + ), .project(target: "", path: try! AbsolutePath(validating: "/"), condition: expected), ] diff --git a/Tests/XcodeMetadataTests/XCFrameworkMetadataProviderTests.swift b/Tests/XcodeMetadataTests/XCFrameworkMetadataProviderTests.swift index 37977b63..15390de0 100644 --- a/Tests/XcodeMetadataTests/XCFrameworkMetadataProviderTests.swift +++ b/Tests/XcodeMetadataTests/XCFrameworkMetadataProviderTests.swift @@ -97,7 +97,7 @@ struct XCFrameworkMetadataProviderTests { ) // When - let metadata = try await subject.loadMetadata(at: frameworkPath, status: .required) + let metadata = try await subject.loadMetadata(at: frameworkPath, expectedSignature: nil, status: .required) // Then let expectedInfoPlist = XCFrameworkInfoPlist(libraries: [ @@ -161,7 +161,7 @@ struct XCFrameworkMetadataProviderTests { ) // When - let metadata = try await subject.loadMetadata(at: frameworkPath, status: .required) + let metadata = try await subject.loadMetadata(at: frameworkPath, expectedSignature: nil, status: .required) // Then let expectedInfoPlist = XCFrameworkInfoPlist(libraries: [ @@ -243,7 +243,7 @@ struct XCFrameworkMetadataProviderTests { ) // When - let metadata = try await subject.loadMetadata(at: frameworkPath, status: .required) + let metadata = try await subject.loadMetadata(at: frameworkPath, expectedSignature: nil, status: .required) // Then #expect(metadata.infoPlist.libraries.count == 2, "Libraries count mismatch")