Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Sources/XcodeGraph/DependenciesGraph/DependenciesGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion Sources/XcodeGraph/Graph/GraphDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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 {
Expand Down
11 changes: 8 additions & 3 deletions Sources/XcodeGraph/Models/Metadata/XCFrameworkMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -32,6 +34,7 @@ public struct XCFrameworkMetadata: Equatable {
self.macroPath = macroPath
self.swiftModules = swiftModules
self.moduleMaps = moduleMaps
self.expectedSignature = expectedSignature
}
}

Expand All @@ -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,
Expand All @@ -56,7 +60,8 @@ public struct XCFrameworkMetadata: Equatable {
status: status,
macroPath: macroPath,
swiftModules: swiftModules,
moduleMaps: moduleMaps
moduleMaps: moduleMaps,
expectedSignature: expectedSignature
)
}
}
Expand Down
30 changes: 26 additions & 4 deletions Sources/XcodeGraph/Models/TargetDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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: _):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -131,20 +133,22 @@ 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 {
let pathString = try pathString.throwing(
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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -62,7 +66,8 @@ extension TargetDependency {
status: status,
macroPath: metadata.macroPath,
swiftModules: metadata.swiftModules,
moduleMaps: metadata.moduleMaps
moduleMaps: metadata.moduleMaps,
expectedSignature: metadata.expectedSignature?.signatureString()
)
)

Expand Down
17 changes: 14 additions & 3 deletions Sources/XcodeGraphMapper/Utilities/PathDependencyMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ public protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
/// - Parameter binaryPath: Path to the binary.
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID>

/// 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.
Expand All @@ -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 {
Expand All @@ -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
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 6 additions & 1 deletion Tests/XcodeGraphTests/Models/TargetDependencyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]

Expand Down
Loading