Add `ValueGraphLoader` (#2180)
* Add `ValueGraphLoader` - Adding a new loader that load and convert models to a `ValueGraph` - This introduces the new loader without integrating it (will be done in a separate commit) - Updated metadata loaders to allow loading metadata needed for external dependencies in one go (equivalent to the graph node loaders) - Added a new metadata provider for system frameworks (hosts the logic that used to be part of the deprecated SDKNode) Test Plan: - Verify unit tests pass * Minor tidy ups - Seperate types to their own dedicated files - Move errors to the top of the file - Add public initializers (as the types are public, they require their initalizers to be public too)
This commit is contained in:
parent
ab9e318c9a
commit
083bd54093
|
@ -5,6 +5,7 @@ import TuistSupport
|
|||
enum GraphLoadingError: FatalError, Equatable {
|
||||
case missingFile(AbsolutePath)
|
||||
case targetNotFound(String, AbsolutePath)
|
||||
case missingProject(AbsolutePath)
|
||||
case manifestNotFound(AbsolutePath)
|
||||
case circularDependency([GraphCircularDetectorNode])
|
||||
case unexpected(String)
|
||||
|
@ -15,6 +16,8 @@ enum GraphLoadingError: FatalError, Equatable {
|
|||
return lhsPath == rhsPath
|
||||
case let (.targetNotFound(lhsName, lhsPath), .targetNotFound(rhsName, rhsPath)):
|
||||
return lhsPath == rhsPath && lhsName == rhsName
|
||||
case let (.missingProject(lhsPath), .missingProject(rhsPath)):
|
||||
return lhsPath == rhsPath
|
||||
case let (.manifestNotFound(lhsPath), .manifestNotFound(rhsPath)):
|
||||
return lhsPath == rhsPath
|
||||
case let (.unexpected(lhsMessage), .unexpected(rhsMessage)):
|
||||
|
@ -36,6 +39,8 @@ enum GraphLoadingError: FatalError, Equatable {
|
|||
return "Couldn't find manifest at path: '\(path.pathString)'"
|
||||
case let .targetNotFound(targetName, path):
|
||||
return "Couldn't find target '\(targetName)' at '\(path.pathString)'"
|
||||
case let .missingProject(path):
|
||||
return "Could not locate project at path: \(path.pathString)"
|
||||
case let .missingFile(path):
|
||||
return "Couldn't find file at path '\(path.pathString)'"
|
||||
case let .unexpected(message):
|
||||
|
|
|
@ -2,27 +2,12 @@ import Foundation
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
public enum SDKSource {
|
||||
case developer // Platforms/iPhoneOS.platform/Developer/Library
|
||||
case system // Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library
|
||||
|
||||
/// Returns the framewok search path that should be used in Xcode to locate the SDK.
|
||||
public var frameworkSearchPath: String? {
|
||||
switch self {
|
||||
case .developer:
|
||||
return "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
case .system:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "SDK nodes are deprecated. Dependencies should be usted instead with the ValueGraph.")
|
||||
public class SDKNode: GraphNode {
|
||||
static let xctestFrameworkName = "XCTest.framework"
|
||||
|
||||
public let status: SDKStatus
|
||||
public let type: Type
|
||||
public let type: SDKType
|
||||
public let source: SDKSource
|
||||
|
||||
public init(name: String,
|
||||
|
@ -32,7 +17,7 @@ public class SDKNode: GraphNode {
|
|||
{
|
||||
let sdk = AbsolutePath("/\(name)")
|
||||
// TODO: Validate using a linter
|
||||
guard let sdkExtension = sdk.extension, let type = Type(rawValue: sdkExtension) else {
|
||||
guard let sdkExtension = sdk.extension, let type = SDKType(rawValue: sdkExtension) else {
|
||||
throw Error.unsupported(sdk: name)
|
||||
}
|
||||
self.status = status
|
||||
|
@ -60,7 +45,7 @@ public class SDKNode: GraphNode {
|
|||
try SDKNode(name: "AppClip.framework", platform: .iOS, status: status, source: .system)
|
||||
}
|
||||
|
||||
static func path(name: String, platform: Platform, source _: SDKSource, type: Type) throws -> AbsolutePath {
|
||||
static func path(name: String, platform: Platform, source _: SDKSource, type: SDKType) throws -> AbsolutePath {
|
||||
let sdkRootPath: AbsolutePath
|
||||
if name == SDKNode.xctestFrameworkName {
|
||||
guard let xcodeDeveloperSdkRootPath = platform.xcodeDeveloperSdkRootPath else {
|
||||
|
@ -87,24 +72,12 @@ public class SDKNode: GraphNode {
|
|||
}
|
||||
}
|
||||
|
||||
public enum `Type`: String, CaseIterable {
|
||||
case framework
|
||||
case library = "tbd"
|
||||
|
||||
static var supportedTypesDescription: String {
|
||||
let supportedTypes = allCases
|
||||
.map { ".\($0.rawValue)" }
|
||||
.joined(separator: ", ")
|
||||
return "[\(supportedTypes)]"
|
||||
}
|
||||
}
|
||||
|
||||
enum Error: FatalError, Equatable {
|
||||
case unsupported(sdk: String)
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .unsupported(sdk):
|
||||
let supportedTypes = Type.supportedTypesDescription
|
||||
let supportedTypes = SDKType.supportedTypesDescription
|
||||
return "The SDK type of \(sdk) is not currently supported - only \(supportedTypes) are supported."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,35 @@ import Foundation
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
// MARK: - Provider Errors
|
||||
|
||||
enum FrameworkMetadataProviderError: FatalError, Equatable {
|
||||
case frameworkNotFound(AbsolutePath)
|
||||
|
||||
// MARK: - FatalError
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .frameworkNotFound(path):
|
||||
return "Couldn't find framework at \(path.pathString)"
|
||||
}
|
||||
}
|
||||
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .frameworkNotFound:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Provider
|
||||
|
||||
public protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Loads all the metadata associated with a framework at the specified path
|
||||
/// - Note: This performs various shell calls and disk operations
|
||||
func loadMetadata(at path: AbsolutePath) throws -> FrameworkMetadata
|
||||
|
||||
/// Given the path to a framework, it returns the path to its dSYMs if they exist
|
||||
/// in the same framework directory.
|
||||
/// - Parameter frameworkPath: Path to the .framework directory.
|
||||
|
@ -18,7 +46,35 @@ public protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
|
|||
func product(frameworkPath: AbsolutePath) throws -> Product
|
||||
}
|
||||
|
||||
// MARK: - Default Implementation
|
||||
|
||||
public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func loadMetadata(at path: AbsolutePath) throws -> FrameworkMetadata {
|
||||
let fileHandler = FileHandler.shared
|
||||
guard fileHandler.exists(path) else {
|
||||
throw FrameworkMetadataProviderError.frameworkNotFound(path)
|
||||
}
|
||||
let binaryPath = self.binaryPath(frameworkPath: path)
|
||||
let dsymPath = self.dsymPath(frameworkPath: path)
|
||||
let bcsymbolmapPaths = try self.bcsymbolmapPaths(frameworkPath: path)
|
||||
let linking = try self.linking(binaryPath: binaryPath)
|
||||
let architectures = try self.architectures(binaryPath: binaryPath)
|
||||
let isCarthage = path.pathString.contains("Carthage/Build")
|
||||
return FrameworkMetadata(
|
||||
path: path,
|
||||
binaryPath: binaryPath,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
isCarthage: isCarthage
|
||||
)
|
||||
}
|
||||
|
||||
public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
let path = AbsolutePath("\(frameworkPath.pathString).dSYM")
|
||||
if FileHandler.shared.exists(path) { return path }
|
||||
|
@ -26,7 +82,7 @@ public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, Frame
|
|||
}
|
||||
|
||||
public func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
let binaryPath = self.binaryPath(frameworkPath: frameworkPath)
|
||||
let uuids = try self.uuids(binaryPath: binaryPath)
|
||||
return uuids
|
||||
.map { frameworkPath.parentDirectory.appending(component: "\($0).bcsymbolmap") }
|
||||
|
@ -35,7 +91,7 @@ public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, Frame
|
|||
}
|
||||
|
||||
public func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
let binaryPath = self.binaryPath(frameworkPath: frameworkPath)
|
||||
switch try linking(binaryPath: binaryPath) {
|
||||
case .dynamic:
|
||||
return .framework
|
||||
|
@ -43,4 +99,8 @@ public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, Frame
|
|||
return .staticFramework
|
||||
}
|
||||
}
|
||||
|
||||
private func binaryPath(frameworkPath: AbsolutePath) -> AbsolutePath {
|
||||
frameworkPath.appending(component: frameworkPath.basenameWithoutExt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,76 @@ import Foundation
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
protocol LibraryMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Returns the product for the given library.
|
||||
/// - Parameter library: Library instance.
|
||||
func product(library: LibraryNode) throws -> Product
|
||||
}
|
||||
// MARK: - Provider Errors
|
||||
|
||||
final class LibraryMetadataProvider: PrecompiledMetadataProvider, LibraryMetadataProviding {
|
||||
func product(library: LibraryNode) throws -> Product {
|
||||
switch try linking(binaryPath: library.path) {
|
||||
case .dynamic:
|
||||
return .dynamicLibrary
|
||||
case .static:
|
||||
return .staticLibrary
|
||||
enum LibraryMetadataProviderError: FatalError, Equatable {
|
||||
case libraryNotFound(AbsolutePath)
|
||||
case publicHeadersNotFound(libraryPath: AbsolutePath, headersPath: AbsolutePath)
|
||||
case swiftModuleMapNotFound(libraryPath: AbsolutePath, moduleMapPath: AbsolutePath)
|
||||
|
||||
// MARK: - FatalError
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .libraryNotFound(path):
|
||||
return "Couldn't find library at \(path.pathString)"
|
||||
case let .publicHeadersNotFound(libraryPath: libraryPath, headersPath: headersPath):
|
||||
return "Couldn't find the public headers at \(headersPath.pathString) for library \(libraryPath.pathString)"
|
||||
case let .swiftModuleMapNotFound(libraryPath: libraryPath, moduleMapPath: moduleMapPath):
|
||||
return "Couldn't find the public headers at \(moduleMapPath.pathString) for library \(libraryPath.pathString)"
|
||||
}
|
||||
}
|
||||
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .libraryNotFound, .publicHeadersNotFound, .swiftModuleMapNotFound:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Provider
|
||||
|
||||
public protocol LibraryMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Loads all the metadata associated with a library (.a / .dylib) at the specified path
|
||||
/// - Note: This performs various shell calls and disk operations
|
||||
func loadMetadata(at path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?) throws -> LibraryMetadata
|
||||
}
|
||||
|
||||
// MARK: - Default Implementation
|
||||
|
||||
public final class LibraryMetadataProvider: PrecompiledMetadataProvider, LibraryMetadataProviding {
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func loadMetadata(at path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?) throws -> LibraryMetadata
|
||||
{
|
||||
let fileHandler = FileHandler.shared
|
||||
guard fileHandler.exists(path) else {
|
||||
throw LibraryMetadataProviderError.libraryNotFound(path)
|
||||
}
|
||||
guard fileHandler.exists(publicHeaders) else {
|
||||
throw LibraryMetadataProviderError.publicHeadersNotFound(libraryPath: path, headersPath: publicHeaders)
|
||||
}
|
||||
if let swiftModuleMap = swiftModuleMap {
|
||||
guard fileHandler.exists(swiftModuleMap) else {
|
||||
throw LibraryMetadataProviderError.swiftModuleMapNotFound(libraryPath: path, moduleMapPath: swiftModuleMap)
|
||||
}
|
||||
}
|
||||
|
||||
let architectures = try self.architectures(binaryPath: path)
|
||||
let linking = try self.linking(binaryPath: path)
|
||||
return LibraryMetadata(
|
||||
path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap,
|
||||
architectures: architectures,
|
||||
linking: linking
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ public protocol PrecompiledMetadataProviding {
|
|||
}
|
||||
|
||||
public class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
public init() {}
|
||||
|
||||
public func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] {
|
||||
let result = try System.shared.capture("/usr/bin/lipo", "-info", binaryPath.pathString).spm_chuzzle() ?? ""
|
||||
let regexes = [
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
// MARK: - Provider Errors
|
||||
|
||||
public enum SystemFrameworkMetadataProviderError: FatalError, Equatable {
|
||||
case unsupportedSDK(name: String)
|
||||
case unsupportedSDKForPlatform(name: String, platform: Platform)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .unsupportedSDK(sdk):
|
||||
let supportedTypes = SDKType.supportedTypesDescription
|
||||
return "The SDK type of \(sdk) is not currently supported - only \(supportedTypes) are supported."
|
||||
case let .unsupportedSDKForPlatform(name: sdk, platform: platform):
|
||||
return "The SDK \(sdk) is not currently supported on \(platform)."
|
||||
}
|
||||
}
|
||||
|
||||
public var type: ErrorType {
|
||||
switch self {
|
||||
case .unsupportedSDK, .unsupportedSDKForPlatform:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Provider
|
||||
|
||||
public protocol SystemFrameworkMetadataProviding {
|
||||
func loadMetadata(sdkName: String, status: SDKStatus, platform: Platform, source: SDKSource) throws -> SystemFrameworkMetadata
|
||||
}
|
||||
|
||||
extension SystemFrameworkMetadataProviding {
|
||||
func loadXCTestMetadata(platform: Platform) throws -> SystemFrameworkMetadata {
|
||||
try loadMetadata(sdkName: "XCTest.framework", status: .required, platform: platform, source: .developer)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Default Implementation
|
||||
|
||||
public final class SystemFrameworkMetadataProvider: SystemFrameworkMetadataProviding {
|
||||
public init() {}
|
||||
|
||||
public func loadMetadata(sdkName: String, status: SDKStatus, platform: Platform, source: SDKSource) throws -> SystemFrameworkMetadata {
|
||||
let sdkNamePath = AbsolutePath("/\(sdkName)")
|
||||
guard let sdkExtension = sdkNamePath.extension,
|
||||
let sdkType = SDKType(rawValue: sdkExtension)
|
||||
else {
|
||||
throw SystemFrameworkMetadataProviderError.unsupportedSDK(name: sdkName)
|
||||
}
|
||||
let path = try sdkPath(name: sdkName, platform: platform, type: sdkType, source: source)
|
||||
return SystemFrameworkMetadata(
|
||||
name: sdkName,
|
||||
path: path,
|
||||
status: status,
|
||||
source: source
|
||||
)
|
||||
}
|
||||
|
||||
private func sdkPath(name: String, platform: Platform, type: SDKType, source: SDKSource) throws -> AbsolutePath {
|
||||
switch source {
|
||||
case .developer:
|
||||
guard let xcodeDeveloperSdkRootPath = platform.xcodeDeveloperSdkRootPath else {
|
||||
throw SystemFrameworkMetadataProviderError.unsupportedSDKForPlatform(name: name, platform: platform)
|
||||
}
|
||||
let sdkRootPath = AbsolutePath("/\(xcodeDeveloperSdkRootPath)")
|
||||
return sdkRootPath
|
||||
.appending(RelativePath("Frameworks"))
|
||||
.appending(component: name)
|
||||
|
||||
case .system:
|
||||
let sdkRootPath = AbsolutePath("/\(platform.xcodeSdkRootPath)")
|
||||
switch type {
|
||||
case .framework:
|
||||
return sdkRootPath
|
||||
.appending(RelativePath("System/Library/Frameworks"))
|
||||
.appending(component: name)
|
||||
case .library:
|
||||
return sdkRootPath
|
||||
.appending(RelativePath("usr/lib"))
|
||||
.appending(component: name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,10 @@ import Foundation
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
// MARK: - Provider Errors
|
||||
|
||||
enum XCFrameworkMetadataProviderError: FatalError, Equatable {
|
||||
case xcframeworkNotFound(AbsolutePath)
|
||||
case missingRequiredFile(AbsolutePath)
|
||||
case supportedArchitectureReferencesNotFound(AbsolutePath)
|
||||
case fileTypeNotRecognised(file: RelativePath, frameworkName: String)
|
||||
|
@ -11,6 +14,8 @@ enum XCFrameworkMetadataProviderError: FatalError, Equatable {
|
|||
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .xcframeworkNotFound(path):
|
||||
return "Couldn't find xcframework at \(path.pathString)"
|
||||
case let .missingRequiredFile(path):
|
||||
return "The .xcframework at path \(path.pathString) doesn't contain an Info.plist. It's possible that the .xcframework was not generated properly or that got corrupted. Please, double check with the author of the framework."
|
||||
case let .supportedArchitectureReferencesNotFound(path):
|
||||
|
@ -22,13 +27,19 @@ enum XCFrameworkMetadataProviderError: FatalError, Equatable {
|
|||
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .missingRequiredFile, .supportedArchitectureReferencesNotFound, .fileTypeNotRecognised:
|
||||
case .xcframeworkNotFound, .missingRequiredFile, .supportedArchitectureReferencesNotFound, .fileTypeNotRecognised:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
// MARK: - Provider
|
||||
|
||||
public protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Loads all the metadata associated with an XCFramework at the specified path
|
||||
/// - Note: This performs various shell calls and disk operations
|
||||
func loadMetadata(at path: AbsolutePath) throws -> XCFrameworkMetadata
|
||||
|
||||
/// Returns the info.plist of the xcframework at the given path.
|
||||
/// - Parameter xcframeworkPath: Path to the xcframework.
|
||||
func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist
|
||||
|
@ -39,8 +50,33 @@ protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
|
|||
func binaryPath(xcframeworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath
|
||||
}
|
||||
|
||||
class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, XCFrameworkMetadataProviding {
|
||||
func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist {
|
||||
// MARK: - Default Implementation
|
||||
|
||||
public final class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, XCFrameworkMetadataProviding {
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func loadMetadata(at path: AbsolutePath) throws -> XCFrameworkMetadata {
|
||||
let fileHandler = FileHandler.shared
|
||||
guard fileHandler.exists(path) else {
|
||||
throw XCFrameworkMetadataProviderError.xcframeworkNotFound(path)
|
||||
}
|
||||
let infoPlist = try self.infoPlist(xcframeworkPath: path)
|
||||
let primaryBinaryPath = try binaryPath(
|
||||
xcframeworkPath: path,
|
||||
libraries: infoPlist.libraries
|
||||
)
|
||||
let linking = try self.linking(binaryPath: primaryBinaryPath)
|
||||
return XCFrameworkMetadata(
|
||||
path: path,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: primaryBinaryPath,
|
||||
linking: linking
|
||||
)
|
||||
}
|
||||
|
||||
public func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist {
|
||||
let fileHandler = FileHandler.shared
|
||||
let infoPlist = xcframeworkPath.appending(component: "Info.plist")
|
||||
guard fileHandler.exists(infoPlist) else {
|
||||
|
@ -50,7 +86,7 @@ class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, XCFrameworkMetad
|
|||
return try fileHandler.readPlistFile(infoPlist)
|
||||
}
|
||||
|
||||
func binaryPath(xcframeworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath {
|
||||
public func binaryPath(xcframeworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath {
|
||||
let archs: [BinaryArchitecture] = [.arm64, .x8664]
|
||||
guard let library = libraries.first(where: { !$0.architectures.filter(archs.contains).isEmpty }) else {
|
||||
throw XCFrameworkMetadataProviderError.supportedArchitectureReferencesNotFound(xcframeworkPath)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// The metadata associated with a precompiled framework (.framework)
|
||||
public struct FrameworkMetadata: Equatable {
|
||||
public var path: AbsolutePath
|
||||
public var binaryPath: AbsolutePath
|
||||
public var dsymPath: AbsolutePath?
|
||||
public var bcsymbolmapPaths: [AbsolutePath]
|
||||
public var linking: BinaryLinking
|
||||
public var architectures: [BinaryArchitecture]
|
||||
public var isCarthage: Bool
|
||||
|
||||
public init(
|
||||
path: AbsolutePath,
|
||||
binaryPath: AbsolutePath,
|
||||
dsymPath: AbsolutePath?,
|
||||
bcsymbolmapPaths: [AbsolutePath],
|
||||
linking: BinaryLinking,
|
||||
architectures: [BinaryArchitecture],
|
||||
isCarthage: Bool
|
||||
) {
|
||||
self.path = path
|
||||
self.binaryPath = binaryPath
|
||||
self.dsymPath = dsymPath
|
||||
self.bcsymbolmapPaths = bcsymbolmapPaths
|
||||
self.linking = linking
|
||||
self.architectures = architectures
|
||||
self.isCarthage = isCarthage
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// The metadata associated with a precompiled library (.a / .dylib)
|
||||
public struct LibraryMetadata: Equatable {
|
||||
public var path: AbsolutePath
|
||||
public var publicHeaders: AbsolutePath
|
||||
public var swiftModuleMap: AbsolutePath?
|
||||
public var architectures: [BinaryArchitecture]
|
||||
public var linking: BinaryLinking
|
||||
|
||||
public init(
|
||||
path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?,
|
||||
architectures: [BinaryArchitecture],
|
||||
linking: BinaryLinking
|
||||
) {
|
||||
self.path = path
|
||||
self.publicHeaders = publicHeaders
|
||||
self.swiftModuleMap = swiftModuleMap
|
||||
self.architectures = architectures
|
||||
self.linking = linking
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// The metadata associated with a system framework or library (e.g. UIKit.framework, libc++.tbd)
|
||||
public struct SystemFrameworkMetadata: Equatable {
|
||||
var name: String
|
||||
var path: AbsolutePath
|
||||
var status: SDKStatus
|
||||
var source: SDKSource
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
path: AbsolutePath,
|
||||
status: SDKStatus,
|
||||
source: SDKSource
|
||||
) {
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.status = status
|
||||
self.source = source
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// The metadata associated with a precompiled xcframework
|
||||
public struct XCFrameworkMetadata: Equatable {
|
||||
public var path: AbsolutePath
|
||||
public var infoPlist: XCFrameworkInfoPlist
|
||||
public var primaryBinaryPath: AbsolutePath
|
||||
public var linking: BinaryLinking
|
||||
|
||||
public init(
|
||||
path: AbsolutePath,
|
||||
infoPlist: XCFrameworkInfoPlist,
|
||||
primaryBinaryPath: AbsolutePath,
|
||||
linking: BinaryLinking
|
||||
) {
|
||||
self.path = path
|
||||
self.infoPlist = infoPlist
|
||||
self.primaryBinaryPath = primaryBinaryPath
|
||||
self.linking = linking
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
public enum SDKSource: Equatable {
|
||||
case developer // Platforms/iPhoneOS.platform/Developer/Library
|
||||
case system // Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library
|
||||
|
||||
/// Returns the framework search path that should be used in Xcode to locate the SDK.
|
||||
public var frameworkSearchPath: String? {
|
||||
switch self {
|
||||
case .developer:
|
||||
return "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
case .system:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
public enum SDKType: String, CaseIterable, Equatable {
|
||||
case framework
|
||||
case library = "tbd"
|
||||
|
||||
static var supportedTypesDescription: String {
|
||||
let supportedTypes = allCases
|
||||
.map { ".\($0.rawValue)" }
|
||||
.joined(separator: ", ")
|
||||
return "[\(supportedTypes)]"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
public protocol ValueGraphLoading {
|
||||
func loadWorkspace(workspace: Workspace, projects: [Project]) throws -> ValueGraph
|
||||
func loadProject(at path: AbsolutePath, projects: [Project]) throws -> (Project, ValueGraph)
|
||||
}
|
||||
|
||||
// MARK: - ValueGraphLoader
|
||||
|
||||
public final class ValueGraphLoader: ValueGraphLoading {
|
||||
private let frameworkMetadataProvider: FrameworkMetadataProviding
|
||||
private let libraryMetadataProvider: LibraryMetadataProviding
|
||||
private let xcframeworkMetadataProvider: XCFrameworkMetadataProviding
|
||||
private let systemFrameworkMetadataProvider: SystemFrameworkMetadataProviding
|
||||
|
||||
public convenience init() {
|
||||
self.init(
|
||||
frameworkMetadataProvider: FrameworkMetadataProvider(),
|
||||
libraryMetadataProvider: LibraryMetadataProvider(),
|
||||
xcframeworkMetadataProvider: XCFrameworkMetadataProvider(),
|
||||
systemFrameworkMetadataProvider: SystemFrameworkMetadataProvider()
|
||||
)
|
||||
}
|
||||
|
||||
public init(
|
||||
frameworkMetadataProvider: FrameworkMetadataProviding,
|
||||
libraryMetadataProvider: LibraryMetadataProviding,
|
||||
xcframeworkMetadataProvider: XCFrameworkMetadataProviding,
|
||||
systemFrameworkMetadataProvider: SystemFrameworkMetadataProviding
|
||||
) {
|
||||
self.frameworkMetadataProvider = frameworkMetadataProvider
|
||||
self.libraryMetadataProvider = libraryMetadataProvider
|
||||
self.xcframeworkMetadataProvider = xcframeworkMetadataProvider
|
||||
self.systemFrameworkMetadataProvider = systemFrameworkMetadataProvider
|
||||
}
|
||||
|
||||
// MARK: - ValueGraphLoading
|
||||
|
||||
public func loadWorkspace(workspace: Workspace, projects: [Project]) throws -> ValueGraph {
|
||||
let cache = Cache(projects: projects)
|
||||
let cycleDetector = GraphCircularDetector()
|
||||
|
||||
try workspace.projects.forEach { project in
|
||||
try loadProject(
|
||||
path: project,
|
||||
cache: cache,
|
||||
cycleDetector: cycleDetector
|
||||
)
|
||||
}
|
||||
|
||||
let updatedWorkspace = workspace.replacing(projects: cache.loadedProjects.keys.sorted())
|
||||
let graph = ValueGraph(
|
||||
name: updatedWorkspace.name,
|
||||
path: updatedWorkspace.path,
|
||||
workspace: updatedWorkspace,
|
||||
projects: cache.loadedProjects,
|
||||
packages: cache.packages,
|
||||
targets: cache.loadedTargets,
|
||||
dependencies: cache.dependencies
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
||||
public func loadProject(at path: AbsolutePath, projects: [Project]) throws -> (Project, ValueGraph) {
|
||||
let cache = Cache(projects: projects)
|
||||
guard let rootProject = cache.allProjects[path] else {
|
||||
throw GraphLoadingError.missingProject(path)
|
||||
}
|
||||
let cycleDetector = GraphCircularDetector()
|
||||
try loadProject(path: path, cache: cache, cycleDetector: cycleDetector)
|
||||
|
||||
let workspace = Workspace(
|
||||
path: path,
|
||||
name: rootProject.name,
|
||||
projects: cache.loadedProjects.keys.sorted()
|
||||
)
|
||||
let graph = ValueGraph(
|
||||
name: rootProject.name,
|
||||
path: path,
|
||||
workspace: workspace,
|
||||
projects: cache.loadedProjects,
|
||||
packages: cache.packages,
|
||||
targets: cache.loadedTargets,
|
||||
dependencies: cache.dependencies
|
||||
)
|
||||
return (rootProject, graph)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadProject(
|
||||
path: AbsolutePath,
|
||||
cache: Cache,
|
||||
cycleDetector: GraphCircularDetector
|
||||
) throws {
|
||||
guard !cache.projectLoaded(path: path) else {
|
||||
return
|
||||
}
|
||||
guard let project = cache.allProjects[path] else {
|
||||
throw GraphLoadingError.missingProject(path)
|
||||
}
|
||||
cache.add(project: project)
|
||||
|
||||
try project.targets.forEach {
|
||||
try loadTarget(
|
||||
path: path,
|
||||
name: $0.name,
|
||||
cache: cache,
|
||||
cycleDetector: cycleDetector
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadTarget(
|
||||
path: AbsolutePath,
|
||||
name: String,
|
||||
cache: Cache,
|
||||
cycleDetector: GraphCircularDetector
|
||||
) throws {
|
||||
guard !cache.targetLoaded(path: path, name: name) else {
|
||||
return
|
||||
}
|
||||
guard let _ = cache.allProjects[path] else {
|
||||
throw GraphLoadingError.missingProject(path)
|
||||
}
|
||||
guard let referencedTargetProject = cache.allTargets[path],
|
||||
let target = referencedTargetProject[name]
|
||||
else {
|
||||
throw GraphLoadingError.targetNotFound(name, path)
|
||||
}
|
||||
|
||||
cache.add(target: target, path: path)
|
||||
let dependencies = try target.dependencies.map {
|
||||
try loadDependency(
|
||||
path: path,
|
||||
fromTarget: target.name,
|
||||
fromPlatform: target.platform,
|
||||
dependency: $0,
|
||||
cache: cache,
|
||||
cycleDetector: cycleDetector
|
||||
)
|
||||
}
|
||||
|
||||
try cycleDetector.complete()
|
||||
|
||||
if !dependencies.isEmpty {
|
||||
cache.dependencies[.target(name: name, path: path)] = Set(dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadDependency(
|
||||
path: AbsolutePath,
|
||||
fromTarget: String,
|
||||
fromPlatform: Platform,
|
||||
dependency: Dependency,
|
||||
cache: Cache,
|
||||
cycleDetector: GraphCircularDetector
|
||||
) throws -> ValueGraphDependency {
|
||||
switch dependency {
|
||||
case let .target(toTarget):
|
||||
// A target within the same project.
|
||||
let circularFrom = GraphCircularDetectorNode(path: path, name: fromTarget)
|
||||
let circularTo = GraphCircularDetectorNode(path: path, name: toTarget)
|
||||
cycleDetector.start(from: circularFrom, to: circularTo)
|
||||
try loadTarget(
|
||||
path: path,
|
||||
name: toTarget,
|
||||
cache: cache,
|
||||
cycleDetector: cycleDetector
|
||||
)
|
||||
return .target(name: toTarget, path: path)
|
||||
|
||||
case let .project(toTarget, projectPath):
|
||||
// A target from another project
|
||||
let circularFrom = GraphCircularDetectorNode(path: path, name: fromTarget)
|
||||
let circularTo = GraphCircularDetectorNode(path: projectPath, name: toTarget)
|
||||
cycleDetector.start(from: circularFrom, to: circularTo)
|
||||
try loadProject(path: projectPath, cache: cache, cycleDetector: cycleDetector)
|
||||
try loadTarget(
|
||||
path: projectPath,
|
||||
name: toTarget,
|
||||
cache: cache,
|
||||
cycleDetector: cycleDetector
|
||||
)
|
||||
return .target(name: toTarget, path: projectPath)
|
||||
|
||||
case let .framework(frameworkPath):
|
||||
return try loadFramework(path: frameworkPath, cache: cache)
|
||||
|
||||
case let .library(libraryPath, publicHeaders, swiftModuleMap):
|
||||
return try loadLibrary(
|
||||
path: libraryPath,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap,
|
||||
cache: cache
|
||||
)
|
||||
|
||||
case let .xcFramework(frameworkPath):
|
||||
return try loadXCFramework(path: frameworkPath, cache: cache)
|
||||
|
||||
case let .sdk(name, status):
|
||||
return try loadSDK(name: name, platform: fromPlatform, status: status, source: .system)
|
||||
|
||||
case let .cocoapods(podsPath):
|
||||
return .cocoapods(path: podsPath)
|
||||
|
||||
case let .package(product):
|
||||
return try loadPackage(fromPath: path, productName: product)
|
||||
|
||||
case .xctest:
|
||||
return try loadXCTestSDK(platform: fromPlatform)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadFramework(path: AbsolutePath, cache: Cache) throws -> ValueGraphDependency {
|
||||
if let loaded = cache.frameworks[path] {
|
||||
return loaded
|
||||
}
|
||||
|
||||
let metadata = try frameworkMetadataProvider.loadMetadata(at: path)
|
||||
let framework: ValueGraphDependency = .framework(
|
||||
path: metadata.path,
|
||||
binaryPath: metadata.binaryPath,
|
||||
dsymPath: metadata.dsymPath,
|
||||
bcsymbolmapPaths: metadata.bcsymbolmapPaths,
|
||||
linking: metadata.linking,
|
||||
architectures: metadata.architectures,
|
||||
isCarthage: metadata.isCarthage
|
||||
)
|
||||
cache.add(framework: framework, at: path)
|
||||
return framework
|
||||
}
|
||||
|
||||
private func loadLibrary(
|
||||
path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?,
|
||||
cache: Cache
|
||||
) throws -> ValueGraphDependency {
|
||||
if let loaded = cache.libraries[path] {
|
||||
return loaded
|
||||
}
|
||||
|
||||
let metadata = try libraryMetadataProvider.loadMetadata(
|
||||
at: path,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap
|
||||
)
|
||||
let library: ValueGraphDependency = .library(
|
||||
path: metadata.path,
|
||||
publicHeaders: metadata.publicHeaders,
|
||||
linking: metadata.linking,
|
||||
architectures: metadata.architectures,
|
||||
swiftModuleMap: metadata.swiftModuleMap
|
||||
)
|
||||
cache.add(library: library, at: path)
|
||||
return library
|
||||
}
|
||||
|
||||
private func loadXCFramework(path: AbsolutePath, cache: Cache) throws -> ValueGraphDependency {
|
||||
if let loaded = cache.xcframeworks[path] {
|
||||
return loaded
|
||||
}
|
||||
|
||||
let metadata = try xcframeworkMetadataProvider.loadMetadata(at: path)
|
||||
let xcframework: ValueGraphDependency = .xcframework(
|
||||
path: metadata.path,
|
||||
infoPlist: metadata.infoPlist,
|
||||
primaryBinaryPath: metadata.primaryBinaryPath,
|
||||
linking: metadata.linking
|
||||
)
|
||||
cache.add(xcframework: xcframework, at: path)
|
||||
return xcframework
|
||||
}
|
||||
|
||||
private func loadSDK(name: String,
|
||||
platform: Platform,
|
||||
status: SDKStatus,
|
||||
source: SDKSource) throws -> ValueGraphDependency
|
||||
{
|
||||
let metadata = try systemFrameworkMetadataProvider.loadMetadata(sdkName: name, status: status, platform: platform, source: source)
|
||||
return .sdk(name: metadata.name, path: metadata.path, status: metadata.status, source: metadata.source)
|
||||
}
|
||||
|
||||
private func loadXCTestSDK(platform: Platform) throws -> ValueGraphDependency {
|
||||
let metadata = try systemFrameworkMetadataProvider.loadXCTestMetadata(platform: platform)
|
||||
return .sdk(name: metadata.name, path: metadata.path, status: metadata.status, source: metadata.source)
|
||||
}
|
||||
|
||||
private func loadPackage(fromPath: AbsolutePath, productName: String) throws -> ValueGraphDependency {
|
||||
// TODO: `fromPath` isn't quite correct as it reflects the path where the dependency was declared
|
||||
// and doesn't uniquely identify it. It's been copied from the previous implementation to maintain
|
||||
// existing behaviour and should be fixed separately
|
||||
.packageProduct(
|
||||
path: fromPath,
|
||||
product: productName
|
||||
)
|
||||
}
|
||||
|
||||
private final class Cache {
|
||||
let allProjects: [AbsolutePath: Project]
|
||||
let allTargets: [AbsolutePath: [String: Target]]
|
||||
|
||||
var loadedProjects: [AbsolutePath: Project] = [:]
|
||||
var loadedTargets: [AbsolutePath: [String: Target]] = [:]
|
||||
var dependencies: [ValueGraphDependency: Set<ValueGraphDependency>] = [:]
|
||||
var frameworks: [AbsolutePath: ValueGraphDependency] = [:]
|
||||
var libraries: [AbsolutePath: ValueGraphDependency] = [:]
|
||||
var xcframeworks: [AbsolutePath: ValueGraphDependency] = [:]
|
||||
var packages: [AbsolutePath: [String: Package]] = [:]
|
||||
|
||||
init(projects: [Project]) {
|
||||
let allProjects = Dictionary(uniqueKeysWithValues: projects.map { ($0.path, $0) })
|
||||
let allTargets = allProjects.mapValues {
|
||||
Dictionary(uniqueKeysWithValues: $0.targets.map { ($0.name, $0) })
|
||||
}
|
||||
self.allProjects = allProjects
|
||||
self.allTargets = allTargets
|
||||
}
|
||||
|
||||
func add(project: Project) {
|
||||
loadedProjects[project.path] = project
|
||||
project.packages.forEach {
|
||||
packages[project.path, default: [:]][$0.name] = $0
|
||||
}
|
||||
}
|
||||
|
||||
func add(target: Target, path: AbsolutePath) {
|
||||
loadedTargets[path, default: [:]][target.name] = target
|
||||
}
|
||||
|
||||
func add(framework: ValueGraphDependency, at path: AbsolutePath) {
|
||||
frameworks[path] = framework
|
||||
}
|
||||
|
||||
func add(xcframework: ValueGraphDependency, at path: AbsolutePath) {
|
||||
xcframeworks[path] = xcframework
|
||||
}
|
||||
|
||||
func add(library: ValueGraphDependency, at path: AbsolutePath) {
|
||||
libraries[path] = library
|
||||
}
|
||||
|
||||
func targetLoaded(path: AbsolutePath, name: String) -> Bool {
|
||||
loadedTargets[path]?[name] != nil
|
||||
}
|
||||
|
||||
func projectLoaded(path: AbsolutePath) -> Bool {
|
||||
loadedProjects[path] != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Package {
|
||||
var name: String {
|
||||
switch self {
|
||||
case let .local(path: path):
|
||||
return path.pathString
|
||||
case let .remote(url: url, requirement: _):
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,15 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public final class MockFrameworkMetadataProvider: MockPrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
public var loadMetadataStub: ((AbsolutePath) throws -> FrameworkMetadata)?
|
||||
public func loadMetadata(at path: AbsolutePath) throws -> FrameworkMetadata {
|
||||
if let loadMetadataStub = loadMetadataStub {
|
||||
return try loadMetadataStub(path)
|
||||
} else {
|
||||
return FrameworkMetadata.test(path: path)
|
||||
}
|
||||
}
|
||||
|
||||
public var dsymPathStub: ((AbsolutePath) -> AbsolutePath?)?
|
||||
public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
dsymPathStub?(frameworkPath) ?? nil
|
||||
|
|
|
@ -3,12 +3,19 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public final class MockLibraryMetadataProvider: MockPrecompiledMetadataProvider, LibraryMetadataProviding {
|
||||
public var productStub: ((LibraryNode) throws -> Product)?
|
||||
public func product(library: LibraryNode) throws -> Product {
|
||||
if let productStub = productStub {
|
||||
return try productStub(library)
|
||||
public var loadMetadataStub: ((AbsolutePath, AbsolutePath, AbsolutePath?) throws -> LibraryMetadata)?
|
||||
public func loadMetadata(at path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?) throws -> LibraryMetadata
|
||||
{
|
||||
if let stub = loadMetadataStub {
|
||||
return try stub(path, publicHeaders, swiftModuleMap)
|
||||
} else {
|
||||
return .staticLibrary
|
||||
return LibraryMetadata.test(
|
||||
path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,18 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public final class MockXCFrameworkMetadataProvider: MockPrecompiledMetadataProvider, XCFrameworkMetadataProviding {
|
||||
public var loadMetadataStub: ((AbsolutePath) throws -> XCFrameworkMetadata)?
|
||||
public func loadMetadata(at path: AbsolutePath) throws -> XCFrameworkMetadata {
|
||||
if let loadMetadataStub = loadMetadataStub {
|
||||
return try loadMetadataStub(path)
|
||||
} else {
|
||||
return XCFrameworkMetadata.test(
|
||||
path: path,
|
||||
primaryBinaryPath: path.appending(RelativePath("ios-arm64/binary"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public var infoPlistStub: ((AbsolutePath) throws -> XCFrameworkInfoPlist)?
|
||||
public func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist {
|
||||
if let infoPlistStub = infoPlistStub {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Kassem Wridan on 22/12/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
|
||||
public extension FrameworkMetadata {
|
||||
static func test(
|
||||
path: AbsolutePath = "/Frameworks/TestFramework.xframework",
|
||||
binaryPath: AbsolutePath = "/Frameworks/TestFramework.xframework/TestFramework",
|
||||
dsymPath: AbsolutePath? = nil,
|
||||
bcsymbolmapPaths: [AbsolutePath] = [],
|
||||
linking: BinaryLinking = .dynamic,
|
||||
architectures: [BinaryArchitecture] = [.arm64],
|
||||
isCarthage: Bool = false
|
||||
) -> FrameworkMetadata {
|
||||
FrameworkMetadata(
|
||||
path: path,
|
||||
binaryPath: binaryPath,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
isCarthage: isCarthage
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Kassem Wridan on 22/12/2020.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
|
||||
public extension LibraryMetadata {
|
||||
static func test(
|
||||
path: AbsolutePath = "/Libraries/libTest/libTest.a",
|
||||
publicHeaders: AbsolutePath = "/Libraries/libTest/include",
|
||||
swiftModuleMap: AbsolutePath? = "/Libraries/libTest/libTest.swiftmodule",
|
||||
architectures: [BinaryArchitecture] = [.arm64],
|
||||
linking: BinaryLinking = .static
|
||||
) -> LibraryMetadata {
|
||||
LibraryMetadata(
|
||||
path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap,
|
||||
architectures: architectures,
|
||||
linking: linking
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
|
||||
public extension XCFrameworkMetadata {
|
||||
static func test(
|
||||
path: AbsolutePath = "/XCFrameworks/XCFramework.xcframework",
|
||||
infoPlist: XCFrameworkInfoPlist = .test(),
|
||||
primaryBinaryPath: AbsolutePath = "/XCFrameworks/XCFramework.xcframework/ios-arm64/XCFramework",
|
||||
linking: BinaryLinking = .dynamic
|
||||
) -> XCFrameworkMetadata {
|
||||
XCFrameworkMetadata(
|
||||
path: path,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: primaryBinaryPath,
|
||||
linking: linking
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import TSCBasic
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class FrameworkMetadataProviderTests: XCTestCase {
|
||||
var subject: FrameworkMetadataProvider!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = FrameworkMetadataProvider()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_loadMetadata() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("xpm.framework"))
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(at: frameworkPath)
|
||||
|
||||
// Then
|
||||
let expectedBinaryPath = frameworkPath.appending(component: frameworkPath.basenameWithoutExt)
|
||||
let expectedDsymPath = frameworkPath.parentDirectory.appending(component: "xpm.framework.dSYM")
|
||||
XCTAssertEqual(metadata, FrameworkMetadata(
|
||||
path: frameworkPath,
|
||||
binaryPath: expectedBinaryPath,
|
||||
dsymPath: expectedDsymPath,
|
||||
bcsymbolmapPaths: [],
|
||||
linking: .dynamic,
|
||||
architectures: [.x8664, .arm64],
|
||||
isCarthage: false
|
||||
))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import TSCBasic
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class LibraryMetadataProviderTests: XCTestCase {
|
||||
var subject: LibraryMetadataProvider!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = LibraryMetadataProvider()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_loadMetadata() throws {
|
||||
// Given
|
||||
let libraryPath = fixturePath(path: RelativePath("libStaticLibrary.a"))
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(at: libraryPath, publicHeaders: libraryPath.parentDirectory, swiftModuleMap: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(metadata, LibraryMetadata(
|
||||
path: libraryPath,
|
||||
publicHeaders: libraryPath.parentDirectory,
|
||||
swiftModuleMap: nil,
|
||||
architectures: [.x8664],
|
||||
linking: .static
|
||||
))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import TSCBasic
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class SystemFrameworkMetadataProviderTests: XCTestCase {
|
||||
var subject: SystemFrameworkMetadataProvider!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = SystemFrameworkMetadataProvider()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_loadMetadata_framework() throws {
|
||||
// Given
|
||||
let sdkName = "UIKit.framework"
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(sdkName: sdkName, status: .required, platform: .iOS, source: .system)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(metadata, SystemFrameworkMetadata(
|
||||
name: sdkName,
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework",
|
||||
status: .required,
|
||||
source: .system
|
||||
))
|
||||
}
|
||||
|
||||
func test_loadMetadata_library() throws {
|
||||
// Given
|
||||
let sdkName = "libc++.tbd"
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(sdkName: sdkName, status: .required, platform: .iOS, source: .system)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(metadata, SystemFrameworkMetadata(
|
||||
name: sdkName,
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libc++.tbd",
|
||||
status: .required,
|
||||
source: .system
|
||||
))
|
||||
}
|
||||
|
||||
func test_loadMetadata_unsupportedType() throws {
|
||||
// Given
|
||||
let sdkName = "UIKit.xcframework"
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadMetadata(sdkName: sdkName, status: .required, platform: .iOS, source: .system),
|
||||
SystemFrameworkMetadataProviderError.unsupportedSDK(name: "UIKit.xcframework")
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadMetadata_developerSource_unsupportedPlatform() throws {
|
||||
// Given
|
||||
let sdkName = "XCTest.framework"
|
||||
let source = SDKSource.developer
|
||||
let platform = Platform.watchOS // watchOS doesn't support XCTest
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadMetadata(sdkName: sdkName, status: .required, platform: platform, source: source),
|
||||
SystemFrameworkMetadataProviderError.unsupportedSDKForPlatform(name: "XCTest.framework", platform: .watchOS)
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadMetadata_developerSource_supportedPlatform() throws {
|
||||
// Given
|
||||
let sdkName = "XCTest.framework"
|
||||
let source = SDKSource.developer
|
||||
let platform = Platform.iOS
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(sdkName: sdkName, status: .required, platform: platform, source: source)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(metadata, SystemFrameworkMetadata(
|
||||
name: sdkName,
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework",
|
||||
status: .required,
|
||||
source: .developer
|
||||
))
|
||||
}
|
||||
}
|
|
@ -74,4 +74,62 @@ final class XCFrameworkMetadataProviderTests: XCTestCase {
|
|||
frameworkPath.appending(RelativePath("ios-x86_64-simulator/libMyStaticLibrary.a"))
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadMetadata_dynamicLibrary() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyFramework.xcframework"))
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(at: frameworkPath)
|
||||
|
||||
// Then
|
||||
let expectedInfoPlist = XCFrameworkInfoPlist(libraries: [
|
||||
.init(
|
||||
identifier: "ios-x86_64-simulator",
|
||||
path: RelativePath("MyFramework.framework"),
|
||||
architectures: [.x8664]
|
||||
),
|
||||
.init(
|
||||
identifier: "ios-arm64",
|
||||
path: RelativePath("MyFramework.framework"),
|
||||
architectures: [.arm64]
|
||||
),
|
||||
])
|
||||
let expectedBinaryPath = frameworkPath.appending(RelativePath("ios-x86_64-simulator/MyFramework.framework/MyFramework"))
|
||||
XCTAssertEqual(metadata, XCFrameworkMetadata(
|
||||
path: frameworkPath,
|
||||
infoPlist: expectedInfoPlist,
|
||||
primaryBinaryPath: expectedBinaryPath,
|
||||
linking: .dynamic
|
||||
))
|
||||
}
|
||||
|
||||
func test_loadMetadata_staticLibrary() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyStaticLibrary.xcframework"))
|
||||
|
||||
// When
|
||||
let metadata = try subject.loadMetadata(at: frameworkPath)
|
||||
|
||||
// Then
|
||||
let expectedInfoPlist = XCFrameworkInfoPlist(libraries: [
|
||||
.init(
|
||||
identifier: "ios-x86_64-simulator",
|
||||
path: RelativePath("libMyStaticLibrary.a"),
|
||||
architectures: [.x8664]
|
||||
),
|
||||
.init(
|
||||
identifier: "ios-arm64",
|
||||
path: RelativePath("libMyStaticLibrary.a"),
|
||||
architectures: [.arm64]
|
||||
),
|
||||
])
|
||||
let expectedBinaryPath = frameworkPath.appending(RelativePath("ios-x86_64-simulator/libMyStaticLibrary.a"))
|
||||
XCTAssertEqual(metadata, XCFrameworkMetadata(
|
||||
path: frameworkPath,
|
||||
infoPlist: expectedInfoPlist,
|
||||
primaryBinaryPath: expectedBinaryPath,
|
||||
linking: .static
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,766 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class ValueGraphLoaderTests: TuistUnitTestCase {
|
||||
private var stubbedFrameworks = [AbsolutePath: PrecompiledMetadata]()
|
||||
private var stubbedLibraries = [AbsolutePath: PrecompiledMetadata]()
|
||||
private var stubbedXCFrameworks = [AbsolutePath: XCFrameworkMetadata]()
|
||||
private var frameworkMetadataProvider: MockFrameworkMetadataProvider!
|
||||
private var libraryMetadataProvider: MockLibraryMetadataProvider!
|
||||
private var xcframeworkMetadataProvider: MockXCFrameworkMetadataProvider!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
frameworkMetadataProvider = makeFrameworkMetadataProvider()
|
||||
libraryMetadataProvider = makeLibraryMetadataProvider()
|
||||
xcframeworkMetadataProvider = makeXCFrameworkMetadataProvider()
|
||||
}
|
||||
|
||||
// MARK: - Load Workspace
|
||||
|
||||
func test_loadWorkspace_unreferencedProjectsAreExcluded() throws {
|
||||
// Given
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.workspace, workspace)
|
||||
XCTAssertEqual(graph.projects, [
|
||||
"/A": projectA,
|
||||
])
|
||||
XCTAssertTrue(graph.targets.isEmpty)
|
||||
XCTAssertTrue(graph.dependencies.isEmpty)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_unlinkedReferencedProjectsAreIncluded() throws {
|
||||
// Given
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.workspace, workspace)
|
||||
XCTAssertEqual(graph.projects, [
|
||||
"/A": projectA,
|
||||
"/B": projectB,
|
||||
])
|
||||
XCTAssertTrue(graph.targets.isEmpty)
|
||||
XCTAssertTrue(graph.dependencies.isEmpty)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_linkedReferencedProjectsAreIncluded() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "B", path: "/B")])
|
||||
let targetB = Target.test(name: "B", dependencies: [])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.workspace, workspace.replacing(projects: ["/A", "/B"]))
|
||||
XCTAssertEqual(graph.projects, [
|
||||
"/A": projectA,
|
||||
"/B": projectB,
|
||||
])
|
||||
XCTAssertEqual(graph.targets, [
|
||||
"/A": ["A": targetA],
|
||||
"/B": ["B": targetB],
|
||||
])
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.target(name: "B", path: "/B"),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Load Project
|
||||
|
||||
func test_loadProject_unlinkedProjectsAreExcluded() throws {
|
||||
// Given
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let (loadedProject, graph) = try subject.loadProject(at: "/A", projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(loadedProject, projectA)
|
||||
XCTAssertEqual(graph.projects, [
|
||||
"/A": projectA,
|
||||
])
|
||||
XCTAssertTrue(graph.targets.isEmpty)
|
||||
XCTAssertTrue(graph.dependencies.isEmpty)
|
||||
}
|
||||
|
||||
func test_loadProject_linkedProjectsAreIncluded() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "B", path: "/B")])
|
||||
let targetB = Target.test(name: "B", dependencies: [])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let (loadedProject, graph) = try subject.loadProject(at: "/A", projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(loadedProject, projectA)
|
||||
XCTAssertEqual(graph.projects, [
|
||||
"/A": projectA,
|
||||
"/B": projectB,
|
||||
])
|
||||
XCTAssertEqual(graph.targets, [
|
||||
"/A": ["A": targetA],
|
||||
"/B": ["B": targetB],
|
||||
])
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.target(name: "B", path: "/B"),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Frameworks
|
||||
|
||||
func test_loadWorkspace_frameworkDependency() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.framework(path: "/Frameworks/F1.framework")])
|
||||
let targetB = Target.test(name: "B", dependencies: [.framework(path: "/Frameworks/F2.framework")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B"])
|
||||
|
||||
stubFramework(
|
||||
metadata: .init(
|
||||
path: "/Frameworks/F1.framework",
|
||||
linkage: .dynamic,
|
||||
architectures: [.arm64]
|
||||
)
|
||||
)
|
||||
stubFramework(
|
||||
metadata: .init(
|
||||
path: "/Frameworks/F2.framework",
|
||||
linkage: .static,
|
||||
architectures: [.x8664]
|
||||
)
|
||||
)
|
||||
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.framework(
|
||||
path: "/Frameworks/F1.framework",
|
||||
binaryPath: "/Frameworks/F1.framework/F1",
|
||||
dsymPath: nil,
|
||||
bcsymbolmapPaths: [],
|
||||
linking: .dynamic,
|
||||
architectures: [.arm64],
|
||||
isCarthage: false
|
||||
),
|
||||
]),
|
||||
.target(name: "B", path: "/B"): Set([
|
||||
.framework(
|
||||
path: "/Frameworks/F2.framework",
|
||||
binaryPath: "/Frameworks/F2.framework/F2",
|
||||
dsymPath: nil,
|
||||
bcsymbolmapPaths: [],
|
||||
linking: .static,
|
||||
architectures: [.x8664],
|
||||
isCarthage: false
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
func test_loadWorkspace_frameworkDependencyReferencedMultipleTimes() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.framework(path: "/Frameworks/F.framework")])
|
||||
let targetB = Target.test(name: "B", dependencies: [.framework(path: "/Frameworks/F.framework")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B"])
|
||||
|
||||
stubFramework(
|
||||
metadata: .init(
|
||||
path: "/Frameworks/F.framework",
|
||||
linkage: .dynamic,
|
||||
architectures: [.arm64]
|
||||
)
|
||||
)
|
||||
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
let frameworkDependency: ValueGraphDependency = .framework(
|
||||
path: "/Frameworks/F.framework",
|
||||
binaryPath: "/Frameworks/F.framework/F",
|
||||
dsymPath: nil,
|
||||
bcsymbolmapPaths: [],
|
||||
linking: .dynamic,
|
||||
architectures: [.arm64],
|
||||
isCarthage: false
|
||||
)
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
frameworkDependency,
|
||||
]),
|
||||
.target(name: "B", path: "/B"): Set([
|
||||
frameworkDependency,
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Libraries
|
||||
|
||||
func test_loadWorkspace_libraryDependency() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [
|
||||
.library(path: "/libs/lib1/libL1.dylib", publicHeaders: "/libs/lib1/include", swiftModuleMap: nil),
|
||||
])
|
||||
let targetB = Target.test(name: "B", dependencies: [
|
||||
.library(
|
||||
path: "/libs/lib2/libL2.a",
|
||||
publicHeaders: "/libs/lib2/include",
|
||||
swiftModuleMap: "/libs/lib2.swiftmodule"
|
||||
),
|
||||
])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B"])
|
||||
|
||||
stubLibrary(
|
||||
metadata: .init(
|
||||
path: "/libs/lib1/libL1.dylib",
|
||||
linkage: .dynamic,
|
||||
architectures: [.arm64]
|
||||
)
|
||||
)
|
||||
stubLibrary(
|
||||
metadata: .init(
|
||||
path: "/libs/lib2/libL2.a",
|
||||
linkage: .static,
|
||||
architectures: [.x8664]
|
||||
)
|
||||
)
|
||||
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.library(
|
||||
path: "/libs/lib1/libL1.dylib",
|
||||
publicHeaders: "/libs/lib1/include",
|
||||
linking: .dynamic,
|
||||
architectures: [.arm64],
|
||||
swiftModuleMap: nil
|
||||
),
|
||||
]),
|
||||
.target(name: "B", path: "/B"): Set([
|
||||
.library(
|
||||
path: "/libs/lib2/libL2.a",
|
||||
publicHeaders: "/libs/lib2/include",
|
||||
linking: .static,
|
||||
architectures: [.x8664],
|
||||
swiftModuleMap: "/libs/lib2.swiftmodule"
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - XCFrameworks
|
||||
|
||||
func test_loadWorkspace_xcframeworkDependency() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.xcFramework(path: "/XCFrameworks/XF1.xcframework")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
|
||||
stubXCFramework(
|
||||
metadata: .init(
|
||||
path: "/XCFrameworks/XF1.xcframework",
|
||||
infoPlist: .test(),
|
||||
primaryBinaryPath: "/XCFrameworks/XF1.xcframework/ios-arm64/XF1",
|
||||
linking: .dynamic
|
||||
)
|
||||
)
|
||||
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.xcframework(
|
||||
path: "/XCFrameworks/XF1.xcframework",
|
||||
infoPlist: .test(),
|
||||
primaryBinaryPath: "/XCFrameworks/XF1.xcframework/ios-arm64/XF1",
|
||||
linking: .dynamic
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - SDKs
|
||||
|
||||
func test_loadWorkspace_sdkDependency() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.sdk(name: "libc++.tbd", status: .required)])
|
||||
let targetB = Target.test(name: "B", dependencies: [.sdk(name: "SwiftUI.framework", status: .optional)])
|
||||
let targetC = Target.test(name: "C", dependencies: [.xctest])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA, targetB, targetC])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.sdk(
|
||||
name: "libc++.tbd",
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libc++.tbd",
|
||||
status: .required,
|
||||
source: .system
|
||||
),
|
||||
]),
|
||||
.target(name: "B", path: "/A"): Set([
|
||||
.sdk(
|
||||
name: "SwiftUI.framework",
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework",
|
||||
status: .optional,
|
||||
source: .system
|
||||
),
|
||||
]),
|
||||
.target(name: "C", path: "/A"): Set([
|
||||
.sdk(
|
||||
name: "XCTest.framework",
|
||||
path: "/Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework",
|
||||
status: .required,
|
||||
source: .developer
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Packages
|
||||
|
||||
func test_loadWorkspace_packages() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [
|
||||
.package(product: "PackageLibraryA1"),
|
||||
])
|
||||
let targetB = Target.test(name: "B", dependencies: [
|
||||
.package(product: "PackageLibraryA2"),
|
||||
])
|
||||
let targetC = Target.test(name: "C", dependencies: [
|
||||
.package(product: "PackageLibraryB"),
|
||||
])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA], packages: [
|
||||
.local(path: "/Packages/PackageLibraryA"),
|
||||
])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB], packages: [
|
||||
.local(path: "/Packages/PackageLibraryA"),
|
||||
])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC], packages: [
|
||||
.remote(url: "https://example.com/package-library-b", requirement: .branch("testing")),
|
||||
])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B", "/C"])
|
||||
|
||||
let subject = makeSubject()
|
||||
|
||||
// When
|
||||
let graph = try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
])
|
||||
|
||||
// Then
|
||||
|
||||
// Note: the following is a reflection of the current implementation
|
||||
// which has a few limitation / bugs when it comes to identifying the same
|
||||
// package referenced by multiple projects/
|
||||
XCTAssertEqual(graph.packages, [
|
||||
"/A": ["/Packages/PackageLibraryA": .local(path: "/Packages/PackageLibraryA")],
|
||||
"/B": ["/Packages/PackageLibraryA": .local(path: "/Packages/PackageLibraryA")],
|
||||
"/C": ["https://example.com/package-library-b": .remote(url: "https://example.com/package-library-b", requirement: .branch("testing"))],
|
||||
])
|
||||
XCTAssertEqual(graph.dependencies, [
|
||||
.target(name: "A", path: "/A"): Set([
|
||||
.packageProduct(path: "/A", product: "PackageLibraryA1"),
|
||||
]),
|
||||
.target(name: "B", path: "/B"): Set([
|
||||
.packageProduct(path: "/B", product: "PackageLibraryA2"),
|
||||
]),
|
||||
.target(name: "C", path: "/C"): Set([
|
||||
.packageProduct(path: "/C", product: "PackageLibraryB"),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Dependency Cycle
|
||||
|
||||
func test_loadProject_localDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.target(name: "B")])
|
||||
let targetB = Target.test(name: "B", dependencies: [.target(name: "C")])
|
||||
let targetC = Target.test(name: "C", dependencies: [.target(name: "A")])
|
||||
let project = Project.test(path: "/A", name: "A", targets: [targetA, targetB, targetC])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadProject(at: "/A", projects: [
|
||||
project,
|
||||
]),
|
||||
GraphLoadingError.circularDependency([
|
||||
.init(path: "/A", name: "A"),
|
||||
.init(path: "/A", name: "B"),
|
||||
.init(path: "/A", name: "C"),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadProject_differentProjectDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "B", path: "/B")])
|
||||
let targetB = Target.test(name: "B", dependencies: [.project(target: "C", path: "/C")])
|
||||
let targetC = Target.test(name: "C", dependencies: [.project(target: "A", path: "/A")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadProject(at: "/A", projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
]),
|
||||
GraphLoadingError.circularDependency([
|
||||
.init(path: "/A", name: "A"),
|
||||
.init(path: "/B", name: "B"),
|
||||
.init(path: "/C", name: "C"),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_differentProjectDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "B", path: "/B")])
|
||||
let targetB = Target.test(name: "B", dependencies: [.project(target: "C", path: "/C")])
|
||||
let targetC = Target.test(name: "C", dependencies: [.project(target: "A", path: "/A")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B", "/C"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
]),
|
||||
GraphLoadingError.circularDependency([
|
||||
.init(path: "/A", name: "A"),
|
||||
.init(path: "/B", name: "B"),
|
||||
.init(path: "/C", name: "C"),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadProject_crossProjectsReferenceWithNoDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA1 = Target.test(name: "A1", dependencies: [.project(target: "B1", path: "/B")])
|
||||
let targetA2 = Target.test(name: "A2", dependencies: [.project(target: "B2", path: "/B")])
|
||||
let targetB1 = Target.test(name: "B1", dependencies: [.project(target: "C", path: "/C")])
|
||||
let targetB2 = Target.test(name: "B2", dependencies: [])
|
||||
let targetC = Target.test(name: "C", dependencies: [.project(target: "A2", path: "/A")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA1, targetA2])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB1, targetB2])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertNoThrow(
|
||||
try subject.loadProject(at: "/A", projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_crossProjectsReferenceWithNoDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA1 = Target.test(name: "A1", dependencies: [.project(target: "B1", path: "/B")])
|
||||
let targetA2 = Target.test(name: "A2", dependencies: [.project(target: "B2", path: "/B")])
|
||||
let targetB1 = Target.test(name: "B1", dependencies: [.project(target: "C", path: "/C")])
|
||||
let targetB2 = Target.test(name: "B2", dependencies: [])
|
||||
let targetC = Target.test(name: "C", dependencies: [.project(target: "A2", path: "/A")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA1, targetA2])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB1, targetB2])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B", "/C"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertNoThrow(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_crossProjectsReferenceWithDependencyCycle() throws {
|
||||
// Given
|
||||
let targetA1 = Target.test(name: "A1", dependencies: [.project(target: "B1", path: "/B")])
|
||||
let targetA2 = Target.test(name: "A2", dependencies: [.project(target: "B2", path: "/B")])
|
||||
let targetB1 = Target.test(name: "B1", dependencies: [.project(target: "C1", path: "/C")])
|
||||
let targetB2 = Target.test(name: "B2", dependencies: [.project(target: "C2", path: "/C")])
|
||||
let targetC1 = Target.test(name: "C1", dependencies: [.project(target: "A2", path: "/A")])
|
||||
let targetC2 = Target.test(name: "C2", dependencies: [.project(target: "B1", path: "/B")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA1, targetA2])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [targetB1, targetB2])
|
||||
let projectC = Project.test(path: "/C", name: "C", targets: [targetC1, targetC2])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B", "/C"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsError(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
projectC,
|
||||
])
|
||||
) { error in
|
||||
// need to manually inspect the error as depending on traversal order may result in different nodes getting listed
|
||||
let graphError = error as? GraphLoadingError
|
||||
XCTAssertNotNil(graphError)
|
||||
XCTAssertTrue(graphError?.isCycleError == true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error Cases
|
||||
|
||||
func test_loadWorkspace_missingProjectReferenceInWorkspace() throws {
|
||||
// Given
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/Missing"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
]),
|
||||
GraphLoadingError.missingProject("/Missing")
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_missingProjectReferenceInDependency() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "Missing", path: "/Missing")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
]),
|
||||
GraphLoadingError.missingProject("/Missing")
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_missingTargetReferenceInLocalProject() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.target(name: "Missing")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
]),
|
||||
GraphLoadingError.targetNotFound("Missing", "/A")
|
||||
)
|
||||
}
|
||||
|
||||
func test_loadWorkspace_missingTargetReferenceInOtherProject() throws {
|
||||
// Given
|
||||
let targetA = Target.test(name: "A", dependencies: [.project(target: "Missing", path: "/B")])
|
||||
let projectA = Project.test(path: "/A", name: "A", targets: [targetA])
|
||||
let projectB = Project.test(path: "/B", name: "B", targets: [])
|
||||
let workspace = Workspace.test(path: "/", name: "Workspace", projects: ["/A", "/B"])
|
||||
let subject = makeSubject()
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(
|
||||
try subject.loadWorkspace(workspace: workspace, projects: [
|
||||
projectA,
|
||||
projectB,
|
||||
]),
|
||||
GraphLoadingError.targetNotFound("Missing", "/B")
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func makeSubject() -> ValueGraphLoader {
|
||||
ValueGraphLoader(
|
||||
frameworkMetadataProvider: frameworkMetadataProvider,
|
||||
libraryMetadataProvider: libraryMetadataProvider,
|
||||
xcframeworkMetadataProvider: xcframeworkMetadataProvider,
|
||||
systemFrameworkMetadataProvider: SystemFrameworkMetadataProvider()
|
||||
)
|
||||
}
|
||||
|
||||
private func makeFrameworkMetadataProvider() -> MockFrameworkMetadataProvider {
|
||||
let provider = MockFrameworkMetadataProvider()
|
||||
provider.loadMetadataStub = { [weak self] path in
|
||||
guard let metadata = self?.stubbedFrameworks[path] else {
|
||||
throw FrameworkMetadataProviderError.frameworkNotFound(path)
|
||||
}
|
||||
return FrameworkMetadata(
|
||||
path: path,
|
||||
binaryPath: path.appending(component: path.basenameWithoutExt),
|
||||
dsymPath: nil,
|
||||
bcsymbolmapPaths: [],
|
||||
linking: metadata.linkage,
|
||||
architectures: metadata.architectures,
|
||||
isCarthage: false
|
||||
)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
private func makeLibraryMetadataProvider() -> MockLibraryMetadataProvider {
|
||||
let provider = MockLibraryMetadataProvider()
|
||||
provider.loadMetadataStub = { [weak self] path, publicHeaders, swiftModuleMap in
|
||||
guard let metadata = self?.stubbedLibraries[path] else {
|
||||
throw LibraryMetadataProviderError.libraryNotFound(path)
|
||||
}
|
||||
return LibraryMetadata(
|
||||
path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap,
|
||||
architectures: metadata.architectures,
|
||||
linking: metadata.linkage
|
||||
)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
private func makeXCFrameworkMetadataProvider() -> MockXCFrameworkMetadataProvider {
|
||||
let provider = MockXCFrameworkMetadataProvider()
|
||||
provider.loadMetadataStub = { [weak self] path in
|
||||
guard let metadata = self?.stubbedXCFrameworks[path] else {
|
||||
throw XCFrameworkMetadataProviderError.xcframeworkNotFound(path)
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
private func stubFramework(metadata: PrecompiledMetadata) {
|
||||
stubbedFrameworks[metadata.path] = metadata
|
||||
}
|
||||
|
||||
private func stubLibrary(metadata: PrecompiledMetadata) {
|
||||
stubbedLibraries[metadata.path] = metadata
|
||||
}
|
||||
|
||||
private func stubXCFramework(metadata: XCFrameworkMetadata) {
|
||||
stubbedXCFrameworks[metadata.path] = metadata
|
||||
}
|
||||
|
||||
// MARK: - Helper types
|
||||
|
||||
private struct PrecompiledMetadata {
|
||||
var path: AbsolutePath
|
||||
var linkage: BinaryLinking
|
||||
var architectures: [BinaryArchitecture]
|
||||
}
|
||||
}
|
||||
|
||||
private extension GraphLoadingError {
|
||||
var isCycleError: Bool {
|
||||
switch self {
|
||||
case .circularDependency:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue