Merge remote-tracking branch 'origin/master' into scaffold_init
This commit is contained in:
commit
32ec0e6b88
|
@ -7,6 +7,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
### Changed
|
||||
- Optimize `TargetNode`'s set operations https://github.com/tuist/tuist/pull/1095 by @kwridan
|
||||
- Optimize `BuildPhaseGenerator`'s method of detecting assets and localized files https://github.com/tuist/tuist/pull/1094 by @kwridan
|
||||
- Concurrent project generation https://github.com/tuist/tuist/pull/1096 by @kwridan
|
||||
|
||||
## 1.4.0
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ let package = Package(
|
|||
),
|
||||
.testTarget(
|
||||
name: "TuistIntegrationTests",
|
||||
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistSupport"]
|
||||
dependencies: ["TuistGenerator", "TuistSupportTesting", "TuistSupport", "TuistCoreTesting"]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -292,7 +292,7 @@ public class Graph: Graphing {
|
|||
if targetNode.target.canLinkStaticProducts() {
|
||||
let transitiveSystemLibraries = transitiveStaticTargetNodes(for: targetNode).flatMap {
|
||||
$0.sdkDependencies.map {
|
||||
GraphDependencyReference.sdk($0.path, $0.status)
|
||||
GraphDependencyReference.sdk(path: $0.path, status: $0.status)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ public class Graph: Graphing {
|
|||
}
|
||||
|
||||
let directSystemLibrariesAndFrameworks = targetNode.sdkDependencies.map {
|
||||
GraphDependencyReference.sdk($0.path, $0.status)
|
||||
GraphDependencyReference.sdk(path: $0.path, status: $0.status)
|
||||
}
|
||||
|
||||
references = references.union(directSystemLibrariesAndFrameworks)
|
||||
|
@ -309,8 +309,7 @@ public class Graph: Graphing {
|
|||
|
||||
let precompiledLibrariesAndFrameworks = targetNode.precompiledDependencies
|
||||
.lazy
|
||||
.map(\.path)
|
||||
.map(GraphDependencyReference.absolute)
|
||||
.map(GraphDependencyReference.init)
|
||||
|
||||
references = references.union(precompiledLibrariesAndFrameworks)
|
||||
|
||||
|
@ -382,13 +381,16 @@ public class Graph: Graphing {
|
|||
|
||||
var references: Set<GraphDependencyReference> = Set([])
|
||||
|
||||
let isDynamicAndLinkable = frameworkUsesDynamicLinking()
|
||||
let isDynamicAndLinkable = { (node: PrecompiledNode) -> Bool in
|
||||
if let framework = node as? FrameworkNode { return framework.linking == .dynamic }
|
||||
if let xcframework = node as? XCFrameworkNode { return xcframework.linking == .dynamic }
|
||||
return false
|
||||
}
|
||||
|
||||
/// Precompiled frameworks
|
||||
let precompiledFrameworks = findAll(targetNode: targetNode, test: isDynamicAndLinkable, skip: canEmbedProducts)
|
||||
.lazy
|
||||
.map(\.path)
|
||||
.map(GraphDependencyReference.absolute)
|
||||
.map(GraphDependencyReference.init)
|
||||
|
||||
references.formUnion(precompiledFrameworks)
|
||||
|
||||
|
@ -573,11 +575,4 @@ public class Graph: Graphing {
|
|||
|
||||
return validProducts.contains(targetNode.target.product)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
fileprivate func frameworkUsesDynamicLinking(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) -> (_ frameworkNode: PrecompiledNode) -> Bool { { frameworkNode in
|
||||
let isDynamicLink = try? frameworkMetadataProvider.linking(precompiled: frameworkNode) == .dynamic
|
||||
return isDynamicLink ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,66 @@ import Basic
|
|||
import Foundation
|
||||
|
||||
public enum GraphDependencyReference: Equatable, Comparable, Hashable {
|
||||
case absolute(AbsolutePath)
|
||||
case xcframework(
|
||||
path: AbsolutePath,
|
||||
infoPlist: XCFrameworkInfoPlist,
|
||||
primaryBinaryPath: AbsolutePath,
|
||||
binaryPath: AbsolutePath
|
||||
)
|
||||
case library(
|
||||
path: AbsolutePath,
|
||||
binaryPath: AbsolutePath,
|
||||
linking: BinaryLinking,
|
||||
architectures: [BinaryArchitecture],
|
||||
product: Product
|
||||
)
|
||||
case framework(
|
||||
path: AbsolutePath,
|
||||
binaryPath: AbsolutePath,
|
||||
isCarthage: Bool,
|
||||
dsymPath: AbsolutePath?,
|
||||
bcsymbolmapPaths: [AbsolutePath],
|
||||
linking: BinaryLinking,
|
||||
architectures: [BinaryArchitecture],
|
||||
product: Product
|
||||
)
|
||||
case product(target: String, productName: String)
|
||||
case sdk(AbsolutePath, SDKStatus)
|
||||
case sdk(path: AbsolutePath, status: SDKStatus)
|
||||
|
||||
init(precompiledNode: PrecompiledNode) {
|
||||
if let frameworkNode = precompiledNode as? FrameworkNode {
|
||||
self = .framework(path: frameworkNode.path,
|
||||
binaryPath: frameworkNode.binaryPath,
|
||||
isCarthage: frameworkNode.isCarthage,
|
||||
dsymPath: frameworkNode.dsymPath,
|
||||
bcsymbolmapPaths: frameworkNode.bcsymbolmapPaths,
|
||||
linking: frameworkNode.linking,
|
||||
architectures: frameworkNode.architectures,
|
||||
product: frameworkNode.product)
|
||||
} else if let libraryNode = precompiledNode as? LibraryNode {
|
||||
self = .library(path: libraryNode.path,
|
||||
binaryPath: libraryNode.binaryPath,
|
||||
linking: libraryNode.linking,
|
||||
architectures: libraryNode.architectures,
|
||||
product: libraryNode.product)
|
||||
} else if let xcframeworkNode = precompiledNode as? XCFrameworkNode {
|
||||
self = .xcframework(path: xcframeworkNode.path,
|
||||
infoPlist: xcframeworkNode.infoPlist,
|
||||
primaryBinaryPath: xcframeworkNode.primaryBinaryPath,
|
||||
binaryPath: xcframeworkNode.binaryPath)
|
||||
} else {
|
||||
preconditionFailure("unsupported precompiled node")
|
||||
}
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case let .absolute(path):
|
||||
hasher.combine(path)
|
||||
case let .library(metadata):
|
||||
hasher.combine(metadata.0)
|
||||
case let .framework(metadata):
|
||||
hasher.combine(metadata.0)
|
||||
case let .xcframework(metadata):
|
||||
hasher.combine(metadata.0)
|
||||
case let .product(target, productName):
|
||||
hasher.combine(target)
|
||||
hasher.combine(productName)
|
||||
|
@ -19,23 +71,30 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: GraphDependencyReference, rhs: GraphDependencyReference) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.absolute(lhsPath), .absolute(rhsPath)):
|
||||
return lhsPath == rhsPath
|
||||
case let (.product(lhsTarget, lhsProductName), .product(rhsTarget, rhsProductName)):
|
||||
return lhsTarget == rhsTarget && lhsProductName == rhsProductName
|
||||
case let (.sdk(lhsPath, lhsStatus), .sdk(rhsPath, rhsStatus)):
|
||||
return lhsPath == rhsPath && lhsStatus == rhsStatus
|
||||
/// For dependencies that exists in the file system. This attribute returns the path to them.
|
||||
public var path: AbsolutePath? {
|
||||
switch self {
|
||||
case let .framework(metadata):
|
||||
return metadata.path
|
||||
case let .library(metadata):
|
||||
return metadata.path
|
||||
case let .xcframework(metadata):
|
||||
return metadata.path
|
||||
case let .sdk(metadata):
|
||||
return metadata.path
|
||||
default:
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public static func < (lhs: GraphDependencyReference, rhs: GraphDependencyReference) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.absolute(lhsPath), .absolute(rhsPath)):
|
||||
return lhsPath < rhsPath
|
||||
case let (.framework(lhsMetadata), .framework(rhsMetadata)):
|
||||
return lhsMetadata.path < rhsMetadata.path
|
||||
case let (.xcframework(lhsMetadata), .xcframework(rhsMetadata)):
|
||||
return lhsMetadata.path < rhsMetadata.path
|
||||
case let (.library(lhsMetadata), .library(rhsMetadata)):
|
||||
return lhsMetadata.path < rhsMetadata.path
|
||||
case let (.product(lhsTarget, lhsProductName), .product(rhsTarget, rhsProductName)):
|
||||
if lhsTarget == rhsTarget {
|
||||
return lhsProductName < rhsProductName
|
||||
|
@ -43,11 +102,25 @@ public enum GraphDependencyReference: Equatable, Comparable, Hashable {
|
|||
return lhsTarget < rhsTarget
|
||||
case let (.sdk(lhsPath, _), .sdk(rhsPath, _)):
|
||||
return lhsPath < rhsPath
|
||||
case (.sdk, .absolute):
|
||||
case (.sdk, .framework):
|
||||
return true
|
||||
case (.sdk, .xcframework):
|
||||
return true
|
||||
case (.sdk, .product):
|
||||
return true
|
||||
case (.product, .absolute):
|
||||
case (.sdk, .library):
|
||||
return true
|
||||
case (.product, .framework):
|
||||
return true
|
||||
case (.product, .xcframework):
|
||||
return true
|
||||
case (.product, .library):
|
||||
return true
|
||||
case (.library, .framework):
|
||||
return true
|
||||
case (.library, .xcframework):
|
||||
return true
|
||||
case (.framework, .xcframework):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
@ -24,10 +24,32 @@ public class GraphLoader: GraphLoading {
|
|||
|
||||
fileprivate let modelLoader: GeneratorModelLoading
|
||||
|
||||
/// Utility to load framework nodes by parsing their information from disk.
|
||||
fileprivate let frameworkNodeLoader: FrameworkNodeLoading
|
||||
|
||||
/// Utility to load xcframework nodes by parsing their information from disk.
|
||||
fileprivate let xcframeworkNodeLoader: XCFrameworkNodeLoading
|
||||
|
||||
/// Utility to load library nodes by parsing their information from disk.
|
||||
fileprivate let libraryNodeLoader: LibraryNodeLoading
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(modelLoader: GeneratorModelLoading) {
|
||||
public convenience init(modelLoader: GeneratorModelLoading) {
|
||||
self.init(modelLoader: modelLoader,
|
||||
frameworkNodeLoader: FrameworkNodeLoader(),
|
||||
xcframeworkNodeLoader: XCFrameworkNodeLoader(),
|
||||
libraryNodeLoader: LibraryNodeLoader())
|
||||
}
|
||||
|
||||
public init(modelLoader: GeneratorModelLoading,
|
||||
frameworkNodeLoader: FrameworkNodeLoading,
|
||||
xcframeworkNodeLoader: XCFrameworkNodeLoading,
|
||||
libraryNodeLoader: LibraryNodeLoading) {
|
||||
self.modelLoader = modelLoader
|
||||
self.frameworkNodeLoader = frameworkNodeLoader
|
||||
self.xcframeworkNodeLoader = xcframeworkNodeLoader
|
||||
self.libraryNodeLoader = libraryNodeLoader
|
||||
}
|
||||
|
||||
// MARK: - GraphLoading
|
||||
|
@ -216,7 +238,7 @@ public class GraphLoader: GraphLoading {
|
|||
/// - graphLoaderCache: Graph loader cache.
|
||||
fileprivate func loadFrameworkNode(frameworkPath: AbsolutePath, graphLoaderCache: GraphLoaderCaching) throws -> FrameworkNode {
|
||||
if let frameworkNode = graphLoaderCache.precompiledNode(frameworkPath) as? FrameworkNode { return frameworkNode }
|
||||
let framewokNode = FrameworkNode(path: frameworkPath)
|
||||
let framewokNode = try frameworkNodeLoader.load(path: frameworkPath)
|
||||
graphLoaderCache.add(precompiledNode: framewokNode)
|
||||
return framewokNode
|
||||
}
|
||||
|
@ -231,26 +253,11 @@ public class GraphLoader: GraphLoading {
|
|||
swiftModuleMap: AbsolutePath?,
|
||||
libraryPath: AbsolutePath,
|
||||
graphLoaderCache: GraphLoaderCaching) throws -> LibraryNode {
|
||||
// TODO: Validate using linters
|
||||
if !FileHandler.shared.exists(libraryPath) {
|
||||
throw GraphLoadingError.missingFile(libraryPath)
|
||||
}
|
||||
if let libraryNode = graphLoaderCache.precompiledNode(libraryPath) as? LibraryNode { return libraryNode }
|
||||
let libraryNode = try libraryNodeLoader.load(path: libraryPath,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap)
|
||||
|
||||
// TODO: Validate using linters
|
||||
if !FileHandler.shared.exists(publicHeaders) {
|
||||
throw GraphLoadingError.missingFile(publicHeaders)
|
||||
}
|
||||
|
||||
// TODO: Validate using linters
|
||||
if let swiftModuleMap = swiftModuleMap {
|
||||
if !FileHandler.shared.exists(swiftModuleMap) {
|
||||
throw GraphLoadingError.missingFile(swiftModuleMap)
|
||||
}
|
||||
}
|
||||
let libraryNode = LibraryNode(path: libraryPath,
|
||||
publicHeaders: publicHeaders,
|
||||
swiftModuleMap: swiftModuleMap)
|
||||
graphLoaderCache.add(precompiledNode: libraryNode)
|
||||
return libraryNode
|
||||
}
|
||||
|
@ -276,10 +283,12 @@ public class GraphLoader: GraphLoading {
|
|||
/// - Parameters:
|
||||
/// - xcframeworkPath: Path to the .xcframework.
|
||||
/// - graphLoaderCache: Graph loader cache.
|
||||
fileprivate func loadXCFrameworkNode(
|
||||
path: AbsolutePath,
|
||||
graphLoaderCache: GraphLoaderCaching
|
||||
) throws -> XCFrameworkNode {
|
||||
try XCFrameworkParser.parse(path: path, cache: graphLoaderCache)
|
||||
fileprivate func loadXCFrameworkNode(path: AbsolutePath, graphLoaderCache: GraphLoaderCaching) throws -> XCFrameworkNode {
|
||||
if let cachedXCFramework = graphLoaderCache.precompiledNode(path) as? XCFrameworkNode {
|
||||
return cachedXCFramework
|
||||
}
|
||||
let xcframework = try xcframeworkNodeLoader.load(path: path)
|
||||
graphLoaderCache.add(precompiledNode: xcframework)
|
||||
return xcframework
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,31 @@ import Foundation
|
|||
import TuistSupport
|
||||
|
||||
public class FrameworkNode: PrecompiledNode {
|
||||
/// Path to the associated .dSYM
|
||||
public let dsymPath: AbsolutePath?
|
||||
|
||||
/// Paths to the bcsymbolmap files.
|
||||
public let bcsymbolmapPaths: [AbsolutePath]
|
||||
|
||||
/// Returns the type of linking
|
||||
public let linking: BinaryLinking
|
||||
|
||||
/// The architectures supported by the binary.
|
||||
public let architectures: [BinaryArchitecture]
|
||||
|
||||
/// Framework dependencies.
|
||||
public let dependencies: [FrameworkNode]
|
||||
|
||||
/// Returns the type of product.
|
||||
public var product: Product {
|
||||
if linking == .static {
|
||||
return .staticFramework
|
||||
} else {
|
||||
return .framework
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if it's a Carthage framework.
|
||||
public var isCarthage: Bool {
|
||||
path.pathString.contains("Carthage/Build")
|
||||
}
|
||||
|
@ -12,15 +37,26 @@ public class FrameworkNode: PrecompiledNode {
|
|||
FrameworkNode.binaryPath(frameworkPath: path)
|
||||
}
|
||||
|
||||
init(path: AbsolutePath,
|
||||
dsymPath: AbsolutePath?,
|
||||
bcsymbolmapPaths: [AbsolutePath],
|
||||
linking: BinaryLinking,
|
||||
architectures: [BinaryArchitecture] = [],
|
||||
dependencies: [FrameworkNode] = []) {
|
||||
self.dsymPath = dsymPath
|
||||
self.bcsymbolmapPaths = bcsymbolmapPaths
|
||||
self.linking = linking
|
||||
self.architectures = architectures
|
||||
self.dependencies = dependencies
|
||||
super.init(path: path)
|
||||
}
|
||||
|
||||
public override func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
let metadataProvider = FrameworkMetadataProvider()
|
||||
|
||||
try container.encode(path.pathString, forKey: .path)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(try metadataProvider.product(framework: self), forKey: .product)
|
||||
let archs = try metadataProvider.architectures(precompiled: self)
|
||||
try container.encode(archs.map(\.rawValue), forKey: .architectures)
|
||||
try container.encode(product, forKey: .product)
|
||||
try container.encode(architectures.map(\.rawValue), forKey: .architectures)
|
||||
try container.encode("precompiled", forKey: .type)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,38 @@ import TuistSupport
|
|||
public class LibraryNode: PrecompiledNode {
|
||||
// MARK: - Attributes
|
||||
|
||||
/// Directory that contains the public headers of the library.
|
||||
let publicHeaders: AbsolutePath
|
||||
|
||||
/// Path to the Swift module map file.
|
||||
let swiftModuleMap: AbsolutePath?
|
||||
|
||||
/// List of supported architectures.
|
||||
let architectures: [BinaryArchitecture]
|
||||
|
||||
/// Type of linking supported by the binary.
|
||||
let linking: BinaryLinking
|
||||
|
||||
/// Library product.
|
||||
var product: Product {
|
||||
if linking == .static {
|
||||
return .staticLibrary
|
||||
} else {
|
||||
return .dynamicLibrary
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
architectures: [BinaryArchitecture],
|
||||
linking: BinaryLinking,
|
||||
swiftModuleMap: AbsolutePath? = nil) {
|
||||
self.publicHeaders = publicHeaders
|
||||
self.swiftModuleMap = swiftModuleMap
|
||||
self.architectures = architectures
|
||||
self.linking = linking
|
||||
super.init(path: path)
|
||||
}
|
||||
|
||||
|
@ -22,6 +44,8 @@ public class LibraryNode: PrecompiledNode {
|
|||
super.hash(into: &hasher)
|
||||
hasher.combine(publicHeaders)
|
||||
hasher.combine(swiftModuleMap)
|
||||
hasher.combine(architectures)
|
||||
hasher.combine(linking)
|
||||
}
|
||||
|
||||
static func == (lhs: LibraryNode, rhs: LibraryNode) -> Bool {
|
||||
|
@ -35,6 +59,8 @@ public class LibraryNode: PrecompiledNode {
|
|||
return path == otherLibraryNode.path
|
||||
&& swiftModuleMap == otherLibraryNode.swiftModuleMap
|
||||
&& publicHeaders == otherLibraryNode.publicHeaders
|
||||
&& architectures == otherLibraryNode.architectures
|
||||
&& linking == otherLibraryNode.linking
|
||||
}
|
||||
|
||||
public override var binaryPath: AbsolutePath {
|
||||
|
@ -43,13 +69,10 @@ public class LibraryNode: PrecompiledNode {
|
|||
|
||||
public override func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
let metadataProvider = LibraryMetadataProvider()
|
||||
|
||||
try container.encode(path.pathString, forKey: .path)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(try metadataProvider.product(library: self), forKey: .product)
|
||||
let archs = try metadataProvider.architectures(precompiled: self)
|
||||
try container.encode(archs.map(\.rawValue), forKey: .architectures)
|
||||
try container.encode(product, forKey: .product)
|
||||
try container.encode(architectures.map(\.rawValue), forKey: .architectures)
|
||||
try container.encode("precompiled", forKey: .type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,33 +3,55 @@ import Foundation
|
|||
import TuistSupport
|
||||
|
||||
public class XCFrameworkNode: PrecompiledNode {
|
||||
/// Coding keys.
|
||||
enum XCFrameworkNodeCodingKeys: String, CodingKey {
|
||||
case libraries
|
||||
case linking
|
||||
case type
|
||||
case path
|
||||
case name
|
||||
case infoPlist = "info_plist"
|
||||
}
|
||||
|
||||
public let libraries: [XCFrameworkInfoPlist.Library]
|
||||
/// The xcframework's Info.plist content.
|
||||
public let infoPlist: XCFrameworkInfoPlist
|
||||
|
||||
/// Path to the primary binary.
|
||||
public let primaryBinaryPath: AbsolutePath
|
||||
|
||||
public init(
|
||||
path: AbsolutePath,
|
||||
libraries: [XCFrameworkInfoPlist.Library],
|
||||
primaryBinaryPath: AbsolutePath
|
||||
) {
|
||||
self.libraries = libraries
|
||||
/// Returns the type of linking
|
||||
public let linking: BinaryLinking
|
||||
|
||||
/// List of other .xcframeworks this xcframework depends on.
|
||||
public let dependencies: [XCFrameworkNode]
|
||||
|
||||
/// Path to the binary.
|
||||
public override var binaryPath: AbsolutePath { primaryBinaryPath }
|
||||
|
||||
/// Initializes the node with its attributes.
|
||||
/// - Parameters:
|
||||
/// - path: Path to the .xcframework.
|
||||
/// - infoPlist: The xcframework's Info.plist content.
|
||||
/// - primaryBinaryPath: Path to the primary binary.
|
||||
/// - linking: Returns the type of linking.
|
||||
/// - dependencies: List of other .xcframeworks this xcframework depends on.
|
||||
public init(path: AbsolutePath,
|
||||
infoPlist: XCFrameworkInfoPlist,
|
||||
primaryBinaryPath: AbsolutePath,
|
||||
linking: BinaryLinking,
|
||||
dependencies: [XCFrameworkNode] = []) {
|
||||
self.infoPlist = infoPlist
|
||||
self.linking = linking
|
||||
self.primaryBinaryPath = primaryBinaryPath
|
||||
self.dependencies = dependencies
|
||||
super.init(path: path)
|
||||
}
|
||||
|
||||
public override var binaryPath: AbsolutePath {
|
||||
primaryBinaryPath
|
||||
}
|
||||
|
||||
public override func encode(to encoder: Encoder) throws {
|
||||
var parentContainer = encoder.container(keyedBy: CodingKeys.self)
|
||||
try parentContainer.encode(path.pathString, forKey: .path)
|
||||
try parentContainer.encode(name, forKey: .name)
|
||||
try parentContainer.encode("precompiled", forKey: .type)
|
||||
var container = encoder.container(keyedBy: XCFrameworkNodeCodingKeys.self)
|
||||
try container.encode(libraries, forKey: .libraries)
|
||||
try container.encode(path.pathString, forKey: .path)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(linking, forKey: .linking)
|
||||
try container.encode("xcframework", forKey: .type)
|
||||
try container.encode(infoPlist, forKey: .infoPlist)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,48 +2,30 @@ import Basic
|
|||
import Foundation
|
||||
import TuistSupport
|
||||
|
||||
public protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// 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.
|
||||
func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath?
|
||||
|
||||
/// Given a framework, it returns the path to its dSYMs if they exist
|
||||
/// in the same framework directory.
|
||||
/// - Parameter framework: Framework instance.
|
||||
func dsymPath(framework: FrameworkNode) -> AbsolutePath?
|
||||
|
||||
/// Given the path to a framework, it returns the list of .bcsymbolmap files that
|
||||
/// are associated to the framework and that are present in the same directory.
|
||||
/// - Parameter frameworkPath: Path to the .framework directory.
|
||||
func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath]
|
||||
|
||||
/// Given a framework, it returns the list of .bcsymbolmap files that
|
||||
/// are associated to the framework and that are present in the same directory.
|
||||
/// - Parameter framework: Framework instance.
|
||||
func bcsymbolmapPaths(framework: FrameworkNode) throws -> [AbsolutePath]
|
||||
|
||||
/// Returns the product for the framework at the given path.
|
||||
/// - Parameter frameworkPath: Path to the .framework directory.
|
||||
func product(frameworkPath: AbsolutePath) throws -> Product
|
||||
|
||||
/// Returns the product for the given framework.
|
||||
/// - Parameter framework: Framework instance.
|
||||
func product(framework: FrameworkNode) throws -> Product
|
||||
}
|
||||
|
||||
public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
let path = AbsolutePath("\(frameworkPath.pathString).dSYM")
|
||||
if FileHandler.shared.exists(path) { return path }
|
||||
return nil
|
||||
}
|
||||
|
||||
public func dsymPath(framework: FrameworkNode) -> AbsolutePath? {
|
||||
dsymPath(frameworkPath: framework.path)
|
||||
}
|
||||
|
||||
public func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
let uuids = try self.uuids(binaryPath: binaryPath)
|
||||
return uuids
|
||||
|
@ -52,11 +34,7 @@ public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, Frame
|
|||
.sorted()
|
||||
}
|
||||
|
||||
public func bcsymbolmapPaths(framework: FrameworkNode) throws -> [AbsolutePath] {
|
||||
try bcsymbolmapPaths(frameworkPath: framework.path)
|
||||
}
|
||||
|
||||
public func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
switch try linking(binaryPath: binaryPath) {
|
||||
case .dynamic:
|
||||
|
@ -65,8 +43,4 @@ public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, Frame
|
|||
return .staticFramework
|
||||
}
|
||||
}
|
||||
|
||||
public func product(framework: FrameworkNode) throws -> Product {
|
||||
try product(frameworkPath: framework.path)
|
||||
}
|
||||
}
|
|
@ -3,26 +3,18 @@ import Foundation
|
|||
import TuistSupport
|
||||
|
||||
protocol LibraryMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Returns the product for the library at the given path.
|
||||
/// - Parameter libraryPath: Path to the library.
|
||||
func product(libraryPath: AbsolutePath) throws -> Product
|
||||
|
||||
/// Returns the product for the given library.
|
||||
/// - Parameter library: Library instance.
|
||||
func product(library: LibraryNode) throws -> Product
|
||||
}
|
||||
|
||||
final class LibraryMetadataProvider: PrecompiledMetadataProvider, LibraryMetadataProviding {
|
||||
func product(libraryPath: AbsolutePath) throws -> Product {
|
||||
switch try linking(binaryPath: libraryPath) {
|
||||
func product(library: LibraryNode) throws -> Product {
|
||||
switch try linking(binaryPath: library.path) {
|
||||
case .dynamic:
|
||||
return .dynamicLibrary
|
||||
case .static:
|
||||
return .staticLibrary
|
||||
}
|
||||
}
|
||||
|
||||
func product(library: LibraryNode) throws -> Product {
|
||||
try product(libraryPath: library.path)
|
||||
}
|
||||
}
|
|
@ -31,42 +31,25 @@ enum PrecompiledMetadataProviderError: FatalError, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol PrecompiledMetadataProviding {
|
||||
protocol PrecompiledMetadataProviding {
|
||||
/// It returns the supported architectures of the binary at the given path.
|
||||
/// - Parameter binaryPath: Binary path.
|
||||
func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture]
|
||||
|
||||
/// It returns the supported architectures of the precompiled framework or library.
|
||||
/// - Parameter precompiled: Precompiled framework or library.
|
||||
func architectures(precompiled: PrecompiledNode) throws -> [BinaryArchitecture]
|
||||
|
||||
/// Return how other binaries should link the binary at the given path.
|
||||
/// - Parameter binaryPath: Path to the binary.
|
||||
func linking(binaryPath: AbsolutePath) throws -> BinaryLinking
|
||||
|
||||
/// Return how other binaries should link the given precompiled framework or library.
|
||||
/// - Parameter precompiled: Precompiled framework or library.
|
||||
func linking(precompiled: PrecompiledNode) throws -> BinaryLinking
|
||||
|
||||
/// It uses 'dwarfdump' to dump the UUIDs of each architecture.
|
||||
/// The UUIDs allows us to know which .bcsymbolmap files belong to this binary.
|
||||
/// - Parameter binaryPath: Path to the binary.
|
||||
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID>
|
||||
|
||||
/// It uses 'dwarfdump' to dump the UUIDs of each architecture.
|
||||
/// The UUIDs allows us to know which .bcsymbolmap files belong to this binary.
|
||||
/// - Parameter precompiled: Precompiled framework or library.
|
||||
func uuids(precompiled: PrecompiledNode) throws -> Set<UUID>
|
||||
}
|
||||
|
||||
public class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
public init() {}
|
||||
|
||||
public func architectures(precompiled: PrecompiledNode) throws -> [BinaryArchitecture] {
|
||||
try architectures(binaryPath: precompiled.binaryPath)
|
||||
}
|
||||
|
||||
public func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] {
|
||||
func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] {
|
||||
let result = try System.shared.capture("/usr/bin/lipo", "-info", binaryPath.pathString).spm_chuzzle() ?? ""
|
||||
let regexes = [
|
||||
// Non-fat file: path is architecture: x86_64
|
||||
|
@ -87,20 +70,12 @@ public class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
|||
return architectures
|
||||
}
|
||||
|
||||
public func linking(precompiled: PrecompiledNode) throws -> BinaryLinking {
|
||||
try linking(binaryPath: precompiled.binaryPath)
|
||||
}
|
||||
|
||||
public func linking(binaryPath: AbsolutePath) throws -> BinaryLinking {
|
||||
func linking(binaryPath: AbsolutePath) throws -> BinaryLinking {
|
||||
let result = try System.shared.capture("/usr/bin/file", binaryPath.pathString).spm_chuzzle() ?? ""
|
||||
return result.contains("dynamically linked") ? .dynamic : .static
|
||||
}
|
||||
|
||||
public func uuids(precompiled: PrecompiledNode) throws -> Set<UUID> {
|
||||
try uuids(binaryPath: precompiled.binaryPath)
|
||||
}
|
||||
|
||||
public func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> {
|
||||
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> {
|
||||
let output = try System.shared.capture(["/usr/bin/xcrun", "dwarfdump", "--uuid", binaryPath.pathString])
|
||||
// UUIDs are letters, decimals, or hyphens.
|
||||
var uuidCharacterSet = CharacterSet()
|
|
@ -28,48 +28,47 @@ enum XCFrameworkMetadataProviderError: FatalError, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol XCFrameworkMetadataProviding {
|
||||
/// Returns the available libraries for the xcframework at the given path.
|
||||
/// - Parameter frameworkPath: Path to the xcframework.
|
||||
func libraries(frameworkPath: AbsolutePath) throws -> [XCFrameworkInfoPlist.Library]
|
||||
protocol XCFrameworkMetadataProviding: PrecompiledMetadataProviding {
|
||||
/// Returns the info.plist of the xcframework at the given path.
|
||||
/// - Parameter xcframeworkPath: Path to the xcframework.
|
||||
func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist
|
||||
|
||||
/// Given a framework path and libraries it returns the path to its binary.
|
||||
/// - Parameter frameworkPath: Framework path.
|
||||
/// - Parameter xcframeworkPath: Path to the .xcframework
|
||||
/// - Parameter libraries: Framework available libraries
|
||||
func binaryPath(frameworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath
|
||||
func binaryPath(xcframeworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath
|
||||
}
|
||||
|
||||
public class XCFrameworkMetadataProvider: XCFrameworkMetadataProviding {
|
||||
public func libraries(frameworkPath: AbsolutePath) throws -> [XCFrameworkInfoPlist.Library] {
|
||||
class XCFrameworkMetadataProvider: PrecompiledMetadataProvider, XCFrameworkMetadataProviding {
|
||||
func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist {
|
||||
let fileHandler = FileHandler.shared
|
||||
let infoPlist = frameworkPath.appending(component: "Info.plist")
|
||||
let infoPlist = xcframeworkPath.appending(component: "Info.plist")
|
||||
guard fileHandler.exists(infoPlist) else {
|
||||
throw XCFrameworkMetadataProviderError.missingRequiredFile(infoPlist)
|
||||
}
|
||||
|
||||
let config: XCFrameworkInfoPlist = try fileHandler.readPlistFile(infoPlist)
|
||||
return config.libraries
|
||||
return try fileHandler.readPlistFile(infoPlist)
|
||||
}
|
||||
|
||||
public func binaryPath(frameworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath {
|
||||
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(frameworkPath)
|
||||
throw XCFrameworkMetadataProviderError.supportedArchitectureReferencesNotFound(xcframeworkPath)
|
||||
}
|
||||
let binaryName = frameworkPath.basenameWithoutExt
|
||||
let binaryName = xcframeworkPath.basenameWithoutExt
|
||||
|
||||
let binaryPath: AbsolutePath
|
||||
|
||||
switch library.path.extension {
|
||||
case "framework":
|
||||
binaryPath = AbsolutePath(library.identifier, relativeTo: frameworkPath)
|
||||
binaryPath = AbsolutePath(library.identifier, relativeTo: xcframeworkPath)
|
||||
.appending(RelativePath(library.path.pathString))
|
||||
.appending(component: binaryName)
|
||||
case "a":
|
||||
binaryPath = AbsolutePath(library.identifier, relativeTo: frameworkPath)
|
||||
binaryPath = AbsolutePath(library.identifier, relativeTo: xcframeworkPath)
|
||||
.appending(RelativePath(library.path.pathString))
|
||||
default:
|
||||
throw XCFrameworkMetadataProviderError.fileTypeNotRecognised(file: library.path, frameworkName: frameworkPath.basename)
|
||||
throw XCFrameworkMetadataProviderError.fileTypeNotRecognised(file: library.path, frameworkName: xcframeworkPath.basename)
|
||||
}
|
||||
return binaryPath
|
||||
}
|
|
@ -8,6 +8,6 @@ public enum BinaryArchitecture: String, Codable {
|
|||
case arm64
|
||||
}
|
||||
|
||||
public enum BinaryLinking: String {
|
||||
public enum BinaryLinking: String, Codable {
|
||||
case `static`, dynamic
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
|
||||
/// It represents th Info.plist contained in an .xcframework bundle.
|
||||
public struct XCFrameworkInfoPlist: Codable, Equatable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case libraries = "AvailableLibraries"
|
||||
}
|
||||
|
||||
/// It represents a library inside an .xcframework
|
||||
public struct Library: Codable, Equatable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case identifier = "LibraryIdentifier"
|
||||
|
@ -13,12 +15,18 @@ public struct XCFrameworkInfoPlist: Codable, Equatable {
|
|||
case architectures = "SupportedArchitectures"
|
||||
}
|
||||
|
||||
/// It represents the library's platform.
|
||||
public enum Platform: String, Codable {
|
||||
case ios
|
||||
}
|
||||
|
||||
/// Library identifier.
|
||||
public let identifier: String
|
||||
|
||||
/// Path to the library.
|
||||
public let path: RelativePath
|
||||
|
||||
/// Architectures the binary is built for.
|
||||
public let architectures: [BinaryArchitecture]
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
@ -29,5 +37,6 @@ public struct XCFrameworkInfoPlist: Codable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
/// List of libraries that are part of the .xcframework.
|
||||
public let libraries: [Library]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistSupport
|
||||
|
||||
enum FrameworkNodeLoaderError: FatalError, Equatable {
|
||||
case frameworkNotFound(AbsolutePath)
|
||||
|
||||
/// Error type.
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .frameworkNotFound:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
|
||||
/// Error description
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .frameworkNotFound(path):
|
||||
return "Couldn't find framework at \(path.pathString)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FrameworkNodeLoading {
|
||||
/// Reads an existing framework and returns its in-memory representation, FrameworkNode.
|
||||
/// - Parameter path: Path to the .framework.
|
||||
func load(path: AbsolutePath) throws -> FrameworkNode
|
||||
}
|
||||
|
||||
final class FrameworkNodeLoader: FrameworkNodeLoading {
|
||||
/// Framework metadata provider.
|
||||
fileprivate let frameworkMetadataProvider: FrameworkMetadataProviding
|
||||
|
||||
/// Initializes the loader with its attributes.
|
||||
/// - Parameter frameworkMetadataProvider: Framework metadata provider.
|
||||
init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) {
|
||||
self.frameworkMetadataProvider = frameworkMetadataProvider
|
||||
}
|
||||
|
||||
func load(path: AbsolutePath) throws -> FrameworkNode {
|
||||
guard FileHandler.shared.exists(path) else {
|
||||
throw FrameworkNodeLoaderError.frameworkNotFound(path)
|
||||
}
|
||||
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: path)
|
||||
let dsymsPath = frameworkMetadataProvider.dsymPath(frameworkPath: path)
|
||||
let bcsymbolmapPaths = try frameworkMetadataProvider.bcsymbolmapPaths(frameworkPath: path)
|
||||
let linking = try frameworkMetadataProvider.linking(binaryPath: binaryPath)
|
||||
let architectures = try frameworkMetadataProvider.architectures(binaryPath: binaryPath)
|
||||
|
||||
return FrameworkNode(path: path,
|
||||
dsymPath: dsymsPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistSupport
|
||||
|
||||
enum LibraryNodeLoaderError: FatalError, Equatable {
|
||||
case libraryNotFound(AbsolutePath)
|
||||
case publicHeadersNotFound(AbsolutePath)
|
||||
case swiftModuleMapNotFound(AbsolutePath)
|
||||
|
||||
/// Error type.
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .libraryNotFound: return .abort
|
||||
case .publicHeadersNotFound: return .abort
|
||||
case .swiftModuleMapNotFound: return .abort
|
||||
}
|
||||
}
|
||||
|
||||
/// Error description
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .libraryNotFound(path):
|
||||
return "The library \(path.pathString) does not exist"
|
||||
case let .publicHeadersNotFound(path):
|
||||
return "The public headers directory \(path.pathString) does not exist"
|
||||
case let .swiftModuleMapNotFound(path):
|
||||
return "The Swift modulemap file \(path.pathString) does not exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol LibraryNodeLoading {
|
||||
/// Reads an existing library and returns its in-memory representation, LibraryNode.
|
||||
/// - Parameters:
|
||||
/// - path: Path to the library.
|
||||
/// - publicHeaders: Path to the directorythat contains the public headers.
|
||||
/// - swiftModuleMap: Path to the Swift modulemap file.
|
||||
func load(path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?) throws -> LibraryNode
|
||||
}
|
||||
|
||||
final class LibraryNodeLoader: LibraryNodeLoading {
|
||||
/// Library metadata provider.
|
||||
fileprivate let libraryMetadataProvider: LibraryMetadataProviding
|
||||
|
||||
/// Initializes the loader with its attributes.
|
||||
/// - Parameter libraryMetadataProvider: Library metadata provider.
|
||||
init(libraryMetadataProvider: LibraryMetadataProviding = LibraryMetadataProvider()) {
|
||||
self.libraryMetadataProvider = libraryMetadataProvider
|
||||
}
|
||||
|
||||
func load(path: AbsolutePath,
|
||||
publicHeaders: AbsolutePath,
|
||||
swiftModuleMap: AbsolutePath?) throws -> LibraryNode {
|
||||
if !FileHandler.shared.exists(path) {
|
||||
throw LibraryNodeLoaderError.libraryNotFound(path)
|
||||
}
|
||||
if !FileHandler.shared.exists(publicHeaders) {
|
||||
throw LibraryNodeLoaderError.publicHeadersNotFound(publicHeaders)
|
||||
}
|
||||
|
||||
if let swiftModuleMap = swiftModuleMap {
|
||||
if !FileHandler.shared.exists(swiftModuleMap) {
|
||||
throw LibraryNodeLoaderError.swiftModuleMapNotFound(swiftModuleMap)
|
||||
}
|
||||
}
|
||||
let architectures = try libraryMetadataProvider.architectures(binaryPath: path)
|
||||
let linking = try libraryMetadataProvider.linking(binaryPath: path)
|
||||
|
||||
return LibraryNode(path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
architectures: architectures,
|
||||
linking: linking,
|
||||
swiftModuleMap: swiftModuleMap)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistSupport
|
||||
|
||||
enum XCFrameworkNodeLoaderError: FatalError, Equatable {
|
||||
case xcframeworkNotFound(AbsolutePath)
|
||||
|
||||
/// Error type.
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .xcframeworkNotFound:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
|
||||
/// Error description
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .xcframeworkNotFound(path):
|
||||
return "Couldn't find xcframework at \(path.pathString)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol XCFrameworkNodeLoading {
|
||||
/// Reads an existing xcframework and returns its in-memory representation, XCFrameworkNode..
|
||||
/// - Parameter path: Path to the .xcframework.
|
||||
func load(path: AbsolutePath) throws -> XCFrameworkNode
|
||||
}
|
||||
|
||||
final class XCFrameworkNodeLoader: XCFrameworkNodeLoading {
|
||||
/// xcframework metadata provider.
|
||||
fileprivate let xcframeworkMetadataProvider: XCFrameworkMetadataProviding
|
||||
|
||||
/// Initializes the loader with its attributes.
|
||||
/// - Parameter xcframeworkMetadataProvider: xcframework metadata provider.
|
||||
init(xcframeworkMetadataProvider: XCFrameworkMetadataProviding = XCFrameworkMetadataProvider()) {
|
||||
self.xcframeworkMetadataProvider = xcframeworkMetadataProvider
|
||||
}
|
||||
|
||||
func load(path: AbsolutePath) throws -> XCFrameworkNode {
|
||||
guard FileHandler.shared.exists(path) else {
|
||||
throw XCFrameworkNodeLoaderError.xcframeworkNotFound(path)
|
||||
}
|
||||
let infoPlist = try xcframeworkMetadataProvider.infoPlist(xcframeworkPath: path)
|
||||
let primaryBinaryPath = try xcframeworkMetadataProvider.binaryPath(xcframeworkPath: path,
|
||||
libraries: infoPlist.libraries)
|
||||
let linking = try xcframeworkMetadataProvider.linking(binaryPath: primaryBinaryPath)
|
||||
return XCFrameworkNode(path: path,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: primaryBinaryPath,
|
||||
linking: linking,
|
||||
dependencies: [])
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
|
||||
public protocol XCFrameworkParsing {
|
||||
static func parse(path: AbsolutePath, cache: GraphLoaderCaching) throws -> XCFrameworkNode
|
||||
}
|
||||
|
||||
struct XCFrameworkParser: XCFrameworkParsing {
|
||||
static func parse(path: AbsolutePath, cache: GraphLoaderCaching) throws -> XCFrameworkNode {
|
||||
if let xcframeworkNode = cache.precompiledNode(path) as? XCFrameworkNode {
|
||||
return xcframeworkNode
|
||||
}
|
||||
|
||||
let metadataProvider = XCFrameworkMetadataProvider()
|
||||
let libraries = try metadataProvider.libraries(frameworkPath: path)
|
||||
let binaryPath = try metadataProvider.binaryPath(frameworkPath: path, libraries: libraries)
|
||||
|
||||
let xcframeworkNode = XCFrameworkNode(
|
||||
path: path,
|
||||
libraries: libraries,
|
||||
primaryBinaryPath: binaryPath
|
||||
)
|
||||
cache.add(precompiledNode: xcframeworkNode)
|
||||
return xcframeworkNode
|
||||
}
|
||||
}
|
|
@ -4,7 +4,17 @@ import Foundation
|
|||
@testable import TuistCore
|
||||
|
||||
public extension FrameworkNode {
|
||||
static func test(path: AbsolutePath = "/Test.framework") -> FrameworkNode {
|
||||
FrameworkNode(path: path)
|
||||
static func test(path: AbsolutePath = "/path/to/Framework.framework",
|
||||
dsymPath: AbsolutePath? = nil,
|
||||
bcsymbolmapPaths: [AbsolutePath] = [],
|
||||
linking: BinaryLinking = .dynamic,
|
||||
architectures: [BinaryArchitecture] = [],
|
||||
dependencies: [FrameworkNode] = []) -> FrameworkNode {
|
||||
FrameworkNode(path: path,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
dependencies: dependencies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
public extension GraphDependencyReference {
|
||||
static func testFramework(
|
||||
path: AbsolutePath = "/frameworks/tuist.framework",
|
||||
binaryPath: AbsolutePath = "/frameworks/tuist.framework/tuist",
|
||||
isCarthage: Bool = false,
|
||||
dsymPath: AbsolutePath? = nil,
|
||||
bcsymbolmapPaths: [AbsolutePath] = [],
|
||||
linking: BinaryLinking = .dynamic,
|
||||
architectures: [BinaryArchitecture] = [.arm64],
|
||||
product: Product = .framework
|
||||
) -> GraphDependencyReference {
|
||||
GraphDependencyReference.framework(path: path,
|
||||
binaryPath: binaryPath,
|
||||
isCarthage: isCarthage,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
product: product)
|
||||
}
|
||||
|
||||
static func testXCFramework(
|
||||
path: AbsolutePath = "/frameworks/tuist.xcframework",
|
||||
infoPlist: XCFrameworkInfoPlist = .test(),
|
||||
primaryBinaryPath: AbsolutePath = "/frameworks/tuist.xcframework/ios-arm64/tuist",
|
||||
binaryPath: AbsolutePath = "/frameworks/tuist.xcframework/ios-arm64/tuist"
|
||||
) -> GraphDependencyReference {
|
||||
GraphDependencyReference.xcframework(path: path,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: primaryBinaryPath,
|
||||
binaryPath: binaryPath)
|
||||
}
|
||||
|
||||
static func testLibrary(path: AbsolutePath = "/libraries/library.a",
|
||||
binaryPath: AbsolutePath = "/libraries/library.a",
|
||||
linking: BinaryLinking = .static,
|
||||
architectures: [BinaryArchitecture] = [BinaryArchitecture.arm64],
|
||||
product: Product = .staticLibrary) -> GraphDependencyReference {
|
||||
GraphDependencyReference.library(path: path,
|
||||
binaryPath: binaryPath,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
product: product)
|
||||
}
|
||||
|
||||
static func testSDK(path: AbsolutePath = "/path/CoreData.framework", status: SDKStatus = .required) -> GraphDependencyReference {
|
||||
GraphDependencyReference.sdk(path: path,
|
||||
status: status)
|
||||
}
|
||||
|
||||
static func testProduct(target: String = "Target", productName: String = "Target.framework") -> GraphDependencyReference {
|
||||
GraphDependencyReference.product(target: target,
|
||||
productName: productName)
|
||||
}
|
||||
}
|
|
@ -4,8 +4,15 @@ import Foundation
|
|||
@testable import TuistCore
|
||||
|
||||
public extension LibraryNode {
|
||||
static func test(path: AbsolutePath = "/libTest.a",
|
||||
publicHeaders: AbsolutePath = "/TestHeaders/") -> LibraryNode {
|
||||
LibraryNode(path: path, publicHeaders: publicHeaders)
|
||||
static func test(path: AbsolutePath = "/Library/libTest.a",
|
||||
publicHeaders: AbsolutePath = "/Library/TestHeaders/",
|
||||
architectures: [BinaryArchitecture] = [.arm64],
|
||||
linking: BinaryLinking = .static,
|
||||
swiftModuleMap: AbsolutePath? = nil) -> LibraryNode {
|
||||
LibraryNode(path: path,
|
||||
publicHeaders: publicHeaders,
|
||||
architectures: architectures,
|
||||
linking: linking,
|
||||
swiftModuleMap: swiftModuleMap)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
|
||||
@testable import TuistCore
|
||||
|
||||
public extension XCFrameworkInfoPlist {
|
||||
static func test(libraries: [XCFrameworkInfoPlist.Library] = [.test()]) -> XCFrameworkInfoPlist {
|
||||
XCFrameworkInfoPlist(libraries: libraries)
|
||||
}
|
||||
}
|
||||
|
||||
public extension XCFrameworkInfoPlist.Library {
|
||||
static func test(identifier: String = "test",
|
||||
path: RelativePath = RelativePath("relative/to/library"),
|
||||
architectures: [BinaryArchitecture] = [.i386]) -> XCFrameworkInfoPlist.Library {
|
||||
XCFrameworkInfoPlist.Library(identifier: identifier,
|
||||
path: path,
|
||||
architectures: architectures)
|
||||
}
|
||||
}
|
|
@ -4,18 +4,15 @@ import Foundation
|
|||
@testable import TuistCore
|
||||
|
||||
public extension XCFrameworkNode {
|
||||
static func test(path: AbsolutePath = "/MyFramework.xcframework") -> XCFrameworkNode {
|
||||
let libraries: [XCFrameworkInfoPlist.Library] = [
|
||||
.init(identifier: "ios-x86_64-simulator",
|
||||
path: RelativePath("MyFramework.framework"), architectures: [.x8664]),
|
||||
.init(identifier: "ios-arm64",
|
||||
path: RelativePath("MyFramework.framework"),
|
||||
architectures: [.arm64]),
|
||||
]
|
||||
|
||||
let primaryBinaryPath = path.appending(RelativePath("ios-arm64/MyFramework.framework/MyFramework"))
|
||||
return XCFrameworkNode(path: path,
|
||||
libraries: libraries,
|
||||
primaryBinaryPath: primaryBinaryPath)
|
||||
static func test(path: AbsolutePath = "/MyFramework/MyFramework.xcframework",
|
||||
infoPlist: XCFrameworkInfoPlist = .test(),
|
||||
primaryBinaryPath: AbsolutePath = "/MyFramework/MyFramework.xcframework/binary",
|
||||
linking: BinaryLinking = .dynamic,
|
||||
dependencies: [XCFrameworkNode] = []) -> XCFrameworkNode {
|
||||
XCFrameworkNode(path: path,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: primaryBinaryPath,
|
||||
linking: linking,
|
||||
dependencies: dependencies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public final class MockFrameworkMetadataProvider: MockPrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
public var dsymPathStub: ((AbsolutePath) -> AbsolutePath?)?
|
||||
public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
dsymPathStub?(frameworkPath) ?? nil
|
||||
}
|
||||
|
||||
public var bcsymbolmapPathsStub: ((AbsolutePath) throws -> [AbsolutePath])?
|
||||
public func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
if let bcsymbolmapPathsStub = bcsymbolmapPathsStub {
|
||||
return try bcsymbolmapPathsStub(frameworkPath)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public var productStub: ((AbsolutePath) throws -> Product)?
|
||||
public func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
if let productStub = productStub {
|
||||
return try productStub(frameworkPath)
|
||||
} else {
|
||||
return .framework
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@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)
|
||||
} else {
|
||||
return .staticLibrary
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public class MockPrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
public init() {}
|
||||
|
||||
public var architecturesStub: ((AbsolutePath) throws -> [BinaryArchitecture])?
|
||||
public func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] {
|
||||
if let architecturesStub = architecturesStub {
|
||||
return try architecturesStub(binaryPath)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public var linkingStub: ((AbsolutePath) throws -> BinaryLinking)?
|
||||
public func linking(binaryPath: AbsolutePath) throws -> BinaryLinking {
|
||||
if let linkingStub = linkingStub {
|
||||
return try linkingStub(binaryPath)
|
||||
} else {
|
||||
return .dynamic
|
||||
}
|
||||
}
|
||||
|
||||
public var uuidsStub: ((AbsolutePath) throws -> Set<UUID>)?
|
||||
public func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> {
|
||||
if let uuidsStub = uuidsStub {
|
||||
return try uuidsStub(binaryPath)
|
||||
} else {
|
||||
return Set()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
@testable import TuistCore
|
||||
|
||||
public final class MockXCFrameworkMetadataProvider: MockPrecompiledMetadataProvider, XCFrameworkMetadataProviding {
|
||||
public var infoPlistStub: ((AbsolutePath) throws -> XCFrameworkInfoPlist)?
|
||||
public func infoPlist(xcframeworkPath: AbsolutePath) throws -> XCFrameworkInfoPlist {
|
||||
if let infoPlistStub = infoPlistStub {
|
||||
return try infoPlistStub(xcframeworkPath)
|
||||
} else {
|
||||
return XCFrameworkInfoPlist.test()
|
||||
}
|
||||
}
|
||||
|
||||
public var binaryPathStub: ((AbsolutePath, [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath)?
|
||||
public func binaryPath(xcframeworkPath: AbsolutePath, libraries: [XCFrameworkInfoPlist.Library]) throws -> AbsolutePath {
|
||||
if let binaryPathStub = binaryPathStub {
|
||||
return try binaryPathStub(xcframeworkPath, libraries)
|
||||
} else {
|
||||
return AbsolutePath.root
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
public final class MockFrameworkNodeLoader: FrameworkNodeLoading {
|
||||
public init() {}
|
||||
|
||||
var loadStub: ((AbsolutePath) throws -> FrameworkNode)?
|
||||
|
||||
public func load(path: AbsolutePath) throws -> FrameworkNode {
|
||||
if let loadStub = loadStub {
|
||||
return try loadStub(path)
|
||||
} else {
|
||||
return FrameworkNode.test(path: path)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
public final class MockLibraryNodeLoader: LibraryNodeLoading {
|
||||
public init() {}
|
||||
|
||||
var loadStub: ((AbsolutePath, AbsolutePath, AbsolutePath?) throws -> LibraryNode)?
|
||||
public func load(path: AbsolutePath, publicHeaders: AbsolutePath, swiftModuleMap: AbsolutePath?) throws -> LibraryNode {
|
||||
if let loadStub = loadStub {
|
||||
return try loadStub(path, publicHeaders, swiftModuleMap)
|
||||
} else {
|
||||
return LibraryNode.test(path: path, publicHeaders: publicHeaders, swiftModuleMap: swiftModuleMap)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
public final class MockXCFrameworkNodeLoader: XCFrameworkNodeLoading {
|
||||
public init() {}
|
||||
|
||||
var loadStub: ((AbsolutePath) throws -> XCFrameworkNode)?
|
||||
public func load(path: AbsolutePath) throws -> XCFrameworkNode {
|
||||
if let loadStub = loadStub {
|
||||
return try loadStub(path)
|
||||
} else {
|
||||
return XCFrameworkNode.test(path: path)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ final class BundleCommand: Command {
|
|||
init(parser: ArgumentParser,
|
||||
versionsController: VersionsControlling,
|
||||
installer: Installing) {
|
||||
let subParser = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
||||
_ = parser.add(subparser: BundleCommand.command, overview: BundleCommand.overview)
|
||||
self.versionsController = versionsController
|
||||
self.installer = installer
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ final class Installer: Installing {
|
|||
try FileHandler.shared.delete(installationDirectory)
|
||||
}
|
||||
try FileHandler.shared.createFolder(installationDirectory)
|
||||
|
||||
|
||||
let templatesDirectory = temporaryDirectory.path.appending(component: Constants.templatesDirectoryName)
|
||||
if FileHandler.shared.exists(templatesDirectory) {
|
||||
try FileHandler.shared.copy(from: templatesDirectory,
|
||||
|
|
|
@ -165,26 +165,31 @@ final class LinkGenerator: LinkGenerating {
|
|||
pbxTarget.buildPhases.append(precompiledEmbedPhase)
|
||||
pbxTarget.buildPhases.append(embedPhase)
|
||||
|
||||
var precompiledFrameworkPaths: [AbsolutePath] = []
|
||||
var frameworkReferences: [GraphDependencyReference] = []
|
||||
|
||||
try dependencies.forEach { dependency in
|
||||
if case let GraphDependencyReference.absolute(path) = dependency {
|
||||
if path.extension == "xcframework" {
|
||||
guard let fileRef = fileElements.file(path: path) else {
|
||||
throw LinkGeneratorError.missingReference(path: path)
|
||||
}
|
||||
let buildFile = PBXBuildFile(
|
||||
file: fileRef,
|
||||
settings: ["ATTRIBUTES": ["CodeSignOnCopy", "RemoveHeadersOnCopy"]]
|
||||
)
|
||||
pbxproj.add(object: buildFile)
|
||||
embedPhase.files?.append(buildFile)
|
||||
} else {
|
||||
precompiledFrameworkPaths.append(path)
|
||||
switch dependency {
|
||||
case .framework:
|
||||
frameworkReferences.append(dependency)
|
||||
case .library:
|
||||
// Do nothing
|
||||
break
|
||||
case let .xcframework(metadata):
|
||||
guard let fileRef = fileElements.file(path: metadata.path) else {
|
||||
throw LinkGeneratorError.missingReference(path: metadata.path)
|
||||
}
|
||||
} else if case let GraphDependencyReference.product(target, _) = dependency {
|
||||
guard let fileRef = fileElements.product(target: target) else {
|
||||
throw LinkGeneratorError.missingProduct(name: target)
|
||||
let buildFile = PBXBuildFile(
|
||||
file: fileRef,
|
||||
settings: ["ATTRIBUTES": ["CodeSignOnCopy", "RemoveHeadersOnCopy"]]
|
||||
)
|
||||
pbxproj.add(object: buildFile)
|
||||
embedPhase.files?.append(buildFile)
|
||||
case .sdk:
|
||||
// Do nothing
|
||||
break
|
||||
case let .product(metadata):
|
||||
guard let fileRef = fileElements.product(target: metadata.target) else {
|
||||
throw LinkGeneratorError.missingProduct(name: metadata.target)
|
||||
}
|
||||
let buildFile = PBXBuildFile(file: fileRef,
|
||||
settings: ["ATTRIBUTES": ["CodeSignOnCopy", "RemoveHeadersOnCopy"]])
|
||||
|
@ -193,10 +198,10 @@ final class LinkGenerator: LinkGenerating {
|
|||
}
|
||||
}
|
||||
|
||||
if precompiledFrameworkPaths.isEmpty {
|
||||
if frameworkReferences.isEmpty {
|
||||
precompiledEmbedPhase.shellScript = "echo \"Skipping, nothing to be embedded.\""
|
||||
} else {
|
||||
let script = try embedScriptGenerator.script(sourceRootPath: sourceRootPath, frameworkPaths: precompiledFrameworkPaths)
|
||||
let script = try embedScriptGenerator.script(sourceRootPath: sourceRootPath, frameworkReferences: frameworkReferences)
|
||||
precompiledEmbedPhase.shellScript = script.script
|
||||
precompiledEmbedPhase.inputPaths = script.inputPaths.map(\.pathString)
|
||||
precompiledEmbedPhase.outputPaths = script.outputPaths
|
||||
|
@ -206,11 +211,8 @@ final class LinkGenerator: LinkGenerating {
|
|||
func setupFrameworkSearchPath(dependencies: [GraphDependencyReference],
|
||||
pbxTarget: PBXTarget,
|
||||
sourceRootPath: AbsolutePath) throws {
|
||||
let paths = dependencies.compactMap { (dependency: GraphDependencyReference) -> AbsolutePath? in
|
||||
if case let .absolute(path) = dependency { return path }
|
||||
return nil
|
||||
}
|
||||
.map { $0.removingLastComponent() }
|
||||
let paths = dependencies.compactMap { $0.path }
|
||||
.map { $0.removingLastComponent() }
|
||||
|
||||
let uniquePaths = Array(Set(paths))
|
||||
try setup(setting: "FRAMEWORK_SEARCH_PATHS",
|
||||
|
@ -275,17 +277,25 @@ final class LinkGenerator: LinkGenerating {
|
|||
pbxproj.add(object: buildPhase)
|
||||
pbxTarget.buildPhases.append(buildPhase)
|
||||
|
||||
func addBuildFile(_ path: AbsolutePath) throws {
|
||||
guard let fileRef = fileElements.file(path: path) else {
|
||||
throw LinkGeneratorError.missingReference(path: path)
|
||||
}
|
||||
let buildFile = PBXBuildFile(file: fileRef)
|
||||
pbxproj.add(object: buildFile)
|
||||
buildPhase.files?.append(buildFile)
|
||||
}
|
||||
|
||||
try dependencies
|
||||
.sorted()
|
||||
.forEach { dependency in
|
||||
switch dependency {
|
||||
case let .absolute(path):
|
||||
guard let fileRef = fileElements.file(path: path) else {
|
||||
throw LinkGeneratorError.missingReference(path: path)
|
||||
}
|
||||
let buildFile = PBXBuildFile(file: fileRef)
|
||||
pbxproj.add(object: buildFile)
|
||||
buildPhase.files?.append(buildFile)
|
||||
case let .framework(metadata):
|
||||
try addBuildFile(metadata.path)
|
||||
case let .library(metadata):
|
||||
try addBuildFile(metadata.path)
|
||||
case let .xcframework(metadata):
|
||||
try addBuildFile(metadata.path)
|
||||
case let .product(target, _):
|
||||
guard let fileRef = fileElements.product(target: target) else {
|
||||
throw LinkGeneratorError.missingProduct(name: target)
|
||||
|
|
|
@ -179,15 +179,23 @@ class ProjectFileElements {
|
|||
sourceRootPath: AbsolutePath,
|
||||
filesGroup: ProjectGroup) throws {
|
||||
let sortedDependencies = dependencyReferences.sorted()
|
||||
|
||||
func generatePrecompiled(_ path: AbsolutePath) throws {
|
||||
let fileElement = GroupFileElement(path: path, group: filesGroup)
|
||||
try generate(fileElement: fileElement,
|
||||
groups: groups,
|
||||
pbxproj: pbxproj,
|
||||
sourceRootPath: sourceRootPath)
|
||||
}
|
||||
|
||||
try sortedDependencies.forEach { dependency in
|
||||
switch dependency {
|
||||
case let .absolute(dependencyPath):
|
||||
let fileElement = GroupFileElement(path: dependencyPath,
|
||||
group: filesGroup)
|
||||
try generate(fileElement: fileElement,
|
||||
groups: groups,
|
||||
pbxproj: pbxproj,
|
||||
sourceRootPath: sourceRootPath)
|
||||
case let .xcframework(metadata):
|
||||
try generatePrecompiled(metadata.path)
|
||||
case let .framework(metadata):
|
||||
try generatePrecompiled(metadata.path)
|
||||
case let .library(metadata):
|
||||
try generatePrecompiled(metadata.path)
|
||||
case let .sdk(sdkNodePath, _):
|
||||
generateSDKFileElement(sdkNodePath: sdkNodePath,
|
||||
toGroup: groups.frameworks,
|
||||
|
|
|
@ -36,30 +36,44 @@ protocol WorkspaceGenerating: AnyObject {
|
|||
}
|
||||
|
||||
final class WorkspaceGenerator: WorkspaceGenerating {
|
||||
struct Config {
|
||||
/// The execution context to use when generating
|
||||
/// descriptors for each project within the workspace / graph
|
||||
var projectGenerationContext: ExecutionContext
|
||||
static var `default`: Config {
|
||||
Config(projectGenerationContext: .concurrent)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Attributes
|
||||
|
||||
private let projectGenerator: ProjectGenerating
|
||||
private let workspaceStructureGenerator: WorkspaceStructureGenerating
|
||||
private let schemesGenerator: SchemesGenerating
|
||||
private let config: Config
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider()) {
|
||||
convenience init(defaultSettingsProvider: DefaultSettingsProviding = DefaultSettingsProvider(),
|
||||
config: Config = .default) {
|
||||
let configGenerator = ConfigGenerator(defaultSettingsProvider: defaultSettingsProvider)
|
||||
let targetGenerator = TargetGenerator(configGenerator: configGenerator)
|
||||
let projectGenerator = ProjectGenerator(targetGenerator: targetGenerator,
|
||||
configGenerator: configGenerator)
|
||||
self.init(projectGenerator: projectGenerator,
|
||||
workspaceStructureGenerator: WorkspaceStructureGenerator(),
|
||||
schemesGenerator: SchemesGenerator())
|
||||
schemesGenerator: SchemesGenerator(),
|
||||
config: config)
|
||||
}
|
||||
|
||||
init(projectGenerator: ProjectGenerating,
|
||||
workspaceStructureGenerator: WorkspaceStructureGenerating,
|
||||
schemesGenerator: SchemesGenerating) {
|
||||
schemesGenerator: SchemesGenerating,
|
||||
config: Config = .default) {
|
||||
self.projectGenerator = projectGenerator
|
||||
self.workspaceStructureGenerator = workspaceStructureGenerator
|
||||
self.schemesGenerator = schemesGenerator
|
||||
self.config = config
|
||||
}
|
||||
|
||||
// MARK: - WorkspaceGenerating
|
||||
|
@ -70,7 +84,7 @@ final class WorkspaceGenerator: WorkspaceGenerating {
|
|||
logger.notice("Generating workspace \(workspaceName)", metadata: .section)
|
||||
|
||||
/// Projects
|
||||
let projects = try graph.projects.map { project in
|
||||
let projects = try graph.projects.map(context: config.projectGenerationContext) { project in
|
||||
try projectGenerator.generate(project: project,
|
||||
graph: graph,
|
||||
sourceRootPath: project.path,
|
||||
|
|
|
@ -11,8 +11,8 @@ protocol EmbedScriptGenerating {
|
|||
/// It returns the script and the input paths list that should be used to generate a Xcode script phase
|
||||
/// to embed the given frameworks into the compiled product.
|
||||
/// - Parameter sourceRootPath: Directory where the Xcode project will be created.
|
||||
/// - Parameter frameworkPaths: Path to the frameworks.
|
||||
func script(sourceRootPath: AbsolutePath, frameworkPaths: [AbsolutePath]) throws -> EmbedScript
|
||||
/// - Parameter frameworkReferences: Framework references.
|
||||
func script(sourceRootPath: AbsolutePath, frameworkReferences: [GraphDependencyReference]) throws -> EmbedScript
|
||||
}
|
||||
|
||||
/// It represents a embed frameworks script.
|
||||
|
@ -30,17 +30,11 @@ struct EmbedScript {
|
|||
final class EmbedScriptGenerator: EmbedScriptGenerating {
|
||||
typealias FrameworkScript = (script: String, inputPaths: [RelativePath], outputPaths: [String])
|
||||
|
||||
let frameworkMetadataProvider: FrameworkMetadataProviding
|
||||
|
||||
init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) {
|
||||
self.frameworkMetadataProvider = frameworkMetadataProvider
|
||||
}
|
||||
|
||||
func script(sourceRootPath: AbsolutePath, frameworkPaths: [AbsolutePath]) throws -> EmbedScript {
|
||||
func script(sourceRootPath: AbsolutePath, frameworkReferences: [GraphDependencyReference]) throws -> EmbedScript {
|
||||
var script = baseScript()
|
||||
script.append("\n")
|
||||
|
||||
let (frameworksScript, inputPaths, outputPaths) = try self.frameworksScript(sourceRootPath: sourceRootPath, frameworkPaths: frameworkPaths)
|
||||
let (frameworksScript, inputPaths, outputPaths) = try self.frameworksScript(sourceRootPath: sourceRootPath, frameworkReferences: frameworkReferences)
|
||||
script.append(frameworksScript)
|
||||
|
||||
return EmbedScript(script: script, inputPaths: inputPaths, outputPaths: outputPaths)
|
||||
|
@ -48,21 +42,25 @@ final class EmbedScriptGenerator: EmbedScriptGenerating {
|
|||
|
||||
// MARK: - Fileprivate
|
||||
|
||||
fileprivate func frameworksScript(sourceRootPath: AbsolutePath,
|
||||
frameworkPaths: [AbsolutePath]) throws -> FrameworkScript {
|
||||
fileprivate func frameworksScript(sourceRootPath: AbsolutePath, frameworkReferences: [GraphDependencyReference]) throws -> FrameworkScript {
|
||||
var script = ""
|
||||
var inputPaths: [RelativePath] = []
|
||||
var outputPaths: [String] = []
|
||||
|
||||
for frameworkPath in frameworkPaths {
|
||||
for frameworkReference in frameworkReferences {
|
||||
guard case let GraphDependencyReference.framework(path, _, _, dsymPath, bcsymbolmapPaths, _, _, _) = frameworkReference else {
|
||||
preconditionFailure("references need to be of type framework")
|
||||
break
|
||||
}
|
||||
|
||||
// Framework
|
||||
let relativeFrameworkPath = frameworkPath.relative(to: sourceRootPath)
|
||||
let relativeFrameworkPath = path.relative(to: sourceRootPath)
|
||||
script.append("install_framework \"\(relativeFrameworkPath.pathString)\"\n")
|
||||
inputPaths.append(relativeFrameworkPath)
|
||||
outputPaths.append("${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/\(relativeFrameworkPath.basename)")
|
||||
|
||||
// .dSYM
|
||||
if let dsymPath = frameworkMetadataProvider.dsymPath(frameworkPath: frameworkPath) {
|
||||
if let dsymPath = dsymPath {
|
||||
let relativeDsymPath = dsymPath.relative(to: sourceRootPath)
|
||||
script.append("install_dsym \"\(relativeDsymPath.pathString)\"\n")
|
||||
inputPaths.append(relativeDsymPath)
|
||||
|
@ -70,8 +68,7 @@ final class EmbedScriptGenerator: EmbedScriptGenerating {
|
|||
}
|
||||
|
||||
// .bcsymbolmap
|
||||
let bcsymbolmaps = try frameworkMetadataProvider.bcsymbolmapPaths(frameworkPath: frameworkPath)
|
||||
for bcsymbolmapPath in bcsymbolmaps {
|
||||
for bcsymbolmapPath in bcsymbolmapPaths {
|
||||
let relativeDsymPath = bcsymbolmapPath.relative(to: sourceRootPath)
|
||||
script.append("install_bcsymbolmap \"\(relativeDsymPath.pathString)\"\n")
|
||||
inputPaths.append(relativeDsymPath)
|
||||
|
|
|
@ -11,13 +11,29 @@ public protocol XcodeProjWriting {
|
|||
// MARK: -
|
||||
|
||||
public final class XcodeProjWriter: XcodeProjWriting {
|
||||
public struct Config {
|
||||
/// The execution context to use when writing
|
||||
/// the project descriptors within a workspace descriptor
|
||||
public var projectDescriptorWritingContext: ExecutionContext
|
||||
public init(projectDescriptorWritingContext: ExecutionContext) {
|
||||
self.projectDescriptorWritingContext = projectDescriptorWritingContext
|
||||
}
|
||||
|
||||
public static var `default`: Config {
|
||||
Config(projectDescriptorWritingContext: .concurrent)
|
||||
}
|
||||
}
|
||||
|
||||
private let fileHandler: FileHandling
|
||||
private let system: Systeming
|
||||
private let config: Config
|
||||
|
||||
public init(fileHandler: FileHandling = FileHandler.shared,
|
||||
system: Systeming = System.shared) {
|
||||
system: Systeming = System.shared,
|
||||
config: Config = .default) {
|
||||
self.fileHandler = fileHandler
|
||||
self.system = system
|
||||
self.config = config
|
||||
}
|
||||
|
||||
public func write(project: ProjectDescriptor) throws {
|
||||
|
@ -28,7 +44,7 @@ public final class XcodeProjWriter: XcodeProjWriting {
|
|||
}
|
||||
|
||||
public func write(workspace: WorkspaceDescriptor) throws {
|
||||
try workspace.projectDescriptors.forEach(write)
|
||||
try workspace.projectDescriptors.forEach(context: config.projectDescriptorWritingContext, write)
|
||||
try workspace.xcworkspace.write(path: workspace.xcworkspacePath.path, override: true)
|
||||
try workspace.schemeDescriptors.forEach { try write(scheme: $0, xccontainerPath: workspace.xcworkspacePath) }
|
||||
try workspace.sideEffectDescriptors.forEach(perform)
|
||||
|
|
|
@ -4,8 +4,8 @@ import SPMUtility
|
|||
import TuistCore
|
||||
import TuistGenerator
|
||||
import TuistLoader
|
||||
import TuistSupport
|
||||
import TuistScaffold
|
||||
import TuistSupport
|
||||
|
||||
private typealias Platform = TuistCore.Platform
|
||||
private typealias Product = TuistCore.Product
|
||||
|
@ -109,7 +109,7 @@ class InitCommand: NSObject, Command {
|
|||
self.templateGenerator = templateGenerator
|
||||
self.templateLoader = templateLoader
|
||||
}
|
||||
|
||||
|
||||
func parse(with parser: ArgumentParser, arguments: [String]) throws -> ArgumentParser.Result {
|
||||
guard arguments.contains("--template") else { return try parser.parse(arguments) }
|
||||
// Plucking out path and template argument
|
||||
|
@ -117,10 +117,10 @@ class InitCommand: NSObject, Command {
|
|||
arguments[$0 ..< min($0 + 2, arguments.count)]
|
||||
}
|
||||
let filteredArguments = pairedArguments
|
||||
.filter {
|
||||
$0.first == "--path" || $0.first == "--template"
|
||||
}
|
||||
.flatMap { Array($0) }
|
||||
.filter {
|
||||
$0.first == "--path" || $0.first == "--template"
|
||||
}
|
||||
.flatMap { Array($0) }
|
||||
// We want to parse only the name of template, not its arguments which will be dynamically added
|
||||
let resultArguments = try parser.parse(filteredArguments)
|
||||
|
||||
|
@ -150,7 +150,7 @@ class InitCommand: NSObject, Command {
|
|||
let path = self.path(arguments: arguments)
|
||||
let name = try self.name(arguments: arguments, path: path)
|
||||
try verifyDirectoryIsEmpty(path: path)
|
||||
|
||||
|
||||
let directories = try templatesDirectoryLocator.templateDirectories(at: path)
|
||||
if let template = arguments.get(templateArgument) {
|
||||
guard
|
||||
|
@ -174,7 +174,6 @@ class InitCommand: NSObject, Command {
|
|||
attributes: ["name": name, "platform": platform.caseValue])
|
||||
}
|
||||
|
||||
|
||||
logger.notice("Project generated at path \(path.pathString).", metadata: .success)
|
||||
}
|
||||
|
||||
|
@ -189,7 +188,7 @@ class InitCommand: NSObject, Command {
|
|||
throw InitCommandError.nonEmptyDirectory(path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Validates if all `attributes` from `template` have been provided
|
||||
/// If those attributes are optional, they default to `default` if not provided
|
||||
/// - Returns: Array of parsed attributes
|
||||
|
@ -218,7 +217,7 @@ class InitCommand: NSObject, Command {
|
|||
return mutableDict
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Finds template directory
|
||||
/// - Parameters:
|
||||
/// - templateDirectories: Paths of available templates
|
||||
|
|
|
@ -22,7 +22,7 @@ public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
|||
}
|
||||
|
||||
// MARK: - TemplatesDirectoryLocating
|
||||
|
||||
|
||||
public func locateTuistTemplates() -> AbsolutePath? {
|
||||
#if DEBUG
|
||||
// Used only for debug purposed to find templates in your tuist working directory
|
||||
|
@ -44,7 +44,6 @@ public final class TemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
|||
return candidates.first(where: FileHandler.shared.exists)
|
||||
}
|
||||
|
||||
|
||||
public func locateUserTemplates(at: AbsolutePath) -> AbsolutePath? {
|
||||
guard let customTemplatesDirectory = locate(from: at) else { return nil }
|
||||
if !FileHandler.shared.exists(customTemplatesDirectory) { return nil }
|
||||
|
|
|
@ -14,7 +14,7 @@ public final class MockTemplatesDirectoryLocator: TemplatesDirectoryLocating {
|
|||
public func templateDirectories(at path: AbsolutePath) throws -> [AbsolutePath] {
|
||||
try templateDirectoriesStub?(path) ?? []
|
||||
}
|
||||
|
||||
|
||||
public func locateTuistTemplates() -> AbsolutePath? {
|
||||
locateTuistTemplatesStub?()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import Foundation
|
|||
/// Ensures that writing and reading from property annotated with this property wrapper is thread safe
|
||||
/// Taken from https://www.onswiftwings.com/posts/atomic-property-wrapper/
|
||||
@propertyWrapper
|
||||
struct Atomic<Value> {
|
||||
class Atomic<Value> {
|
||||
private var value: Value
|
||||
private let lock = NSLock()
|
||||
|
||||
|
@ -16,13 +16,13 @@ struct Atomic<Value> {
|
|||
set { store(newValue: newValue) }
|
||||
}
|
||||
|
||||
func load() -> Value {
|
||||
private func load() -> Value {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return value
|
||||
}
|
||||
|
||||
mutating func store(newValue: Value) {
|
||||
private func store(newValue: Value) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
value = newValue
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import Foundation
|
||||
|
||||
/// Execution Context
|
||||
///
|
||||
/// Defines a context for operations to be performed in.
|
||||
/// e.g. `.concurrent` or `.serial`
|
||||
///
|
||||
public struct ExecutionContext {
|
||||
public enum ExecutionType {
|
||||
case serial
|
||||
case concurrent
|
||||
}
|
||||
|
||||
public var executionType: ExecutionType
|
||||
public init(executionType: ExecutionType) {
|
||||
self.executionType = executionType
|
||||
}
|
||||
|
||||
public static var serial: ExecutionContext {
|
||||
ExecutionContext(executionType: .serial)
|
||||
}
|
||||
|
||||
public static var concurrent: ExecutionContext {
|
||||
ExecutionContext(executionType: .concurrent)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array {
|
||||
/// Map (with execution context)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - context: The execution context to perform the `transform` with
|
||||
/// - transform: The transformation closure to apply to the array
|
||||
func map<B>(context: ExecutionContext, _ transform: (Element) throws -> B) rethrows -> [B] {
|
||||
switch context.executionType {
|
||||
case .serial:
|
||||
return try map(transform)
|
||||
case .concurrent:
|
||||
return try concurrentMap(transform)
|
||||
}
|
||||
}
|
||||
|
||||
/// For Each (with execution context)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - context: The execution context to perform the `perform` operation with
|
||||
/// - transform: The perform closure to call on each element in the array
|
||||
func forEach(context: ExecutionContext, _ perform: (Element) throws -> Void) rethrows {
|
||||
switch context.executionType {
|
||||
case .serial:
|
||||
return try forEach(perform)
|
||||
case .concurrent:
|
||||
return try concurrentForEach(perform)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
//
|
||||
// Concurrent Map / For Each
|
||||
// based on https://talk.objc.io/episodes/S01E90-concurrent-map
|
||||
//
|
||||
extension Array {
|
||||
private final class ThreadSafe<A> {
|
||||
private var _value: A
|
||||
private let queue = DispatchQueue(label: "ThreadSafe")
|
||||
init(_ value: A) {
|
||||
_value = value
|
||||
}
|
||||
|
||||
var value: A {
|
||||
queue.sync { _value }
|
||||
}
|
||||
|
||||
func atomically(_ transform: @escaping (inout A) -> Void) {
|
||||
queue.async {
|
||||
transform(&self._value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func concurrentMap<B>(_ transform: (Element) throws -> B) rethrows -> [B] {
|
||||
let result = ThreadSafe([Result<B, Error>?](repeating: nil, count: count))
|
||||
DispatchQueue.concurrentPerform(iterations: count) { idx in
|
||||
let element = self[idx]
|
||||
do {
|
||||
let transformed = try transform(element)
|
||||
result.atomically {
|
||||
$0[idx] = .success(transformed)
|
||||
}
|
||||
} catch {
|
||||
result.atomically {
|
||||
$0[idx] = .failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return try result.value.map { try $0!.get() }
|
||||
}
|
||||
|
||||
private func concurrentForEach(_ perform: (Element) throws -> Void) rethrows {
|
||||
let result = ThreadSafe([Error?](repeating: nil, count: count))
|
||||
DispatchQueue.concurrentPerform(iterations: count) { idx in
|
||||
let element = self[idx]
|
||||
do {
|
||||
try perform(element)
|
||||
} catch {
|
||||
result.atomically {
|
||||
$0[idx] = error
|
||||
}
|
||||
}
|
||||
}
|
||||
return try result.value.compactMap { $0 }.forEach {
|
||||
throw $0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -65,9 +65,7 @@ public struct StandardLogHandler: LogHandler {
|
|||
|
||||
extension FileHandle {
|
||||
func print(_ string: String, terminator: String = "\n") {
|
||||
string.data(using: .utf8)
|
||||
.map(write)
|
||||
terminator.data(using: .utf8)
|
||||
(string + terminator).data(using: .utf8)
|
||||
.map(write)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,14 +32,18 @@ public class XcodeController: XcodeControlling {
|
|||
/// - Throws: An error if it can't be obtained.
|
||||
public func selected() throws -> Xcode? {
|
||||
// Return cached value if available
|
||||
guard selectedXcode == nil else { return selectedXcode }
|
||||
if let cached = selectedXcode {
|
||||
return cached
|
||||
}
|
||||
|
||||
// e.g. /Applications/Xcode.app/Contents/Developer
|
||||
guard let path = try? System.shared.capture(["xcode-select", "-p"]).spm_chomp() else {
|
||||
return nil
|
||||
}
|
||||
selectedXcode = try Xcode.read(path: AbsolutePath(path).parentDirectory.parentDirectory)
|
||||
return selectedXcode
|
||||
|
||||
let xcode = try Xcode.read(path: AbsolutePath(path).parentDirectory.parentDirectory)
|
||||
selectedXcode = xcode
|
||||
return xcode
|
||||
}
|
||||
|
||||
enum XcodeVersionError: FatalError {
|
||||
|
|
|
@ -9,7 +9,14 @@ import Foundation
|
|||
import TuistSupport
|
||||
|
||||
public struct TestingLogHandler: LogHandler {
|
||||
static var collected: [Logger.Level: [String]] = [:]
|
||||
static var collected: [Logger.Level: [String]] {
|
||||
collectionQueue.sync {
|
||||
collectedLogs
|
||||
}
|
||||
}
|
||||
|
||||
private static var collectionQueue = DispatchQueue(label: "io.tuist.tuistTestingSupport.logging")
|
||||
private static var collectedLogs: [Logger.Level: [String]] = [:]
|
||||
|
||||
public var logLevel: Logger.Level
|
||||
public let label: String
|
||||
|
@ -25,7 +32,9 @@ public struct TestingLogHandler: LogHandler {
|
|||
metadata _: Logger.Metadata?,
|
||||
file _: String, function _: String, line _: UInt
|
||||
) {
|
||||
Self.collected[level, default: []].append(message.description)
|
||||
TestingLogHandler.collectionQueue.async {
|
||||
TestingLogHandler.collectedLogs[level, default: []].append(message.description)
|
||||
}
|
||||
}
|
||||
|
||||
public var metadata = Logger.Metadata()
|
||||
|
|
|
@ -23,10 +23,9 @@ final class FrameworkMetadataProviderIntegrationTests: TuistTestCase {
|
|||
// Given
|
||||
let carthagePath = try temporaryFixture("Carthage/")
|
||||
let frameworkPath = FileHandler.shared.glob(carthagePath, glob: "*.framework").first!
|
||||
let framework = FrameworkNode(path: frameworkPath)
|
||||
|
||||
// When
|
||||
let got = try subject.bcsymbolmapPaths(framework: framework).sorted()
|
||||
let got = try subject.bcsymbolmapPaths(frameworkPath: frameworkPath).sorted()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, [
|
||||
|
@ -39,10 +38,9 @@ final class FrameworkMetadataProviderIntegrationTests: TuistTestCase {
|
|||
// Given
|
||||
let carthagePath = try temporaryFixture("Carthage/")
|
||||
let frameworkPath = FileHandler.shared.glob(carthagePath, glob: "*.framework").first!
|
||||
let framework = FrameworkNode(path: frameworkPath)
|
||||
|
||||
// When
|
||||
let got = subject.dsymPath(framework: framework)
|
||||
let got = subject.dsymPath(frameworkPath: frameworkPath)
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(got == carthagePath.appending(component: "\(frameworkPath.basename).dSYM"))
|
|
@ -22,10 +22,9 @@ final class PrecompiledMetadataProviderIntegrationTests: TuistTestCase {
|
|||
func test_architectures() throws {
|
||||
// Given
|
||||
let frameworkPath = try temporaryFixture("xpm.framework")
|
||||
let framework = FrameworkNode(path: frameworkPath)
|
||||
|
||||
// When
|
||||
let got = try subject.architectures(precompiled: framework)
|
||||
let got = try subject.architectures(binaryPath: FrameworkNode.binaryPath(frameworkPath: frameworkPath))
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.map(\.rawValue).sorted(), ["arm64", "x86_64"])
|
||||
|
@ -34,10 +33,9 @@ final class PrecompiledMetadataProviderIntegrationTests: TuistTestCase {
|
|||
func test_uuids() throws {
|
||||
// Given
|
||||
let frameworkPath = try temporaryFixture("xpm.framework")
|
||||
let framework = FrameworkNode(path: frameworkPath)
|
||||
|
||||
// When
|
||||
let got = try subject.uuids(precompiled: framework)
|
||||
let got = try subject.uuids(binaryPath: FrameworkNode.binaryPath(frameworkPath: frameworkPath))
|
||||
|
||||
// Then
|
||||
let expected = Set([
|
|
@ -8,57 +8,27 @@ import XCTest
|
|||
@testable import TuistSupportTesting
|
||||
|
||||
final class GraphDependencyReferenceTests: TuistUnitTestCase {
|
||||
func test_equal() {
|
||||
let subjects: [(GraphDependencyReference, GraphDependencyReference, Bool)] = [
|
||||
// Absolute
|
||||
(.absolute(.init("/a.framework")), .absolute(.init("/a.framework")), true),
|
||||
(.absolute(.init("/a.framework")), .product(target: "Main", productName: "Main.app"), false),
|
||||
(.absolute(.init("/a.framework")), .sdk(.init("/CoreData.framework"), .required), false),
|
||||
|
||||
// Product
|
||||
(.product(target: "Main", productName: "Main.app"), .product(target: "Main", productName: "Main.app"), true),
|
||||
(.product(target: "Main", productName: "Main.app"), .absolute(.init("/a.framework")), false),
|
||||
(.product(target: "Main", productName: "Main.app"), .sdk(.init("/CoreData.framework"), .required), false),
|
||||
(.product(target: "Main-iOS", productName: "Main.app"), .product(target: "Main-macOS", productName: "Main.app"), false),
|
||||
|
||||
// SDK
|
||||
(.sdk(.init("/CoreData.framework"), .required), .sdk(.init("/CoreData.framework"), .required), true),
|
||||
(.sdk(.init("/CoreData.framework"), .required), .product(target: "Main", productName: "Main.app"), false),
|
||||
(.sdk(.init("/CoreData.framework"), .required), .absolute(.init("/a.framework")), false),
|
||||
]
|
||||
|
||||
XCTAssertEqualPairs(subjects)
|
||||
}
|
||||
|
||||
func test_compare() {
|
||||
XCTAssertFalse(GraphDependencyReference.absolute("/A") < .absolute("/A"))
|
||||
XCTAssertTrue(GraphDependencyReference.absolute("/A") < .absolute("/B"))
|
||||
XCTAssertFalse(GraphDependencyReference.absolute("/B") < .absolute("/A"))
|
||||
// Given
|
||||
let subject = makeReferences().sorted()
|
||||
|
||||
XCTAssertFalse(GraphDependencyReference.product(target: "A", productName: "A.framework") < .product(target: "A", productName: "A.framework"))
|
||||
XCTAssertTrue(GraphDependencyReference.product(target: "A", productName: "A.framework") < .product(target: "B", productName: "B.framework"))
|
||||
XCTAssertFalse(GraphDependencyReference.product(target: "B", productName: "B.framework") < .product(target: "A", productName: "A.framework"))
|
||||
XCTAssertTrue(GraphDependencyReference.product(target: "A", productName: "A.app") < .product(target: "A", productName: "A.framework"))
|
||||
|
||||
XCTAssertTrue(GraphDependencyReference.product(target: "/A", productName: "A.framework") < .absolute("/A"))
|
||||
XCTAssertTrue(GraphDependencyReference.product(target: "/A", productName: "A.framework") < .absolute("/B"))
|
||||
XCTAssertTrue(GraphDependencyReference.product(target: "/B", productName: "B.framework") < .absolute("/A"))
|
||||
|
||||
XCTAssertFalse(GraphDependencyReference.absolute("/A") < .product(target: "/A", productName: "A.framework"))
|
||||
XCTAssertFalse(GraphDependencyReference.absolute("/A") < .product(target: "/B", productName: "B.framework"))
|
||||
XCTAssertFalse(GraphDependencyReference.absolute("/B") < .product(target: "/A", productName: "A.framework"))
|
||||
XCTAssertEqual(subject, [
|
||||
.sdk(path: "/A.framework", status: .required),
|
||||
.sdk(path: "/B.framework", status: .optional),
|
||||
.product(target: "A", productName: "A.framework"),
|
||||
.product(target: "B", productName: "B.framework"),
|
||||
.testLibrary(path: "/libraries/A.library"),
|
||||
.testLibrary(path: "/libraries/B.library"),
|
||||
.testFramework(path: "/frameworks/A.framework"),
|
||||
.testFramework(path: "/frameworks/B.framework"),
|
||||
.testXCFramework(path: "/xcframeworks/A.xcframework"),
|
||||
.testXCFramework(path: "/xcframeworks/B.xcframework"),
|
||||
])
|
||||
}
|
||||
|
||||
func test_compare_isStable() {
|
||||
// Given
|
||||
let subject: [GraphDependencyReference] = [
|
||||
.absolute("/A"),
|
||||
.absolute("/B"),
|
||||
.product(target: "A", productName: "A.framework"),
|
||||
.product(target: "B", productName: "B.framework"),
|
||||
.sdk("/A.framework", .required),
|
||||
.sdk("/B.framework", .optional),
|
||||
]
|
||||
let subject = makeReferences()
|
||||
|
||||
// When
|
||||
let sorted = (0 ..< 10).map { _ in subject.shuffled().sorted() }
|
||||
|
@ -67,4 +37,19 @@ final class GraphDependencyReferenceTests: TuistUnitTestCase {
|
|||
let unstable = sorted.dropFirst().filter { $0 != sorted.first }
|
||||
XCTAssertTrue(unstable.isEmpty)
|
||||
}
|
||||
|
||||
func makeReferences() -> [GraphDependencyReference] {
|
||||
[
|
||||
.testXCFramework(path: "/xcframeworks/A.xcframework"),
|
||||
.testXCFramework(path: "/xcframeworks/B.xcframework"),
|
||||
.testFramework(path: "/frameworks/A.framework"),
|
||||
.testFramework(path: "/frameworks/B.framework"),
|
||||
.testLibrary(path: "/libraries/A.library"),
|
||||
.testLibrary(path: "/libraries/B.library"),
|
||||
.product(target: "A", productName: "A.framework"),
|
||||
.product(target: "B", productName: "B.framework"),
|
||||
.sdk(path: "/A.framework", status: .required),
|
||||
.sdk(path: "/B.framework", status: .optional),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import TuistSupport
|
|||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupport
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
|
@ -22,7 +23,7 @@ final class GraphErrorTests: XCTestCase {
|
|||
|
||||
final class GraphTests: TuistUnitTestCase {
|
||||
func test_frameworks() throws {
|
||||
let framework = FrameworkNode(path: AbsolutePath("/path/to/framework.framework"))
|
||||
let framework = FrameworkNode.test(path: AbsolutePath("/path/to/framework.framework"))
|
||||
let cache = GraphLoaderCache()
|
||||
cache.add(precompiledNode: framework)
|
||||
let graph = Graph.test(cache: cache)
|
||||
|
@ -78,7 +79,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
|
||||
func test_linkableDependencies_whenPrecompiled() throws {
|
||||
let target = Target.test(name: "Main")
|
||||
let precompiledNode = FrameworkNode(path: AbsolutePath("/test/test.framework"))
|
||||
let precompiledNode = FrameworkNode.test(path: AbsolutePath("/test/test.framework"))
|
||||
let project = Project.test(targets: [target])
|
||||
let targetNode = TargetNode(project: project,
|
||||
target: target,
|
||||
|
@ -91,7 +92,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
output: "Architectures in the fat file: Alamofire are: x86_64 arm64")
|
||||
|
||||
let got = try graph.linkableDependencies(path: project.path, name: target.name)
|
||||
XCTAssertEqual(got.first, .absolute(precompiledNode.path))
|
||||
XCTAssertEqual(got.first, GraphDependencyReference(precompiledNode: precompiledNode))
|
||||
}
|
||||
|
||||
func test_linkableDependencies_whenALibraryTarget() throws {
|
||||
|
@ -506,8 +507,8 @@ final class GraphTests: TuistUnitTestCase {
|
|||
func test_librariesPublicHeaders() throws {
|
||||
let target = Target.test(name: "Main")
|
||||
let publicHeadersPath = AbsolutePath("/test/public/")
|
||||
let precompiledNode = LibraryNode(path: AbsolutePath("/test/test.a"),
|
||||
publicHeaders: publicHeadersPath)
|
||||
let precompiledNode = LibraryNode.test(path: AbsolutePath("/test/test.a"),
|
||||
publicHeaders: publicHeadersPath)
|
||||
let project = Project.test(targets: [target])
|
||||
let targetNode = TargetNode(project: project,
|
||||
target: target,
|
||||
|
@ -564,7 +565,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
func test_embeddableFrameworks_when_dependencyIsAFramework() throws {
|
||||
let frameworkPath = AbsolutePath("/test/test.framework")
|
||||
let target = Target.test(name: "Main", platform: .iOS)
|
||||
let frameworkNode = FrameworkNode(path: frameworkPath)
|
||||
let frameworkNode = FrameworkNode.test(path: frameworkPath)
|
||||
let project = Project.test(targets: [target])
|
||||
let targetNode = TargetNode(project: project,
|
||||
target: target,
|
||||
|
@ -578,7 +579,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
|
||||
let got = try graph.embeddableFrameworks(path: project.path, name: target.name)
|
||||
|
||||
XCTAssertEqual(got.first, GraphDependencyReference.absolute(frameworkPath))
|
||||
XCTAssertEqual(got.first, GraphDependencyReference(precompiledNode: frameworkNode))
|
||||
}
|
||||
|
||||
func test_embeddableFrameworks_when_dependencyIsATransitiveFramework() throws {
|
||||
|
@ -587,7 +588,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
let project = Project.test(targets: [target])
|
||||
|
||||
let frameworkPath = AbsolutePath("/test/test.framework")
|
||||
let frameworkNode = FrameworkNode(path: frameworkPath)
|
||||
let frameworkNode = FrameworkNode.test(path: frameworkPath)
|
||||
|
||||
let dependencyNode = TargetNode(
|
||||
project: project,
|
||||
|
@ -610,7 +611,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
|
||||
XCTAssertEqual(got, [
|
||||
GraphDependencyReference.product(target: "Dependency", productName: "Dependency.framework"),
|
||||
GraphDependencyReference.absolute(frameworkPath),
|
||||
GraphDependencyReference(precompiledNode: frameworkNode),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -619,7 +620,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
let target = Target.test(name: "Main")
|
||||
let project = Project.test(targets: [target])
|
||||
|
||||
let frameworkNode = FrameworkNode(path: "/test/StaticFramework.framework")
|
||||
let frameworkNode = FrameworkNode.test(path: "/test/StaticFramework.framework", linking: .static)
|
||||
let targetNode = TargetNode(
|
||||
project: project,
|
||||
target: target,
|
||||
|
@ -629,9 +630,6 @@ final class GraphTests: TuistUnitTestCase {
|
|||
cache.add(targetNode: targetNode)
|
||||
let graph = Graph.test(cache: cache)
|
||||
|
||||
system.succeedCommand("/usr/bin/file", "/test/StaticFramework.framework/StaticFramework",
|
||||
output: "current ar archive random library")
|
||||
|
||||
// When
|
||||
let result = try graph.embeddableFrameworks(path: project.path, name: target.name)
|
||||
|
||||
|
@ -769,8 +767,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
func test_librariesSearchPaths() throws {
|
||||
// Given
|
||||
let target = Target.test(name: "Main")
|
||||
let precompiledNode = LibraryNode(path: AbsolutePath("/test/test.a"),
|
||||
publicHeaders: AbsolutePath("/test/public/"))
|
||||
let precompiledNode = LibraryNode.test(path: "/test/test.a", publicHeaders: "/test/public/")
|
||||
let project = Project.test(targets: [target])
|
||||
let targetNode = TargetNode(project: project,
|
||||
target: target,
|
||||
|
@ -790,12 +787,8 @@ final class GraphTests: TuistUnitTestCase {
|
|||
func test_librariesSwiftIncludePaths() throws {
|
||||
// Given
|
||||
let target = Target.test(name: "Main")
|
||||
let precompiledNodeA = LibraryNode(path: AbsolutePath("/test/test.a"),
|
||||
publicHeaders: AbsolutePath("/test/public/"),
|
||||
swiftModuleMap: AbsolutePath("/test/modules/test.swiftmodulemap"))
|
||||
let precompiledNodeB = LibraryNode(path: AbsolutePath("/test/another.a"),
|
||||
publicHeaders: AbsolutePath("/test/public/"),
|
||||
swiftModuleMap: nil)
|
||||
let precompiledNodeA = LibraryNode.test(path: "/test/test.a", swiftModuleMap: "/test/modules/test.swiftmodulemap")
|
||||
let precompiledNodeB = LibraryNode.test(path: "/test/another.a", swiftModuleMap: nil)
|
||||
let project = Project.test(targets: [target])
|
||||
let targetNode = TargetNode(project: project,
|
||||
target: target,
|
||||
|
@ -959,9 +952,9 @@ final class GraphTests: TuistUnitTestCase {
|
|||
System.shared = System()
|
||||
let cache = GraphLoaderCache()
|
||||
let graph = Graph.test(cache: cache)
|
||||
let framework = FrameworkNode(path: fixturePath(path: RelativePath("xpm.framework")))
|
||||
let library = LibraryNode(path: fixturePath(path: RelativePath("libStaticLibrary.a")),
|
||||
publicHeaders: fixturePath(path: RelativePath("")))
|
||||
let framework = FrameworkNode.test(path: fixturePath(path: RelativePath("xpm.framework")), architectures: [.x8664, .arm64])
|
||||
let library = LibraryNode.test(path: fixturePath(path: RelativePath("libStaticLibrary.a")),
|
||||
publicHeaders: fixturePath(path: RelativePath("")))
|
||||
let target = TargetNode.test(dependencies: [framework, library])
|
||||
cache.add(targetNode: target)
|
||||
cache.add(precompiledNode: framework)
|
||||
|
@ -969,36 +962,36 @@ final class GraphTests: TuistUnitTestCase {
|
|||
|
||||
let expected = """
|
||||
[
|
||||
{
|
||||
"product" : "\(target.target.product.rawValue)",
|
||||
"bundle_id" : "\(target.target.bundleId)",
|
||||
"platform" : "\(target.target.platform.rawValue)",
|
||||
"path" : "\(target.path)",
|
||||
"dependencies" : [
|
||||
{
|
||||
"product" : "\(target.target.product.rawValue)",
|
||||
"bundle_id" : "\(target.target.bundleId)",
|
||||
"platform" : "\(target.target.platform.rawValue)",
|
||||
"path" : "\(target.path)",
|
||||
"dependencies" : [
|
||||
"xpm",
|
||||
"libStaticLibrary"
|
||||
],
|
||||
"name" : "Target",
|
||||
"type" : "source"
|
||||
},
|
||||
{
|
||||
"path" : "\(library.path)",
|
||||
"architectures" : [
|
||||
"x86_64"
|
||||
],
|
||||
"product" : "static_library",
|
||||
"name" : "\(library.name)",
|
||||
"type" : "precompiled"
|
||||
},
|
||||
{
|
||||
"path" : "\(framework.path)",
|
||||
"architectures" : [
|
||||
"x86_64",
|
||||
],
|
||||
"name" : "Target",
|
||||
"type" : "source"
|
||||
},
|
||||
{
|
||||
"path" : "\(library.path)",
|
||||
"architectures" : [
|
||||
"arm64"
|
||||
],
|
||||
"product" : "framework",
|
||||
"name" : "\(framework.name)",
|
||||
"type" : "precompiled"
|
||||
],
|
||||
"product" : "static_library",
|
||||
"name" : "\(library.name)",
|
||||
"type" : "precompiled"
|
||||
},
|
||||
{
|
||||
"path" : "\(framework.path)",
|
||||
"architectures" : [
|
||||
"x86_64",
|
||||
"arm64"
|
||||
],
|
||||
"product" : "framework",
|
||||
"name" : "\(framework.name)",
|
||||
"type" : "precompiled"
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
@ -1010,7 +1003,7 @@ final class GraphTests: TuistUnitTestCase {
|
|||
// MARK: - Helpers
|
||||
|
||||
private func mockDynamicFrameworkNode(at path: AbsolutePath) -> FrameworkNode {
|
||||
let precompiledNode = FrameworkNode(path: path)
|
||||
let precompiledNode = FrameworkNode.test()
|
||||
let binaryPath = path.appending(component: path.basenameWithoutExt)
|
||||
system.succeedCommand("/usr/bin/file",
|
||||
binaryPath.pathString,
|
||||
|
|
|
@ -8,16 +8,26 @@ import XCTest
|
|||
|
||||
final class FrameworkNodeTests: TuistUnitTestCase {
|
||||
var subject: FrameworkNode!
|
||||
var path: AbsolutePath!
|
||||
var frameworkPath: AbsolutePath!
|
||||
var dsymPath: AbsolutePath?
|
||||
var bcsymbolmapPaths: [AbsolutePath]!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
path = AbsolutePath("/test.framework")
|
||||
subject = FrameworkNode(path: path)
|
||||
let path = AbsolutePath.root
|
||||
frameworkPath = path.appending(component: "test.framework")
|
||||
dsymPath = path.appending(component: "test.dSYM")
|
||||
bcsymbolmapPaths = [path.appending(component: "test.bcsymbolmap")]
|
||||
subject = FrameworkNode(path: frameworkPath,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: .dynamic)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
path = nil
|
||||
frameworkPath = nil
|
||||
dsymPath = nil
|
||||
bcsymbolmapPaths = nil
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
@ -32,25 +42,49 @@ final class FrameworkNodeTests: TuistUnitTestCase {
|
|||
|
||||
func test_isCarthage() {
|
||||
XCTAssertFalse(subject.isCarthage)
|
||||
subject = FrameworkNode(path: AbsolutePath("/path/Carthage/Build/iOS/A.framework"))
|
||||
subject = FrameworkNode(path: AbsolutePath("/path/Carthage/Build/iOS/A.framework"),
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: .dynamic)
|
||||
XCTAssertTrue(subject.isCarthage)
|
||||
}
|
||||
|
||||
func test_encode() {
|
||||
// Given
|
||||
System.shared = System()
|
||||
let framework = FrameworkNode(path: fixturePath(path: RelativePath("xpm.framework")))
|
||||
let expected = """
|
||||
{
|
||||
"path": "\(framework.path)",
|
||||
"architectures": ["x86_64", "arm64"],
|
||||
"name": "xpm",
|
||||
"path": "\(subject.path)",
|
||||
"architectures": [],
|
||||
"name": "test",
|
||||
"type": "precompiled",
|
||||
"product": "framework"
|
||||
}
|
||||
"""
|
||||
|
||||
// Then
|
||||
XCTAssertEncodableEqualToJson(framework, expected)
|
||||
XCTAssertEncodableEqualToJson(subject, expected)
|
||||
}
|
||||
|
||||
func test_product_when_static() {
|
||||
// Given
|
||||
let subject = FrameworkNode.test(linking: .static)
|
||||
|
||||
// When
|
||||
let got = subject.product
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .staticFramework)
|
||||
}
|
||||
|
||||
func test_product_when_dynamic() {
|
||||
// Given
|
||||
let subject = FrameworkNode.test(linking: .dynamic)
|
||||
|
||||
// When
|
||||
let got = subject.product
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .framework)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ final class GraphNodeTests: XCTestCase {
|
|||
let c2 = TargetNode(project: .test(path: "/path/c"),
|
||||
target: .test(name: "c2"),
|
||||
dependencies: [])
|
||||
let d = LibraryNode(path: "/path/a", publicHeaders: "/path/to/headers")
|
||||
let e = LibraryNode(path: "/path/c", publicHeaders: "/path/to/headers")
|
||||
let d = LibraryNode(path: "/path/a", publicHeaders: "/path/to/headers", architectures: [.arm64], linking: .static)
|
||||
let e = LibraryNode(path: "/path/c", publicHeaders: "/path/to/headers", architectures: [.arm64], linking: .static)
|
||||
|
||||
// When
|
||||
var set = Set<GraphNode>()
|
||||
|
@ -48,7 +48,7 @@ final class GraphNodeTests: XCTestCase {
|
|||
// Given
|
||||
let a = GraphNode(path: "/a", name: "a")
|
||||
let b = TargetNode(project: .test(path: "/a"), target: .test(), dependencies: [])
|
||||
let c = LibraryNode(path: "/a", publicHeaders: "/path/to/headers")
|
||||
let c = LibraryNode(path: "/a", publicHeaders: "/path/to/headers", architectures: [.arm64], linking: .static)
|
||||
|
||||
// When / Then
|
||||
let all = [a, b, c]
|
||||
|
|
|
@ -13,7 +13,7 @@ final class LibraryNodeTests: TuistUnitTestCase {
|
|||
override func setUp() {
|
||||
super.setUp()
|
||||
path = AbsolutePath("/test.a")
|
||||
subject = LibraryNode(path: path, publicHeaders: AbsolutePath("/headers"))
|
||||
subject = LibraryNode(path: path, publicHeaders: AbsolutePath("/headers"), architectures: [.arm64], linking: .static)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -32,9 +32,21 @@ final class LibraryNodeTests: TuistUnitTestCase {
|
|||
|
||||
func test_equality() {
|
||||
// Given
|
||||
let a1 = LibraryNode(path: "/a", publicHeaders: "/a/header", swiftModuleMap: "/a/swiftmodulemap")
|
||||
let a2 = LibraryNode(path: "/a", publicHeaders: "/a/header/2", swiftModuleMap: "/a/swiftmodulemap")
|
||||
let b = LibraryNode(path: "/b", publicHeaders: "/b/header", swiftModuleMap: "/b/swiftmodulemap")
|
||||
let a1 = LibraryNode(path: "/a",
|
||||
publicHeaders: "/a/header",
|
||||
architectures: [.arm64],
|
||||
linking: .static,
|
||||
swiftModuleMap: "/a/swiftmodulemap")
|
||||
let a2 = LibraryNode(path: "/a",
|
||||
publicHeaders: "/a/header/2",
|
||||
architectures: [.arm64],
|
||||
linking: .static,
|
||||
swiftModuleMap: "/a/swiftmodulemap")
|
||||
let b = LibraryNode(path: "/b",
|
||||
publicHeaders: "/b/header",
|
||||
architectures: [.arm64],
|
||||
linking: .static,
|
||||
swiftModuleMap: "/b/swiftmodulemap")
|
||||
|
||||
// When / Then
|
||||
XCTAssertEqual(a1, a1)
|
||||
|
@ -47,13 +59,15 @@ final class LibraryNodeTests: TuistUnitTestCase {
|
|||
// Given
|
||||
System.shared = System()
|
||||
let library = LibraryNode(path: fixturePath(path: RelativePath("libStaticLibrary.a")),
|
||||
publicHeaders: fixturePath(path: RelativePath("")))
|
||||
publicHeaders: fixturePath(path: RelativePath("")),
|
||||
architectures: [.arm64],
|
||||
linking: .static)
|
||||
let expected = """
|
||||
{
|
||||
"type": "precompiled",
|
||||
"path" : "\(library.path)",
|
||||
"path" : "\(library.path.pathString)",
|
||||
"architectures" : [
|
||||
"x86_64"
|
||||
"arm64"
|
||||
],
|
||||
"name" : "\(library.name)",
|
||||
"product" : "static_library"
|
||||
|
@ -63,4 +77,26 @@ final class LibraryNodeTests: TuistUnitTestCase {
|
|||
// Then
|
||||
XCTAssertEncodableEqualToJson(library, expected)
|
||||
}
|
||||
|
||||
func test_product_when_static() {
|
||||
// Given
|
||||
let subject = LibraryNode.test(linking: .static)
|
||||
|
||||
// When
|
||||
let got = subject.product
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .staticLibrary)
|
||||
}
|
||||
|
||||
func test_product_when_dynamic() {
|
||||
// Given
|
||||
let subject = LibraryNode.test(linking: .dynamic)
|
||||
|
||||
// When
|
||||
let got = subject.product
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .dynamicLibrary)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,27 +27,22 @@ final class XCFrameworkNodeTests: TuistUnitTestCase {
|
|||
|
||||
let expected = """
|
||||
{
|
||||
"path" : "\\/MyFramework.xcframework",
|
||||
"libraries" : [
|
||||
{
|
||||
"SupportedArchitectures" : [
|
||||
"x86_64"
|
||||
],
|
||||
"LibraryIdentifier" : "ios-x86_64-simulator",
|
||||
"LibraryPath" : "MyFramework.framework"
|
||||
},
|
||||
{
|
||||
"SupportedArchitectures" : [
|
||||
"arm64"
|
||||
],
|
||||
"LibraryIdentifier" : "ios-arm64",
|
||||
"LibraryPath" : "MyFramework.framework"
|
||||
}
|
||||
],
|
||||
"path" : "/MyFramework/MyFramework.xcframework",
|
||||
"name" : "MyFramework",
|
||||
"type" : "precompiled"
|
||||
"type" : "xcframework",
|
||||
"linking": "dynamic",
|
||||
"info_plist" : {
|
||||
"AvailableLibraries" : [
|
||||
{
|
||||
"SupportedArchitectures" : [
|
||||
"i386"
|
||||
],
|
||||
"LibraryIdentifier" : "test",
|
||||
"LibraryPath" : "relative/to/library"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
// Then
|
||||
|
|
|
@ -6,15 +6,6 @@ import XCTest
|
|||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class BinaryArchitectureTests: TuistTestCase {
|
||||
func test_rawValue() {
|
||||
XCTAssertEqual(BinaryArchitecture.x8664.rawValue, "x86_64")
|
||||
XCTAssertEqual(BinaryArchitecture.i386.rawValue, "i386")
|
||||
XCTAssertEqual(BinaryArchitecture.armv7.rawValue, "armv7")
|
||||
XCTAssertEqual(BinaryArchitecture.armv7s.rawValue, "armv7s")
|
||||
}
|
||||
}
|
||||
|
||||
final class PrecompiledMetadataProviderTests: TuistUnitTestCase {
|
||||
var subject: PrecompiledMetadataProvider!
|
||||
|
|
@ -20,10 +20,10 @@ final class XCFrameworkMetadataProviderTests: XCTestCase {
|
|||
func test_libraries_when_frameworkIsPresent() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyFramework.xcframework"))
|
||||
let libraries = try subject.libraries(frameworkPath: frameworkPath)
|
||||
let infoPlist = try subject.infoPlist(xcframeworkPath: frameworkPath)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(libraries, [
|
||||
XCTAssertEqual(infoPlist.libraries, [
|
||||
.init(identifier: "ios-x86_64-simulator",
|
||||
path: RelativePath("MyFramework.framework"),
|
||||
architectures: [.x8664]),
|
||||
|
@ -36,8 +36,8 @@ final class XCFrameworkMetadataProviderTests: XCTestCase {
|
|||
func test_binaryPath_when_frameworkIsPresent() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyFramework.xcframework"))
|
||||
let libraries = try subject.libraries(frameworkPath: frameworkPath)
|
||||
let binaryPath = try subject.binaryPath(frameworkPath: frameworkPath, libraries: libraries)
|
||||
let infoPlist = try subject.infoPlist(xcframeworkPath: frameworkPath)
|
||||
let binaryPath = try subject.binaryPath(xcframeworkPath: frameworkPath, libraries: infoPlist.libraries)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(
|
||||
|
@ -49,10 +49,10 @@ final class XCFrameworkMetadataProviderTests: XCTestCase {
|
|||
func test_libraries_when_staticLibraryIsPresent() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyStaticLibrary.xcframework"))
|
||||
let libraries = try subject.libraries(frameworkPath: frameworkPath)
|
||||
let infoPlist = try subject.infoPlist(xcframeworkPath: frameworkPath)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(libraries, [
|
||||
XCTAssertEqual(infoPlist.libraries, [
|
||||
.init(identifier: "ios-x86_64-simulator",
|
||||
path: RelativePath("libMyStaticLibrary.a"),
|
||||
architectures: [.x8664]),
|
||||
|
@ -65,8 +65,8 @@ final class XCFrameworkMetadataProviderTests: XCTestCase {
|
|||
func test_binaryPath_when_staticLibraryIsPresent() throws {
|
||||
// Given
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyStaticLibrary.xcframework"))
|
||||
let libraries = try subject.libraries(frameworkPath: frameworkPath)
|
||||
let binaryPath = try subject.binaryPath(frameworkPath: frameworkPath, libraries: libraries)
|
||||
let infoPlist = try subject.infoPlist(xcframeworkPath: frameworkPath)
|
||||
let binaryPath = try subject.binaryPath(xcframeworkPath: frameworkPath, libraries: infoPlist.libraries)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(
|
|
@ -0,0 +1,16 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class BinaryArchitectureTests: TuistTestCase {
|
||||
func test_rawValue() {
|
||||
XCTAssertEqual(BinaryArchitecture.x8664.rawValue, "x86_64")
|
||||
XCTAssertEqual(BinaryArchitecture.i386.rawValue, "i386")
|
||||
XCTAssertEqual(BinaryArchitecture.armv7.rawValue, "armv7")
|
||||
XCTAssertEqual(BinaryArchitecture.armv7s.rawValue, "armv7s")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import Basic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class FrameworkNodeLoaderErrorTests: TuistUnitTestCase {
|
||||
func test_type_when_frameworkNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/frameworks/tuist.framework")
|
||||
let subject = FrameworkNodeLoaderError.frameworkNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.type
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .abort)
|
||||
}
|
||||
|
||||
func test_description_when_frameworkNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/frameworks/tuist.framework")
|
||||
let subject = FrameworkNodeLoaderError.frameworkNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.description
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "Couldn't find framework at \(path.pathString)")
|
||||
}
|
||||
}
|
||||
|
||||
final class FrameworkNodeLoaderTests: TuistUnitTestCase {
|
||||
var frameworkMetadataProvider: MockFrameworkMetadataProvider!
|
||||
var subject: FrameworkNodeLoader!
|
||||
|
||||
override func setUp() {
|
||||
frameworkMetadataProvider = MockFrameworkMetadataProvider()
|
||||
subject = FrameworkNodeLoader(frameworkMetadataProvider: frameworkMetadataProvider)
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
frameworkMetadataProvider = nil
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_load_when_the_framework_doesnt_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let frameworkPath = path.appending(component: "tuist.framework")
|
||||
|
||||
// Then
|
||||
XCTAssertThrowsSpecific(try subject.load(path: frameworkPath), FrameworkNodeLoaderError.frameworkNotFound(frameworkPath))
|
||||
}
|
||||
|
||||
func test_oad_when_the_framework_exists() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let frameworkPath = path.appending(component: "tuist.framework")
|
||||
let dsymPath = path.appending(component: "tuist.dSYM")
|
||||
let bcsymbolmapPaths = [path.appending(component: "tuist.bcsymbolmap")]
|
||||
let architectures = [BinaryArchitecture.armv7s]
|
||||
let linking = BinaryLinking.dynamic
|
||||
|
||||
try FileHandler.shared.touch(frameworkPath)
|
||||
|
||||
frameworkMetadataProvider.dsymPathStub = { path in
|
||||
XCTAssertEqual(path, frameworkPath)
|
||||
return dsymPath
|
||||
}
|
||||
frameworkMetadataProvider.bcsymbolmapPathsStub = { path in
|
||||
XCTAssertEqual(path, frameworkPath)
|
||||
return bcsymbolmapPaths
|
||||
}
|
||||
frameworkMetadataProvider.linkingStub = { path in
|
||||
XCTAssertEqual(path, FrameworkNode.binaryPath(frameworkPath: frameworkPath))
|
||||
return linking
|
||||
}
|
||||
frameworkMetadataProvider.architecturesStub = { path in
|
||||
XCTAssertEqual(path, FrameworkNode.binaryPath(frameworkPath: frameworkPath))
|
||||
return architectures
|
||||
}
|
||||
|
||||
// When
|
||||
let got = try subject.load(path: frameworkPath)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, FrameworkNode(path: frameworkPath,
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: bcsymbolmapPaths,
|
||||
linking: linking,
|
||||
architectures: architectures,
|
||||
dependencies: []))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
import Basic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class LibraryNodeLoaderErrorTests: TuistUnitTestCase {
|
||||
func test_type_when_libraryNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.libraryNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.type
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .abort)
|
||||
}
|
||||
|
||||
func test_description_when_libraryNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.libraryNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.description
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "The library \(path.pathString) does not exist")
|
||||
}
|
||||
|
||||
func test_type_when_publicHeadersNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.publicHeadersNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.type
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .abort)
|
||||
}
|
||||
|
||||
func test_description_when_publicHeadersNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.publicHeadersNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.description
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "The public headers directory \(path.pathString) does not exist")
|
||||
}
|
||||
|
||||
func test_type_when_swiftModuleMapNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.swiftModuleMapNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.type
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, .abort)
|
||||
}
|
||||
|
||||
func test_description_when_swiftModuleMapNotFound() {
|
||||
// Given
|
||||
let path = AbsolutePath("/libraries/libTuist.a")
|
||||
let subject = LibraryNodeLoaderError.swiftModuleMapNotFound(path)
|
||||
|
||||
// When
|
||||
let got = subject.description
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, "The Swift modulemap file \(path.pathString) does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
final class LibraryNodeLoaderTests: TuistUnitTestCase {
|
||||
var libraryMetadataProvider: MockLibraryMetadataProvider!
|
||||
var subject: LibraryNodeLoader!
|
||||
|
||||
override func setUp() {
|
||||
libraryMetadataProvider = MockLibraryMetadataProvider()
|
||||
subject = LibraryNodeLoader(libraryMetadataProvider: libraryMetadataProvider)
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
libraryMetadataProvider = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_load_when_the_path_doesnt_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let libraryPath = path.appending(component: "libTuist.a")
|
||||
let publicHeadersPath = path.appending(component: "headers")
|
||||
|
||||
try FileHandler.shared.createFolder(publicHeadersPath)
|
||||
|
||||
// Then
|
||||
XCTAssertThrowsSpecific(try subject.load(path: libraryPath,
|
||||
publicHeaders: publicHeadersPath,
|
||||
swiftModuleMap: nil), LibraryNodeLoaderError.libraryNotFound(libraryPath))
|
||||
}
|
||||
|
||||
func test_load_when_the_public_headers_directory_doesnt_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let libraryPath = path.appending(component: "libTuist.a")
|
||||
let publicHeadersPath = path.appending(component: "headers")
|
||||
|
||||
try FileHandler.shared.touch(libraryPath)
|
||||
|
||||
// Then
|
||||
XCTAssertThrowsSpecific(try subject.load(path: libraryPath,
|
||||
publicHeaders: publicHeadersPath,
|
||||
swiftModuleMap: nil), LibraryNodeLoaderError.publicHeadersNotFound(publicHeadersPath))
|
||||
}
|
||||
|
||||
func test_load_when_the_swift_modulemap_doesnt_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let libraryPath = path.appending(component: "libTuist.a")
|
||||
let publicHeadersPath = path.appending(component: "headers")
|
||||
let swiftModulemapPath = path.appending(component: "tuist.modulemap")
|
||||
try FileHandler.shared.createFolder(publicHeadersPath)
|
||||
try FileHandler.shared.touch(libraryPath)
|
||||
|
||||
// Then
|
||||
XCTAssertThrowsSpecific(try subject.load(path: libraryPath,
|
||||
publicHeaders: publicHeadersPath,
|
||||
swiftModuleMap: swiftModulemapPath), LibraryNodeLoaderError.swiftModuleMapNotFound(swiftModulemapPath))
|
||||
}
|
||||
|
||||
func test_load_when_all_files_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let libraryPath = path.appending(component: "libTuist.a")
|
||||
let publicHeadersPath = path.appending(component: "headers")
|
||||
let swiftModulemapPath = path.appending(component: "tuist.modulemap")
|
||||
let architectures: [BinaryArchitecture] = [.armv7, .armv7s]
|
||||
let linking: BinaryLinking = .dynamic
|
||||
|
||||
try FileHandler.shared.createFolder(publicHeadersPath)
|
||||
try FileHandler.shared.touch(libraryPath)
|
||||
try FileHandler.shared.touch(swiftModulemapPath)
|
||||
|
||||
libraryMetadataProvider.architecturesStub = { path in
|
||||
XCTAssertEqual(path, libraryPath)
|
||||
return architectures
|
||||
}
|
||||
libraryMetadataProvider.linkingStub = { path in
|
||||
XCTAssertEqual(path, libraryPath)
|
||||
return linking
|
||||
}
|
||||
|
||||
// When
|
||||
let got = try subject.load(path: libraryPath,
|
||||
publicHeaders: publicHeadersPath,
|
||||
swiftModuleMap: swiftModulemapPath)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, LibraryNode(path: libraryPath,
|
||||
publicHeaders: publicHeadersPath,
|
||||
architectures: architectures,
|
||||
linking: linking,
|
||||
swiftModuleMap: swiftModulemapPath))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import Basic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class XCFrameworkNodeLoaderErrorTests: TuistUnitTestCase {
|
||||
func test_type_when_xcframeworkNotFound() {
|
||||
// Given
|
||||
let subject = XCFrameworkNodeLoaderError.xcframeworkNotFound("/frameworks/tuist.xcframework")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subject.type, .abort)
|
||||
}
|
||||
|
||||
func test_description_when_xcframeworkNotFound() {
|
||||
// Given
|
||||
let subject = XCFrameworkNodeLoaderError.xcframeworkNotFound("/frameworks/tuist.xcframework")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subject.description, "Couldn't find xcframework at /frameworks/tuist.xcframework")
|
||||
}
|
||||
}
|
||||
|
||||
final class XCFrameworkNodeLoaderTests: TuistUnitTestCase {
|
||||
var xcframeworkMetadataProvider: MockXCFrameworkMetadataProvider!
|
||||
var subject: XCFrameworkNodeLoader!
|
||||
|
||||
override func setUp() {
|
||||
xcframeworkMetadataProvider = MockXCFrameworkMetadataProvider()
|
||||
subject = XCFrameworkNodeLoader(xcframeworkMetadataProvider: xcframeworkMetadataProvider)
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
xcframeworkMetadataProvider = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_load_throws_when_the_xcframework_doesnt_exist() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let xcframeworkPath = path.appending(component: "tuist.xcframework")
|
||||
|
||||
// Then
|
||||
XCTAssertThrowsSpecific(try subject.load(path: xcframeworkPath), XCFrameworkNodeLoaderError.xcframeworkNotFound(xcframeworkPath))
|
||||
}
|
||||
|
||||
func test_load_when_the_xcframework_exists() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let xcframeworkPath = path.appending(component: "tuist.xcframework")
|
||||
let binaryPath = path.appending(RelativePath("tuist.xcframework/whatever/tuist"))
|
||||
let linking: BinaryLinking = .dynamic
|
||||
|
||||
let infoPlist = XCFrameworkInfoPlist.test()
|
||||
try FileHandler.shared.touch(xcframeworkPath)
|
||||
|
||||
xcframeworkMetadataProvider.infoPlistStub = { path in
|
||||
XCTAssertEqual(xcframeworkPath, path)
|
||||
return infoPlist
|
||||
}
|
||||
xcframeworkMetadataProvider.binaryPathStub = { path, libraries in
|
||||
XCTAssertEqual(xcframeworkPath, path)
|
||||
XCTAssertEqual(libraries, infoPlist.libraries)
|
||||
return binaryPath
|
||||
}
|
||||
xcframeworkMetadataProvider.linkingStub = { path in
|
||||
XCTAssertEqual(binaryPath, path)
|
||||
return linking
|
||||
}
|
||||
|
||||
// When
|
||||
let got = try subject.load(path: xcframeworkPath)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got, XCFrameworkNode(path: xcframeworkPath,
|
||||
infoPlist: infoPlist,
|
||||
primaryBinaryPath: binaryPath,
|
||||
linking: linking,
|
||||
dependencies: []))
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import Basic
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class XCFrameworkParserTests: TuistUnitTestCase {
|
||||
func test_parsing() {
|
||||
let cache = GraphLoaderCache()
|
||||
let frameworkPath = fixturePath(path: RelativePath("MyFramework.xcframework"))
|
||||
let xcFramework = try! XCFrameworkParser.parse(path: frameworkPath, cache: cache)
|
||||
|
||||
let architectures = xcFramework.libraries.flatMap { $0.architectures }
|
||||
XCTAssertEqual([.x8664, .arm64], architectures)
|
||||
|
||||
XCTAssertEqual(xcFramework.binaryPath, frameworkPath.appending(RelativePath("ios-x86_64-simulator/MyFramework.framework/MyFramework")))
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ final class BuildCopierTests: XCTestCase {
|
|||
XCTAssertEqual(toPath.glob("*").count, BuildCopier.files.count)
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: toPath.appending(component: "test").pathString))
|
||||
}
|
||||
|
||||
|
||||
func test_copy_without_templates() throws {
|
||||
let fromDir = try TemporaryDirectory(removeTreeOnDeinit: true)
|
||||
let fromPath = fromDir.path
|
||||
|
|
|
@ -159,7 +159,7 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
"-Xswiftc", "-emit-module-interface",
|
||||
"-Xswiftc", "-emit-module-interface-path",
|
||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString)
|
||||
|
||||
|
||||
try FileHandler.shared.createFolder(temporaryDirectory.path.appending(component: Constants.templatesDirectoryName))
|
||||
try FileHandler.shared.createFolder(temporaryDirectory.path.appending(RelativePath(".build/release")))
|
||||
|
||||
|
@ -205,7 +205,7 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
"-Xswiftc", "-emit-module-interface",
|
||||
"-Xswiftc", "-emit-module-interface-path",
|
||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString)
|
||||
|
||||
|
||||
try FileHandler.shared.createFolder(temporaryDirectory.path.appending(component: Constants.templatesDirectoryName))
|
||||
try FileHandler.shared.createFolder(temporaryDirectory.path.appending(RelativePath(".build/release")))
|
||||
|
||||
|
@ -220,7 +220,7 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
let tuistVersionPath = installationDirectory.appending(component: Constants.versionFileName)
|
||||
XCTAssertTrue(FileHandler.shared.exists(tuistVersionPath))
|
||||
}
|
||||
|
||||
|
||||
func test_install_when_no_bundled_release_and_no_templates() throws {
|
||||
let version = "3.2.1"
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
|
@ -252,7 +252,7 @@ final class InstallerTests: TuistUnitTestCase {
|
|||
"-Xswiftc", "-emit-module-interface",
|
||||
"-Xswiftc", "-emit-module-interface-path",
|
||||
"-Xswiftc", temporaryDirectory.path.appending(RelativePath(".build/release/ProjectDescription.swiftinterface")).pathString)
|
||||
|
||||
|
||||
try FileHandler.shared.createFolder(temporaryDirectory.path.appending(RelativePath(".build/release")))
|
||||
|
||||
try subject.install(version: version, temporaryDirectory: temporaryDirectory)
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistCoreTesting
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
import XCTest
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class WorkspaceGeneratorIntegrationTests: TuistTestCase {
|
||||
var subject: WorkspaceGenerator!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = WorkspaceGenerator(config: .init(projectGenerationContext: .concurrent))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
subject = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
|
||||
func test_generate_stressTest() throws {
|
||||
// Given
|
||||
let temporaryPath = try self.temporaryPath()
|
||||
let projects = (0 ..< 20).map {
|
||||
Project.test(path: temporaryPath.appending(component: "Project\($0)"),
|
||||
name: "Test",
|
||||
settings: .default,
|
||||
targets: [Target.test(name: "Project\($0)_Target")])
|
||||
}
|
||||
let graph = Graph.create(projects: projects,
|
||||
dependencies: projects.flatMap { project in
|
||||
project.targets.map { target in
|
||||
(project: project, target: target, dependencies: [])
|
||||
}
|
||||
})
|
||||
let workspace = Workspace.test(path: temporaryPath,
|
||||
projects: projects.map(\.path))
|
||||
|
||||
// When / Then
|
||||
try (0 ..< 50).forEach { _ in
|
||||
_ = try subject.generate(workspace: workspace,
|
||||
path: temporaryPath,
|
||||
graph: graph)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import XCTest
|
|||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class XcodeProjWriterTests: TuistUnitTestCase {
|
||||
final class XcodeProjWriterTests: TuistTestCase {
|
||||
private var subject: XcodeProjWriter!
|
||||
|
||||
override func setUp() {
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class EmbedScriptGeneratorIntegrationTests: TuistTestCase {
|
||||
var subject: EmbedScriptGenerator!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = EmbedScriptGenerator()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_script() throws {
|
||||
// Given
|
||||
let carthagePath = try temporaryFixture("Carthage/")
|
||||
let frameworkPath = FileHandler.shared.glob(carthagePath, glob: "*.framework").first!
|
||||
let framework = FrameworkNode(path: frameworkPath)
|
||||
|
||||
// When
|
||||
let got = try subject.script(sourceRootPath: carthagePath, frameworkPaths: [framework.path])
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(got.inputPaths.contains(RelativePath("2510FE01-4D40-3956-BB71-857D3B2D9E73.bcsymbolmap")))
|
||||
XCTAssertTrue(got.inputPaths.contains(RelativePath("773847A9-0D05-35AF-9865-94A9A670080B.bcsymbolmap")))
|
||||
XCTAssertTrue(got.inputPaths.contains(RelativePath("RxBlocking.framework")))
|
||||
XCTAssertTrue(got.inputPaths.contains(RelativePath("RxBlocking.framework.dSYM")))
|
||||
|
||||
XCTAssertTrue(got.outputPaths.contains("${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework"))
|
||||
XCTAssertTrue(got.outputPaths.contains("${DWARF_DSYM_FOLDER_PATH}/RxBlocking.framework.dSYM"))
|
||||
XCTAssertTrue(got.outputPaths.contains("${BUILT_PRODUCTS_DIR}/2510FE01-4D40-3956-BB71-857D3B2D9E73.bcsymbolmap"))
|
||||
XCTAssertTrue(got.outputPaths.contains("${BUILT_PRODUCTS_DIR}/773847A9-0D05-35AF-9865-94A9A670080B.bcsymbolmap"))
|
||||
|
||||
XCTAssertTrue(got.script.contains("install_framework \"RxBlocking.framework\""))
|
||||
XCTAssertTrue(got.script.contains("install_dsym \"RxBlocking.framework.dSYM\""))
|
||||
XCTAssertTrue(got.script.contains("install_bcsymbolmap \"2510FE01-4D40-3956-BB71-857D3B2D9E73.bcsymbolmap\""))
|
||||
XCTAssertTrue(got.script.contains("install_bcsymbolmap \"773847A9-0D05-35AF-9865-94A9A670080B.bcsymbolmap\""))
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
func test_generateEmbedPhase() throws {
|
||||
// Given
|
||||
var dependencies: [GraphDependencyReference] = []
|
||||
dependencies.append(GraphDependencyReference.absolute(AbsolutePath("/test.framework")))
|
||||
dependencies.append(GraphDependencyReference.testFramework())
|
||||
dependencies.append(GraphDependencyReference.product(target: "Test", productName: "Test.framework"))
|
||||
let pbxproj = PBXProj()
|
||||
let pbxTarget = PBXNativeTarget(name: "Test")
|
||||
|
@ -92,7 +92,7 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
func test_generateEmbedPhase_setupEmbedFrameworksBuildPhase_whenXCFrameworkIsPresent() throws {
|
||||
// Given
|
||||
var dependencies: [GraphDependencyReference] = []
|
||||
dependencies.append(GraphDependencyReference.absolute(AbsolutePath("/Frameworks/Test.xcframework")))
|
||||
dependencies.append(GraphDependencyReference.testXCFramework(path: "/Frameworks/Test.xcframework"))
|
||||
let pbxproj = PBXProj()
|
||||
let pbxTarget = PBXNativeTarget(name: "Test")
|
||||
let sourceRootPath = AbsolutePath("/")
|
||||
|
@ -122,9 +122,9 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
|
||||
func test_setupFrameworkSearchPath() throws {
|
||||
let dependencies = [
|
||||
GraphDependencyReference.absolute(AbsolutePath("/Dependencies/A.framework")),
|
||||
GraphDependencyReference.absolute(AbsolutePath("/Dependencies/B.framework")),
|
||||
GraphDependencyReference.absolute(AbsolutePath("/Dependencies/C/C.framework")),
|
||||
GraphDependencyReference.testFramework(path: "/Dependencies/A.framework"),
|
||||
GraphDependencyReference.testFramework(path: "/Dependencies/B.framework"),
|
||||
GraphDependencyReference.testFramework(path: "/Dependencies/C/C.framework"),
|
||||
]
|
||||
let sourceRootPath = AbsolutePath("/")
|
||||
|
||||
|
@ -312,7 +312,7 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
|
||||
func test_generateLinkingPhase() throws {
|
||||
var dependencies: [GraphDependencyReference] = []
|
||||
dependencies.append(GraphDependencyReference.absolute(AbsolutePath("/test.framework")))
|
||||
dependencies.append(GraphDependencyReference.testFramework(path: "/test.framework"))
|
||||
dependencies.append(GraphDependencyReference.product(target: "Test", productName: "Test.framework"))
|
||||
let pbxproj = PBXProj()
|
||||
let pbxTarget = PBXNativeTarget(name: "Test")
|
||||
|
@ -340,7 +340,7 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
|
||||
func test_generateLinkingPhase_throws_whenFileReferenceIsMissing() throws {
|
||||
var dependencies: [GraphDependencyReference] = []
|
||||
dependencies.append(GraphDependencyReference.absolute(AbsolutePath("/test.framework")))
|
||||
dependencies.append(GraphDependencyReference.testFramework(path: "/test.framework"))
|
||||
let pbxproj = PBXProj()
|
||||
let pbxTarget = PBXNativeTarget(name: "Test")
|
||||
let fileElements = ProjectFileElements()
|
||||
|
@ -371,8 +371,8 @@ final class LinkGeneratorErrorTests: XCTestCase {
|
|||
func test_generateLinkingPhase_sdkNodes() throws {
|
||||
// Given
|
||||
let dependencies: [GraphDependencyReference] = [
|
||||
.sdk("/Strong/Foo.framework", .required),
|
||||
.sdk("/Weak/Bar.framework", .optional),
|
||||
.sdk(path: "/Strong/Foo.framework", status: .required),
|
||||
.sdk(path: "/Weak/Bar.framework", status: .optional),
|
||||
]
|
||||
let pbxproj = PBXProj()
|
||||
let pbxTarget = PBXNativeTarget(name: "Test")
|
||||
|
|
|
@ -406,7 +406,7 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
xcodeprojPath: project.path.appending(component: "\(project.fileName).xcodeproj"),
|
||||
sourceRootPath: sourceRootPath)
|
||||
var dependencies: Set<GraphDependencyReference> = Set()
|
||||
let precompiledNode = GraphDependencyReference.absolute(project.path.appending(component: "waka.framework"))
|
||||
let precompiledNode = GraphDependencyReference.testFramework(path: project.path.appending(component: "waka.framework"))
|
||||
dependencies.insert(precompiledNode)
|
||||
|
||||
try subject.generate(dependencyReferences: dependencies,
|
||||
|
@ -610,7 +610,7 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
let sdk = try SDKNode(name: "ARKit.framework",
|
||||
platform: .iOS,
|
||||
status: .required)
|
||||
let sdkDependency = GraphDependencyReference.sdk(sdk.path, sdk.status)
|
||||
let sdkDependency = GraphDependencyReference.sdk(path: sdk.path, status: sdk.status)
|
||||
|
||||
// When
|
||||
try subject.generate(dependencyReferences: [sdkDependency],
|
||||
|
|
|
@ -13,7 +13,7 @@ final class WorkspaceGeneratorTests: TuistUnitTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = WorkspaceGenerator()
|
||||
subject = WorkspaceGenerator(config: .init(projectGenerationContext: .serial))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
|
|
@ -32,8 +32,8 @@ final class GraphLinterTests: TuistUnitTestCase {
|
|||
|
||||
try FileHandler.shared.createFolder(frameworkAPath)
|
||||
|
||||
let frameworkA = FrameworkNode(path: frameworkAPath)
|
||||
let frameworkB = FrameworkNode(path: frameworkBPath)
|
||||
let frameworkA = FrameworkNode.test(path: frameworkAPath)
|
||||
let frameworkB = FrameworkNode.test(path: frameworkBPath)
|
||||
|
||||
cache.add(precompiledNode: frameworkA)
|
||||
cache.add(precompiledNode: frameworkB)
|
||||
|
@ -130,8 +130,8 @@ final class GraphLinterTests: TuistUnitTestCase {
|
|||
|
||||
try FileHandler.shared.createFolder(frameworkAPath)
|
||||
|
||||
let frameworkA = FrameworkNode(path: frameworkAPath)
|
||||
let frameworkB = FrameworkNode(path: frameworkBPath)
|
||||
let frameworkA = FrameworkNode.test(path: frameworkAPath)
|
||||
let frameworkB = FrameworkNode.test(path: frameworkBPath)
|
||||
|
||||
cache.add(precompiledNode: frameworkA)
|
||||
cache.add(precompiledNode: frameworkB)
|
||||
|
|
|
@ -45,7 +45,7 @@ class StaticProductsGraphLinterTests: XCTestCase {
|
|||
let framework = Target.test(name: "Framework", product: .framework)
|
||||
let project = Project.test(targets: [app, framework])
|
||||
|
||||
let libraryNode = LibraryNode(path: "/path/to/library", publicHeaders: "/path/to/library/include")
|
||||
let libraryNode = LibraryNode.test(path: "/path/to/library", publicHeaders: "/path/to/library/include")
|
||||
let frameworkNode = TargetNode(project: project, target: framework, dependencies: [libraryNode])
|
||||
let appNode = TargetNode(project: project, target: app, dependencies: [libraryNode, frameworkNode])
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class EmbedScriptGeneratorTests: TuistUnitTestCase {
|
||||
var subject: EmbedScriptGenerator!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = EmbedScriptGenerator()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_script() throws {
|
||||
// Given
|
||||
let path = AbsolutePath("/frameworks/tuist.framework")
|
||||
let dsymPath = AbsolutePath("/frameworks/tuist.dSYM")
|
||||
let bcsymbolPath = AbsolutePath("/frameworks/tuist.bcsymbolmap")
|
||||
let framework = GraphDependencyReference.testFramework(path: path,
|
||||
binaryPath: path.appending(component: "tuist"),
|
||||
dsymPath: dsymPath,
|
||||
bcsymbolmapPaths: [bcsymbolPath])
|
||||
// When
|
||||
let got = try subject.script(sourceRootPath: framework.path!.parentDirectory, frameworkReferences: [framework])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(got.inputPaths, [
|
||||
RelativePath(path.basename),
|
||||
RelativePath(dsymPath.basename),
|
||||
RelativePath(bcsymbolPath.basename),
|
||||
])
|
||||
XCTAssertEqual(got.outputPaths, [
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/\(path.basename)",
|
||||
"${DWARF_DSYM_FOLDER_PATH}/tuist.dSYM", "${BUILT_PRODUCTS_DIR}/\(bcsymbolPath.basename)",
|
||||
])
|
||||
|
||||
XCTAssertTrue(got.script.contains("install_framework \"\(path.basename)\""))
|
||||
XCTAssertTrue(got.script.contains("install_dsym \"\(dsymPath.basename)\""))
|
||||
XCTAssertTrue(got.script.contains("install_bcsymbolmap \"\(bcsymbolPath.basename)\""))
|
||||
}
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class MockEmbedScriptGenerator: EmbedScriptGenerating {
|
||||
var scriptArgs: [(AbsolutePath, [AbsolutePath])] = []
|
||||
var scriptArgs: [(AbsolutePath, [GraphDependencyReference])] = []
|
||||
var scriptStub: Result<EmbedScript, Error>?
|
||||
|
||||
func script(sourceRootPath: AbsolutePath,
|
||||
frameworkPaths: [AbsolutePath]) throws -> EmbedScript {
|
||||
scriptArgs.append((sourceRootPath, frameworkPaths))
|
||||
frameworkReferences: [GraphDependencyReference]) throws -> EmbedScript {
|
||||
scriptArgs.append((sourceRootPath, frameworkReferences))
|
||||
if let scriptStub = scriptStub {
|
||||
switch scriptStub {
|
||||
case let .failure(error): throw error
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Basic
|
||||
import TuistCore
|
||||
import TuistCoreTesting
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
import XCTest
|
||||
|
@ -8,7 +9,7 @@ import XCTest
|
|||
@testable import TuistSupport
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class StableXcodeProjIntegrationTests: TuistUnitTestCase {
|
||||
final class StableXcodeProjIntegrationTests: TuistTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
|
@ -59,7 +60,14 @@ final class StableXcodeProjIntegrationTests: TuistUnitTestCase {
|
|||
let subject = DescriptorGenerator()
|
||||
let writer = XcodeProjWriter()
|
||||
let linter = GraphLinter()
|
||||
let graphLoader = GraphLoader(modelLoader: try createModelLoader())
|
||||
let frameworkNodeLoader = MockFrameworkNodeLoader()
|
||||
let libraryNodeLoader = MockLibraryNodeLoader()
|
||||
let xcframeworkNodeLoader = MockXCFrameworkNodeLoader()
|
||||
|
||||
let graphLoader = GraphLoader(modelLoader: try createModelLoader(),
|
||||
frameworkNodeLoader: frameworkNodeLoader,
|
||||
xcframeworkNodeLoader: xcframeworkNodeLoader,
|
||||
libraryNodeLoader: libraryNodeLoader)
|
||||
|
||||
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
|
||||
try linter.lint(graph: graph).printAndThrowIfNeeded()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import SPMUtility
|
||||
import TuistSupport
|
||||
import TuistScaffold
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistKit
|
||||
@testable import TuistSupportTesting
|
||||
@testable import TuistScaffoldTesting
|
||||
@testable import TuistLoaderTesting
|
||||
@testable import TuistScaffoldTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class InitCommandTests: TuistUnitTestCase {
|
||||
var subject: InitCommand!
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import TuistSupport
|
||||
|
||||
final class ArrayExecutionContextTests: XCTestCase {
|
||||
func test_concurrentMap_success() {
|
||||
// Given
|
||||
let numbers = Array(0 ... 1000)
|
||||
let transform: (Int) -> String = { "Number \($0)" }
|
||||
|
||||
// When
|
||||
let results = numbers.map(context: .concurrent, transform)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(results, numbers.map(transform))
|
||||
}
|
||||
|
||||
func test_concurrentMap_errors() throws {
|
||||
// Given
|
||||
let numbers = Array(0 ... 1000)
|
||||
let transform: (Int) throws -> String = {
|
||||
guard $0 % 100 == 0 else {
|
||||
throw TestError.someError
|
||||
}
|
||||
return "Number \($0)"
|
||||
}
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(try numbers.map(context: .concurrent, transform), TestError.someError)
|
||||
}
|
||||
|
||||
func test_concurrentForEach_success() {
|
||||
// Given
|
||||
let numbers = Array(0 ... 1000)
|
||||
var performedNumbers = Set<Int>()
|
||||
let queue = DispatchQueue(label: "TestQueue")
|
||||
let perform: (Int) -> Void = { number in
|
||||
queue.async {
|
||||
performedNumbers.insert(number)
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
numbers.forEach(context: .concurrent, perform)
|
||||
|
||||
// Then
|
||||
let resuls = queue.sync {
|
||||
performedNumbers
|
||||
}
|
||||
XCTAssertEqual(resuls, Set(numbers))
|
||||
}
|
||||
|
||||
func test_concurrentForEach_error() {
|
||||
// Given
|
||||
let numbers = Array(0 ... 1000)
|
||||
let perform: (Int) throws -> Void = {
|
||||
guard $0 % 100 == 0 else {
|
||||
throw TestError.someError
|
||||
}
|
||||
}
|
||||
|
||||
// When / Then
|
||||
XCTAssertThrowsSpecific(try numbers.forEach(context: .concurrent, perform), TestError.someError)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private enum TestError: Error {
|
||||
case someError
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ module.exports = {
|
|||
title: title,
|
||||
description: `Tuist is a tool that helps developers manage large Xcode projects by leveraging project generation. Moreover, it provides some tools to automate most common tasks, allowing developers to focus on building apps.`,
|
||||
siteUrl: siteUrl,
|
||||
discourseUrl: "https://community.tuist.io",
|
||||
githubUrl: 'https://github.com/tuist',
|
||||
releasesUrl: 'https://github.com/tuist/tuist/releases',
|
||||
documentationUrl: 'https://docs.tuist.io/',
|
||||
|
@ -26,6 +27,12 @@ module.exports = {
|
|||
`gatsby-plugin-theme-ui`,
|
||||
`gatsby-transformer-yaml`,
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: `gatsby-plugin-google-analytics`,
|
||||
options: {
|
||||
trackingId: "UA-125584790-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
name: 'data',
|
||||
|
@ -167,6 +174,7 @@ module.exports = {
|
|||
remarkPlugins: [remarkSlug],
|
||||
gatsbyRemarkPlugins: [
|
||||
`gatsby-remark-smartypants`,
|
||||
`gatsby-remark-copy-linked-files`,
|
||||
{
|
||||
resolve: `gatsby-remark-images`,
|
||||
options: {
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/** @jsx jsx */
|
||||
import { jsx, Styled, useThemeUI, useColorMode } from 'theme-ui'
|
||||
import { useResponsiveValue } from '@theme-ui/match-media'
|
||||
import { Graphviz } from 'graphviz-react';
|
||||
|
||||
const useTextColor = () => {
|
||||
const { theme } = useThemeUI()
|
||||
const [colorMode, _] = useColorMode()
|
||||
|
||||
if (colorMode === 'light' || colorMode === 'default') {
|
||||
return theme.colors.text
|
||||
} else {
|
||||
return theme.colors.modes[colorMode].text
|
||||
}
|
||||
}
|
||||
|
||||
const useBackgroundColor = () => {
|
||||
const { theme } = useThemeUI()
|
||||
const [colorMode, _] = useColorMode()
|
||||
if (colorMode === 'light' || colorMode === 'default') {
|
||||
return theme.colors.background
|
||||
} else {
|
||||
return theme.colors.modes[colorMode].background
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const CrossPlatform = () => {
|
||||
const textColor = useTextColor()
|
||||
const backgroundColor = useBackgroundColor()
|
||||
const width = useResponsiveValue(["100%", "600"])
|
||||
|
||||
return <Graphviz options={{ width: width }} dot={`digraph {
|
||||
graph [bgcolor="${backgroundColor}"];
|
||||
µSearchiOS [color="${textColor}", fontcolor="${textColor}"];
|
||||
µSearchmacOS [color="${textColor}", fontcolor="${textColor}"];
|
||||
µSearchwatchOS [color="${textColor}", fontcolor="${textColor}"];
|
||||
µSearch [color="${textColor}", fontcolor="${textColor}"];
|
||||
µSearchiOS -> µSearch [color="${textColor}"];
|
||||
µSearchmacOS -> µSearch [color="${textColor}"];
|
||||
µSearchwatchOS -> µSearch [color="${textColor}"];
|
||||
}`} />
|
||||
}
|
||||
|
||||
const MicroFeature = () => {
|
||||
const textColor = useTextColor()
|
||||
const backgroundColor = useBackgroundColor()
|
||||
const width = useResponsiveValue(["100%", "300"])
|
||||
|
||||
return <div sx={{ py: 2 }}>
|
||||
<Graphviz options={{ width: width }} dot={`digraph {
|
||||
graph [bgcolor="${backgroundColor}"];
|
||||
Example [color="${textColor}", fontcolor="${textColor}"];
|
||||
Source [color="${textColor}", fontcolor="${textColor}"];
|
||||
Testing [color="${textColor}", fontcolor="${textColor}"];
|
||||
Tests [color="${textColor}", fontcolor="${textColor}"];
|
||||
|
||||
Example -> Source [color="${textColor}"];
|
||||
Example -> Testing [color="${textColor}"];
|
||||
Tests -> Source [color="${textColor}"];
|
||||
Tests -> Testing [color="${textColor}"];
|
||||
Testing -> Source [color="${textColor}"];
|
||||
}`} />
|
||||
</div>
|
||||
}
|
||||
|
||||
const Layers = () => {
|
||||
const textColor = useTextColor()
|
||||
const backgroundColor = useBackgroundColor()
|
||||
const width = useResponsiveValue(["100%", "400"])
|
||||
|
||||
return <div sx={{ py: 3 }}>
|
||||
<Graphviz options={{ width: width }} dot={`digraph {
|
||||
graph [bgcolor="${backgroundColor}"];
|
||||
|
||||
Application [color="${textColor}", fontcolor="${textColor}"];
|
||||
µSearch [color="${textColor}", fontcolor="${textColor}"];
|
||||
µHome [color="${textColor}", fontcolor="${textColor}"];
|
||||
µProfile [color="${textColor}", fontcolor="${textColor}"];
|
||||
µDependencies [color="${textColor}", fontcolor="${textColor}"];
|
||||
µCore [color="${textColor}", fontcolor="${textColor}"];
|
||||
µUI [color="${textColor}", fontcolor="${textColor}"];
|
||||
µTesting [color="${textColor}", fontcolor="${textColor}"];
|
||||
|
||||
Application -> µSearch [color="${textColor}"];
|
||||
Application -> µHome [color="${textColor}"];
|
||||
Application -> µProfile [color="${textColor}"];
|
||||
µSearch -> µDependencies [color="${textColor}"];
|
||||
µHome -> µDependencies [color="${textColor}"];
|
||||
µProfile -> µDependencies [color="${textColor}"];
|
||||
µDependencies -> µCore [color="${textColor}"];
|
||||
µDependencies -> µUI [color="${textColor}"];
|
||||
µDependencies -> µTesting [color="${textColor}"];
|
||||
}`} />
|
||||
</div>
|
||||
}
|
||||
|
||||
export { CrossPlatform, MicroFeature, Layers }
|
|
@ -0,0 +1,283 @@
|
|||
---
|
||||
name: µFeatures Architecture
|
||||
order: 1
|
||||
excerpt: 'This document describes an approach for architecting a modular Apple OS application to enable scalability, optimize build and test cycles, and ensure good practices.'
|
||||
---
|
||||
|
||||
import { CrossPlatform, MicroFeature, Layers } from "./components/microfeatures"
|
||||
|
||||
# µFeatures Architecture
|
||||
|
||||
## What
|
||||
|
||||
uFeatures is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs.
|
||||
|
||||
These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools and advice if you decide to use this architecture.
|
||||
|
||||
<Message
|
||||
info
|
||||
title="Name"
|
||||
description={`The name uFeatures *(microfeatures)* comes from the [microservices architecture](https://martinfowler.com/articles/microservices.html), where different "backend features" run as different services with defined APIs to enable communication between them.`}
|
||||
/>
|
||||
|
||||
## Context
|
||||
|
||||
Apps are made of features. Typically these features are part of the same module, or target where the whole application is defined. The natural inclination in the team is to continue building features and its tests in the same targets. As a result, the application and its tests target grows in complexity which manifests in bugs, bad compilation times, and team performance. What seemed to be a good architecture, doesn't work out that well in large codebases or teams.
|
||||
|
||||
This is frequently a big source of frustration when it comes to work on those projects. The time we spend goes into compiling rather than building and experimenting with the platform.
|
||||
|
||||
## Motivation
|
||||
|
||||
The µFeatures's main motivation is to support the scalability of large iOS codebases leveraging platform features and tools. There are other solutions out there that could be also be considered to overcome those issues. A very popular one nowadays is [React Native](https://facebook.github.io/react-native/) that leverages the Javascript dynamism to offer developers a pleasant experience working in the code base, but at the same time a native experience from the user point of view.
|
||||
|
||||
**We believe that the usage of native tools and technologies can be optimized to overcome scalability challenges that sooner or later show up in our projects**
|
||||
|
||||
## Before reading
|
||||
|
||||
- Don't expect this to be a silver-bullet solution to your problems. You should take the core ideas, process them, and apply the principles to your projects.
|
||||
- Each project is different, and so are the needs. With the ideas in the guidelines, and your needs, you should figure out what might work out for you.
|
||||
- Since everything this architecture depends on is evolving *(tools, languages, concepts)*, the guidelines might get outdated very quickly. If that happens, don't hesitate to open a PR and contribute with keeping this guidelines up to date.
|
||||
- It can very tempting to scale your app architecture before it actually needs it. If your app needs it, you'll notice it, and only at that point, you should consider start tackling the issue.
|
||||
|
||||
## Core principle
|
||||
|
||||
Developers should be able to **build, test and try** their features fast, with independence of the main app.
|
||||
|
||||
## What is a µFeature
|
||||
A µFeature represents an application feature and is a combination of the following 4 targets *(referring with target to a Xcode target)*:
|
||||
|
||||
- **Source:** Contains the feature source code *(Swift, Objective-C, C++, React Native...)* and its resources *(images, fonts, storyboards, xibs)*.
|
||||
- **Tests:** Contains the feature unit and integration tests.
|
||||
- **Testing:** Provides testing data that can be used for the tests and from the example app. It also provide mocks for uFeature classes and protocols that can be used by other features as we'll see later.
|
||||
- **Example:** Contains an example app that developers can use to try out the feature under certain conditions *(different languages, screen sizes, settings)*.
|
||||
|
||||
|
||||
The diagram below shows the 4 targets and the dependencies between them:
|
||||
|
||||
<MicroFeature/>
|
||||
|
||||
## Why a µFeature
|
||||
|
||||
### Clear and concise APIs
|
||||
When all the app source code lives in the same target is very easy to build implicit dependencies in code, and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature *"clients"* to use the feature and we can enforce good practises by designing safe APIs.
|
||||
|
||||
### Small modules
|
||||
[Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app.
|
||||
|
||||
### Reusability
|
||||
Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building µFeatures reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing µFeatures and adding *(when necessary)* platform-specific UI layers.
|
||||
|
||||
## Types of µFeatures
|
||||
|
||||
### Foundation
|
||||
Foundation µFeatures contain foundational tools *(wrappers, extensions, ...)* that are combined to build other µFeatures. Thus other µFeatures have access to the foundation ones. Some examples of foundations µFeatures are:
|
||||
|
||||
- **µUI:** Provides custom views, UIKit extensions, fonts, and colors that are used to build user-facing layouts.
|
||||
- **µTesting:** Facilitates testing by providing XCTest extensions as well as custom assertions.
|
||||
- **µCore:** It can be seen as the `Foundation` of your app, providing tools such as analytics reporter, logger, API client or a storage class.
|
||||
|
||||
In practice, foundation µFeatures expose interfaces (Structs, Classes, Enums) and extensions of platform frameworks such as `XCTest`, `Foundation` or `UIKit`.
|
||||
|
||||
<Message
|
||||
warning
|
||||
title="Static instances"
|
||||
description="Foundation µFeatures shouldn't expose static instances that are globally accessed. As we'll see later, it's up to the app to control the lifecycle of those foundation dependencies, and pass them to other µFeatures using dependency injection."
|
||||
/>
|
||||
|
||||
### Product
|
||||
Product µFeatures contain features that the user can feel and interact with. They are built by combining foundation µFeatures. Some examples of product µFeatures are:
|
||||
|
||||
- **µSearch:** Contains your product search feature that allows users searching content on the platform.
|
||||
- **µPayments:** Contains the business logic to handle payment flows and upsell screens to upgrade users to premium plans.
|
||||
- **µHome:** Contains the product home screen with the most recent platform content.
|
||||
|
||||
<Message
|
||||
warning
|
||||
title="Product domain"
|
||||
description="Product µFeatures usually represent your product's features."
|
||||
/>
|
||||
|
||||
In practice, product µFeatures expose **views** and **services**. In the following sections we'll see how the app target uses those views and services to build up the app.
|
||||
|
||||
## Hooking µFeatures
|
||||
|
||||
As we mentioned earlier, µFeatures **don't expose instances** and it's the app responsibility to create instances and use them. How we instantiate and hook µFeatures depends on the type of µFeature.
|
||||
|
||||
### Services
|
||||
|
||||
Apps usually have services or utils whose state is tied to the application lifecycle. Those instances are global and the majority of the features will need to access them.
|
||||
|
||||
```swift
|
||||
// Services.swift in the main application
|
||||
import uCore
|
||||
import uPlayback
|
||||
|
||||
class Services {
|
||||
static let playback = PlaybackService() // From uPlayback
|
||||
static let client = Client(baseUrl: "https://api.shakira.io") // From uCore
|
||||
static let analytics = Analytics(firebaseKey: "xxx") // From uCore
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `Services.swift` works as a static container, initializing all the services and tools with their initial state. Some of these services might need to know about the application lifecycle. We could subscribe to those notifications internally, but then we'd be coupling the service to the `NotificationCenter` and the platform-specific lifecycle notifications. What we could do instead is explicitly notifying them about the lifecycle events from the app delegate.
|
||||
|
||||
```swift
|
||||
// AppDelegate.swift
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
Services.playback.restoreState()
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Views/ViewControllers
|
||||
|
||||
On the other side, product µFeatures can also expose views and view controllers. These views usually encapsulate the logic to update themselves according to internal state changes, and react to user actions, turning those actions into state updates *(e.g. synchronizing data with the API)*.
|
||||
|
||||
```swift
|
||||
// Home.swift in uHome
|
||||
import UIKit
|
||||
import uCore
|
||||
|
||||
public class Home {
|
||||
let client: Client
|
||||
public init(client: Client) {
|
||||
self.client = client
|
||||
}
|
||||
public func makeViewController(delegate: HomeDelegate) -> UIViewController {
|
||||
return HomeViewController(client: client, delegate: delegate)
|
||||
}
|
||||
}
|
||||
```
|
||||
The example above shows how the home µFeature looks like. It gets initialized with its dependencies and expose a method that instantiates and returns a view controller to be used from the app. Notice that the method returns a `UIViewController` instead of a `HomeViewController`. By doing that we abstract the app from any implementation detail.
|
||||
|
||||
#### Delegating navigation
|
||||
You might have noticed that we pass a delegate when we instantiate the view controller. The delegate responds to actions that trigger a navigation to a different µFeature. It's up to the app to define the navigation between different µFeatures. A pattern that works very well here is the [Coordinator Pattern](https://vimeo.com/144116310) that allows you represent your navigation as a tree of coordinators. These coordinators would be in the app, responding to µFeatures actions, and triggering the navigation to other coordinators.
|
||||
|
||||
Delegating the navigation to the app gives us the flexibility to change the navigation based on the product where we are consuming the µFeature from. Let's take an hypothetical search µFeature that exposes a search view controller. When we use that view controller from the app, we want to navigate to another µFeature when the user taps in one of the search results. However, if we use that view controller from an iMessage extension, we want the action to be different, and instead, share the search result with one of your contacts.
|
||||
|
||||
## Layers
|
||||
|
||||
We talked about µFeatures, types, and how to use them from the app, but we missed out a very important point, how to organize them to prevent ending up with a messy dependency graph or eventually with circular dependencies impossible to resolve for the compiler.
|
||||
|
||||
It's recommended to organize the µFeatures in three layers below the product layer as shown in the image below.
|
||||
|
||||
<Layers/>
|
||||
|
||||
- **Product µFeatures:** Contains all the product µFeatures.
|
||||
- **Dependency inversion:** Contains the product µFeatures public interfaces.
|
||||
- **Foundation µFeatures:** Contains all the foundation µFeatures.
|
||||
|
||||
Product µFeatures don't depend on each other, instead we use the **dependency inversion principle** to expose their interfaces in a layer underneath them. There are a couple of advantages that support it:
|
||||
|
||||
- It delegates to the app the decission about how different features are connected. We could decide in a product-basis.
|
||||
- It decouples the targets in the same layer, removing the need to compile all the dependencie when we try to compile a single product µFeature *(faster compilation)*.
|
||||
|
||||
## Dependencies
|
||||
|
||||
As soon as you start building µFeatures you'll realize that most of the features need dependencies that are injected from the app. We could inject those dependencies in the constructor but we'd end up with constructors with a long list of parameters being passed. Instead, we could leverage protocols to represent the µFeatures dependencies and pass them easily *(credits to [@andreacipriani](https://github.com/andreacipriani) for coming up with this approach)*:
|
||||
|
||||
```swift
|
||||
public protocol BaseDependencies {
|
||||
func makeClient() -> Client
|
||||
func makeLogger() -> Logger
|
||||
}
|
||||
```
|
||||
|
||||
A protocol defines the base dependencies that are the most common dependencies across all the features. Dependencies are exposed through methods that return the dependency as a return parameter of those methods.
|
||||
|
||||
```swift
|
||||
class AppDependencies: BaseDependencies {
|
||||
func makeClient() -> Client {
|
||||
return Services.client
|
||||
}
|
||||
func makeLogger() -> Logger {
|
||||
return Services.logger
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
From the app we conform the `BaseDependencies` protocol, defining a class, `AppDependencies` that represents our application base dependencies.
|
||||
|
||||
```swift
|
||||
public protocol SearchDependencies: BaseDependencies {
|
||||
func makeAnalytics() -> Analytics
|
||||
}
|
||||
```
|
||||
|
||||
For some particular µFeatures, we might need some extra dependencies. We can define those in a new protocol that conforms the `BaseDependencies` protocol, adding the extra dependencies. In the example below `SearchDependencies` exposes also an `Analytics` dependency.
|
||||
|
||||
```swift
|
||||
public final class SearchBuilder {
|
||||
|
||||
private let dependenciesSolver: SearchDependencies
|
||||
|
||||
public init(dependenciesSolver: SearchDependencies) {
|
||||
self.dependenciesSolver = dependenciesSolver
|
||||
}
|
||||
|
||||
public func makeViewController() -> UIViewController {
|
||||
let client = dependenciesSolver.makeClient()
|
||||
let logger = dependenciesSolver.makeLogger()
|
||||
let analytics = dependenciesSolver.makeAnalytics()
|
||||
return SearchViewController(client: client, logger: logger, analytics: analytics)
|
||||
}
|
||||
}
|
||||
|
||||
// From the app
|
||||
let searchBuilder = SearchBuilder(dependenciesSolver: AppDependencies())
|
||||
```
|
||||
|
||||
The example above shows how we can inject dependencies in a builder that builds the µFeature instance, in this case a `UIViewController`.
|
||||
|
||||
<Message
|
||||
info
|
||||
title = "Alternatives"
|
||||
description = "This is just an example of how we can simplify dependency injection. There are other alternatives out there. It's up to you to pick up the one that works best for your project and setup."/>
|
||||
|
||||
## Cross-platform µFeatures
|
||||
Your product might be available in different platforms or from different products. In that case it's very important that we reuse as much code as possible. `Foundation` APIs are very similar across plaftorms, with just some subtle differences, but UI frameworks like `UIKit` or `AppKit` are not. We could have multiplatform µFeatures by using [Swift macros](http://ericasadun.com/2014/06/06/swift-cross-platform-code/) that conditionally compile UI for each of the platforms. However, Xcode is very bad dealing with those macros and the syntax highlghting and the autocompletion don't work very well.
|
||||
|
||||
There is something we can do though to enable cross-platform µFeatures. Splitting business logic and UI in two different layers, one that contains the business layer and is cross-platform, and another one that contains the UI and that is platform/product specific.
|
||||
|
||||
<CrossPlatform/>
|
||||
|
||||
The image above shows how the Search µFeature is split into the business logic target, `µSearch` and the platform specific ones `µSearchiOS` and `µSearchmacOS`. Both UI frameworks depend on `µSearch`. Each of those targets would have their corresponding tests target.
|
||||
|
||||
<Message
|
||||
info
|
||||
title = "Building cross-platform frameworks"
|
||||
description = "You can read more about how to setup frameworks to be cross-platform no the [following link](http://ilya.puchka.me/xcode-cross-platform-frameworks/)."/>
|
||||
|
||||
## Example app
|
||||
|
||||
We are working on defining an example app with Tuist. Stay tuned!
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
#### One or multiple Git repositories?
|
||||
If you are working with git branches, we recommend you to keep everything in the same repository for convenience reasons. Facebook is a good example of a huge company keeping all the project in a single repositories and Uber [wrote about it](https://eng.uber.com/ios-monorepo/) a year ago.
|
||||
|
||||
#### How do you version µFeatures?
|
||||
If µFeatures are part of the same repository, they are versioned with the app. If you have them in different repositories you can use Git Submodules, Carthage, or your own dependency resolver to fetch specific versions of your µFeatures to link from the app.
|
||||
|
||||
#### External dependencies?
|
||||
This architecture doesn't limit you from using external dependencies. There's one thing to keep in mind though: you won't be able to use [CocoaPods](https://cocoapods.org) dependencies with your µFeatures. CocoaPods is not able to analyze your dependencies tree, and setup all the targets in the stack accordingly. If you want to use an external dependency from a µFeature framework, we recommend you to use [Carthage](https://github.com/carthage).
|
||||
|
||||
#### Can I include resources?
|
||||
You can, but remember that you can only do it if your µFeature is a framework and not a library.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
- [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures)
|
||||
- [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl)
|
||||
- [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift)
|
||||
- [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1)
|
||||
- [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/)
|
||||
- [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/)
|
||||
- [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html)
|
||||
- [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html)
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
name: Cloud
|
||||
excerpt: 'Learn more about what cloud features are, why the are important for scaling up projects, and why they need a server-side component that holds state across project generations, builds, and team mebers.'
|
||||
---
|
||||
|
||||
# Cloud
|
||||
|
||||
## Context
|
||||
|
||||
Easing the maintenance of large and modular Xcode projects is **not the only challenge** teams run into when scaling up projects.
|
||||
Other challenges that they face are keeping the build and test times slow, both locally and on CI *(i.e. faster feedback)*,
|
||||
and ensuring that the projects and the derived artifacts *(e.g. app bundles)* meet some quality standards.
|
||||
For instance,
|
||||
in most Xcode projects,
|
||||
when developers open pull/merge requests,
|
||||
they don't have a way to know if they are negatively impacting the quality of the project:
|
||||
*build times are under a reasonable value, or
|
||||
the size of the apps is acceptable.*
|
||||
|
||||
Tuist can leverage the generation of Xcode projects to help teams with those challenges.
|
||||
However,
|
||||
**it needs a server side component** that holds a state across project generations, builds, and all the team members.
|
||||
The subset of features that require that server side component are referred to as **cloud**.
|
||||
|
||||
<Message
|
||||
info
|
||||
title="Work in progress"
|
||||
description={`We are currently working on the implementation of the first two cloud features: *caching and insights*. If you'd like to stay up to date with the progress that we are making, we suggest following us on [Twitter](https://twitter.com/tuistio).`}/>
|
||||
|
||||
|
||||
## Server-agnostic
|
||||
|
||||
Tuist provides the client-side logic for those features,
|
||||
and defines the contract with the server-side component through an HTTP [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer).
|
||||
That gives the teams and projects the flexibility to choose the implementation that they'd like to use,
|
||||
and also decide if they'd like to host it internally.
|
||||
|
||||
More information about the contract can be found on [this page](/docs/cloud/contract/).
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
name: Contract
|
||||
excerpt: ''
|
||||
---
|
||||
|
||||
# Contract
|
||||
|
||||
In order for Tuist's cloud features to interact with HTTP servers,
|
||||
the exposed API needs to conform with the contract described in this page.
|
||||
|
||||
<Message
|
||||
info
|
||||
title="Base URL"
|
||||
description={`In all the examples below, we are assuming that the project is pointing to a server with URL https://cloud.tuist.io`}/>
|
||||
|
||||
## Authentication
|
||||
|
||||
### User authentication
|
||||
|
||||
When using Tuist's cloud features from a local environments,
|
||||
users will need to authenticate with the API.
|
||||
If the authentication is successful,
|
||||
a token will be generated and stored safely in the user's environment.
|
||||
The generated token must be unique to the user and valid across all the projects the user has access to.
|
||||
|
||||
The authentication flow:
|
||||
|
||||
1. When the user runs `tuist cloud auth`, Tuist sends the user to `https://cloud.tuist.io/auth`.
|
||||
2. The user authenticates on the server.
|
||||
3. When the authentication finishes, the server redirects the user to `http://localhost:4455/auth`.
|
||||
|
||||
The URL that triggers the call to localhost should include the any of the following attributes:
|
||||
|
||||
- **token:** The generated token if the authentication was successful.
|
||||
- **error:** The error message if the authentication failed.
|
||||
|
||||
```bash
|
||||
# A successful authentication
|
||||
http://localhost:4455/auth?token=xyz
|
||||
|
||||
# An errored authentication
|
||||
# Notice that the error argument has been escaped.
|
||||
http://localhost:4455/auth?error=Couldn%27t%20find%20the%20current%20user
|
||||
```
|
||||
|
||||
<Message
|
||||
info
|
||||
title="Authenticating users"
|
||||
description={`How the server decides to authenticate the user is up to the server. However, we recommend authenticating using the version control provider *(e.g. GitHub)*. That'll allow tying the project to a repository and leverage the API to know whether the user has access to the repository and therefore the project.`}/>
|
||||
|
||||
### Continuous integration (CI) authentication
|
||||
|
||||
On CI,
|
||||
projects will authenticate using a secret token that is associated to the project.
|
||||
The variable should be present in the environment with the name `TUIST_CLOUD_TOKEN`.
|
||||
For security reasons,
|
||||
we recommend defining that variable as a secret variables.
|
||||
|
||||
### Authentication header
|
||||
|
||||
Tuist will authenticate HTTP requests by including an `Authorization` header:
|
||||
|
||||
```bash
|
||||
curl -v -H "Authorization: token TOKEN" https://cloud.tuist.io/api/...
|
||||
```
|
||||
|
||||
Depending on the endpoint, the token will represent a project or a user.
|
||||
Each of the endpoints documented in the following sections will indicate what type of token is expected.
|
|
@ -0,0 +1,40 @@
|
|||
/** @jsx jsx */
|
||||
import { jsx, MenuButton } from 'theme-ui'
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCloud,
|
||||
faUsers,
|
||||
faFileCode,
|
||||
faLayerGroup,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const Cloud = () => {
|
||||
return (
|
||||
<FontAwesomeIcon sx={{ width: 20, height: 20 }} icon={faCloud} size="sm" />
|
||||
)
|
||||
}
|
||||
const Users = () => {
|
||||
return (
|
||||
<FontAwesomeIcon sx={{ width: 20, height: 20 }} icon={faUsers} size="sm" />
|
||||
)
|
||||
}
|
||||
const Contributors = () => {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
sx={{ width: 20, height: 20 }}
|
||||
icon={faFileCode}
|
||||
size="sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
const Architectures = () => {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
sx={{ width: 20, height: 20 }}
|
||||
icon={faLayerGroup}
|
||||
size="sm"
|
||||
/>
|
||||
)
|
||||
}
|
||||
export { Cloud, Users, Contributors, Architectures }
|
|
@ -11,7 +11,14 @@ const Message = ({ title, description }) => {
|
|||
<div sx={{ fontWeight: 'heading', fontSize: 2 }}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<ReactMarkdown source={description} />
|
||||
<ReactMarkdown source={description} sx={{
|
||||
"a:-webkit-any-link": {
|
||||
color: "primary",
|
||||
":hover,:focus,:visited": {
|
||||
color: "secondary",
|
||||
},
|
||||
},
|
||||
}} />
|
||||
</ThemeUIMessage>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -87,3 +87,64 @@ And last but not least,
|
|||
our test cases should contemplate failing scenarios.
|
||||
Not only they ensure that we are handling errors as we are supposed to,
|
||||
but prevent future developers from breaking that logic.
|
||||
|
||||
## 7. Write code for humans
|
||||
|
||||
The code that we write can make a huge difference between a buggy and unmaintainable code base,
|
||||
and a stable and maintainable one.
|
||||
Write beautiful and concise code that is **easy to read and understand**.
|
||||
Leverage the abstractions and primitives provided by the programming language,
|
||||
Swift,
|
||||
to create a solid structure made of simple pieces with scoped responsibilities.
|
||||
Don't add code to Tuist that reads like a long and mysterious bash script.
|
||||
The programming patterns and paradigms that you might apply when building apps might apply to CLI too.
|
||||
In fact, a pattern like MVP, is also valid in the context of CLIs with the difference that the view is the CLI output *(chunks of data sent through the standard output and error)*.
|
||||
|
||||
|
||||
Code that reads like a book encourages contributions,
|
||||
and contributions bring new ideas to the table that
|
||||
|
||||
|
||||
|
||||
We write code not just to be understood by the computer or other programmers, but to bask in the warm glow of beauty. Aesthetically pleasing code is a value unto itself and should be pursued with vigor. That doesn’t mean that beautiful code always trumps other concerns, but it should have a full seat at the table of priorities.
|
||||
|
||||
So what is beautiful code? In Ruby, it’s often somewhere at the intersection between native Ruby idioms and the power of a custom domain-specific language. It’s a fuzzy line, but one well worth trying to dance.
|
||||
|
||||
Here’s a simple example from Active Record:
|
||||
|
||||
|
||||
class Project < ApplicationRecord
|
||||
belongs_to :account
|
||||
has_many :participants, class_name: 'Person'
|
||||
validates_presence_of :name
|
||||
end
|
||||
This looks like DSL, but it’s really just a class definition with three class-method calls that take symbols and options. There’s nothing fancy here. But it sure is pretty. It sure is simple. It gives an immense amount of power and flexibility from those few declarations.
|
||||
|
||||
Part of the beauty comes from these calls honoring the previous principles, like Convention over Configuration. When we call belongs_to :account, we’re assuming that the foreign key is called account_id and that it lives in the projects table. When we have to designate the class_name of Person to the role of the participants association, we require just that class name definition. From it we’ll derive, again, the foreign keys and other configuration points.
|
||||
|
||||
Here’s another example from the database migrations system:
|
||||
|
||||
|
||||
class CreateAccounts < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :accounts do |t|
|
||||
t.integer :queenbee_id
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
This is the essence of framework power. The programmer declares a class according to certain convention, like a ActiveRecord::Migration subclass that implements #change, and the framework can do all the plumbing that goes around that, and know this is the method to call.
|
||||
|
||||
This leaves the programmer with very little code to write. In the case of migrations, not only will this allow a call to rails db:migrate to upgrade the database to add this new table, it’ll also allow it to go the other way of dropping this table with another call. This is very different from a programmer making all this happen and stitching the workflow together from libraries they call themselves.
|
||||
|
||||
Sometimes beautiful code is more subtle, though. It’s less about making something as short or powerful as possible, but more about making the rhythm of the declaration flow.
|
||||
|
||||
These two statements do the same:
|
||||
|
||||
|
||||
if people.include? person
|
||||
…
|
||||
|
||||
if person.in? people
|
||||
But the flow and focus is subtlety different. In the first statement, the focus is on the collection. That’s our subject. In the second statement, the subject is clearly the person. There’s not much between the two statements in length, but I’ll contend that the second is far more beautiful and likely to make me smile when used in a spot where the condition is about the person.
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { Cloud, Users, Contributors, Architectures } from "./components/icons"
|
||||
|
||||
<Users/>
|
||||
|
||||
- [Users](/docs/usage/getting-started/)
|
||||
- [Getting started](/docs/usage/getting-started/)
|
||||
- [Project & workspace](/docs/usage/projectswift/)
|
||||
|
@ -11,6 +15,22 @@
|
|||
|
||||
<br></br>
|
||||
|
||||
<Cloud/>
|
||||
|
||||
- [Cloud](/docs/cloud/cloud/)
|
||||
- [Contract](/docs/cloud/contract/)
|
||||
|
||||
<br></br>
|
||||
|
||||
<Architectures/>
|
||||
|
||||
- [Architectures](/docs/architectures/microfeatures/)
|
||||
- [µFeatures](/docs/architectures/microfeatures/)
|
||||
|
||||
<br></br>
|
||||
|
||||
<Contributors/>
|
||||
|
||||
- [Contributors](/docs/contribution/getting-started/)
|
||||
- [Getting started](/docs/contribution/getting-started/)
|
||||
- [Code reviews](/docs/contribution/code-reviews/)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@mdx-js/react": "^1.5.7",
|
||||
"@mdx-js/tag": "^0.20.3",
|
||||
"@theme-ui/color": "^0.3.1",
|
||||
"@theme-ui/match-media": "^0.3.1",
|
||||
"@theme-ui/presets": "^0.3.0",
|
||||
"@theme-ui/prism": "^0.3.0",
|
||||
"@theme-ui/sidenav": "^0.3.1",
|
||||
|
@ -25,6 +26,7 @@
|
|||
"gatsby-image": "^2.2.44",
|
||||
"gatsby-plugin-favicon": "3.1.6",
|
||||
"gatsby-plugin-feed": "^2.3.28",
|
||||
"gatsby-plugin-google-analytics": "^2.1.38",
|
||||
"gatsby-plugin-manifest": "2.2.48",
|
||||
"gatsby-plugin-mdx": "^1.0.83",
|
||||
"gatsby-plugin-meta-redirect": "^1.1.1",
|
||||
|
@ -39,11 +41,13 @@
|
|||
"gatsby-plugin-theme-ui": "^0.3.0",
|
||||
"gatsby-redirect-from": "^0.2.1",
|
||||
"gatsby-remark-check-links": "^2.1.0",
|
||||
"gatsby-remark-copy-linked-files": "^2.1.40",
|
||||
"gatsby-remark-images": "^3.1.50",
|
||||
"gatsby-remark-smartypants": "^2.1.23",
|
||||
"gatsby-remark-social-cards": "https://github.com/pepibumur/gatsby-remark-social-cards.git#0.5.2",
|
||||
"gatsby-source-filesystem": "^2.1.55",
|
||||
"gatsby-transformer-yaml": "^2.2.27",
|
||||
"graphviz-react": "^1.0.4",
|
||||
"is-absolute-url": "^3.0.3",
|
||||
"moment": "^2.24.0",
|
||||
"polished": "^3.4.4",
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Location } from '@reach/router'
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faSlack } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faDiscourse } from '@fortawesome/free-brands-svg-icons'
|
||||
import logo from '../../static/logo.svg'
|
||||
|
||||
const ColorButton = ({ mode, ...props }) => (
|
||||
|
@ -74,12 +75,13 @@ export default ({ menuOpen, setMenuOpen, menuRef }) => {
|
|||
}
|
||||
const {
|
||||
site: {
|
||||
siteMetadata: { githubUrl, slackUrl, firstDocumentationPagePath },
|
||||
siteMetadata: { githubUrl, discourseUrl, slackUrl, firstDocumentationPagePath },
|
||||
},
|
||||
} = useStaticQuery(graphql`
|
||||
query {
|
||||
site {
|
||||
siteMetadata {
|
||||
discourseUrl
|
||||
githubUrl
|
||||
slackUrl
|
||||
firstDocumentationPagePath
|
||||
|
@ -187,7 +189,7 @@ export default ({ menuOpen, setMenuOpen, menuRef }) => {
|
|||
</Link>
|
||||
</div>
|
||||
|
||||
<div sx={{ flexDirection: 'row', display: 'flex' }}>
|
||||
<div sx={{ flexDirection: 'row', display: 'flex', mt: [3, 0] }}>
|
||||
<a
|
||||
sx={{
|
||||
...linkStyle,
|
||||
|
@ -206,6 +208,24 @@ export default ({ menuOpen, setMenuOpen, menuRef }) => {
|
|||
size="lg"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
sx={{
|
||||
...linkStyle,
|
||||
ml: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
target="__blank"
|
||||
href={discourseUrl}
|
||||
alt="The project's Discourse"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
sx={{ mt: -1, path: { fill: theme.colors.text }, "&:hover": { path: { fill: theme.colors.primary } } }}
|
||||
icon={faDiscourse}
|
||||
size="lg"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
sx={{
|
||||
...linkStyle,
|
||||
|
|
|
@ -47,57 +47,57 @@ fontSizes.display = fontSizes[5]
|
|||
// Colors
|
||||
const colors = {
|
||||
text: "#000",
|
||||
background: "#fff",
|
||||
background: "#ffffff",
|
||||
primary: "#046abd",
|
||||
secondary: "#6F52DA",
|
||||
accent: "hsl(280, 100%, 57%)",
|
||||
accent: "#b624ff",
|
||||
muted: "#f9f9fc",
|
||||
gray: "#555",
|
||||
modes: {
|
||||
black: {
|
||||
text: "#fff",
|
||||
text: "#ffffff",
|
||||
background: "#000",
|
||||
primary: "#0ff",
|
||||
secondary: "#0fc",
|
||||
accent: "#f0f",
|
||||
muted: "#111",
|
||||
gray: "#888",
|
||||
primary: "#00ffff",
|
||||
secondary: "#00ffcc",
|
||||
accent: "#ff00ff",
|
||||
muted: "#111111",
|
||||
gray: "#888888",
|
||||
},
|
||||
dark: {
|
||||
text: "#fff",
|
||||
background: "hsl(180, 5%, 15%)",
|
||||
primary: "hsl(180, 100%, 57%)",
|
||||
secondary: "hsl(50, 100%, 57%)",
|
||||
accent: "hsl(310, 100%, 57%)",
|
||||
muted: "hsl(180, 5%, 5%)",
|
||||
gray: "hsl(180, 0%, 70%)",
|
||||
text: "#ffffff",
|
||||
background: "#242828",
|
||||
primary: "#24ffff",
|
||||
secondary: "#ffda24",
|
||||
accent: "#ff24da",
|
||||
muted: "#0c0d0d",
|
||||
gray: "#b3b3b3",
|
||||
},
|
||||
deep: {
|
||||
text: "#fff",
|
||||
background: "hsl(230,25%,18%)",
|
||||
primary: "hsl(260, 100%, 80%)",
|
||||
secondary: "hsl(290, 100%, 80%)",
|
||||
accent: "hsl(290, 100%, 80%)",
|
||||
muted: "hsla(230, 20%, 0%, 20%)",
|
||||
gray: "hsl(210, 50%, 60%)",
|
||||
text: "#ffffff",
|
||||
background: "#222639",
|
||||
primary: "#bb99ff",
|
||||
secondary: "#ee99ff",
|
||||
accent: "#ee99ff",
|
||||
muted: "#000000",
|
||||
gray: "#6699cc",
|
||||
},
|
||||
hack: {
|
||||
text: "hsl(120, 100%, 75%)",
|
||||
background: "hsl(120, 20%, 10%)",
|
||||
primary: "hsl(120, 100%, 40%)",
|
||||
secondary: "hsl(120, 50%, 40%)",
|
||||
accent: "hsl(120, 100%, 90%)",
|
||||
muted: "hsl(120, 20%, 7%)",
|
||||
gray: "hsl(120, 20%, 40%)",
|
||||
text: "#80ff80",
|
||||
background: "#141f14",
|
||||
primary: "#00cc00",
|
||||
secondary: "#339933",
|
||||
accent: "#ccffcc",
|
||||
muted: "#0e150e",
|
||||
gray: "#527a52",
|
||||
},
|
||||
pink: {
|
||||
text: "hsl(350, 80%, 10%)",
|
||||
background: "hsl(350, 100%, 90%)",
|
||||
primary: "hsl(350, 100%, 50%)",
|
||||
secondary: "hsl(280, 100%, 50%)",
|
||||
accent: "hsl(280, 100%, 20%)",
|
||||
muted: "hsl(350, 100%, 88%)",
|
||||
gray: "hsl(350, 40%, 50%)",
|
||||
text: "#2e050c",
|
||||
background: "#ffccd5",
|
||||
primary: "#ff002b",
|
||||
secondary: "#aa00ff",
|
||||
accent: "#440066",
|
||||
muted: "#ffc2cc",
|
||||
gray: "#b34d5e",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -112,43 +112,54 @@ const styles = {
|
|||
transitionTimingFunction: "ease-out",
|
||||
transitionDuration: ".4s",
|
||||
},
|
||||
ul: {
|
||||
pl: 4
|
||||
},
|
||||
ol: {
|
||||
pl: 4
|
||||
},
|
||||
a: {
|
||||
color: "primary",
|
||||
":hover,:focus": {
|
||||
":hover,:focus,:visited": {
|
||||
color: "secondary",
|
||||
},
|
||||
},
|
||||
h1: {
|
||||
variant: "text.heading",
|
||||
mb: 4,
|
||||
mt: 4
|
||||
my: 4,
|
||||
},
|
||||
h2: {
|
||||
mt: 4,
|
||||
mb: 4,
|
||||
my: 4,
|
||||
variant: "text.heading",
|
||||
fontSize: 4,
|
||||
},
|
||||
h3: {
|
||||
my: 3,
|
||||
my: 4,
|
||||
variant: 'text.heading',
|
||||
fontSize: 2,
|
||||
fontSize: 3,
|
||||
},
|
||||
h4: {
|
||||
my: 3,
|
||||
variant: "text.heading",
|
||||
fontSize: 2,
|
||||
},
|
||||
h5: {
|
||||
my: 3,
|
||||
variant: "text.heading",
|
||||
fontSize: 2,
|
||||
},
|
||||
h6: {
|
||||
my: 3,
|
||||
variant: "text.heading",
|
||||
fontSize: 2,
|
||||
},
|
||||
img: {
|
||||
maxWidth: "100%",
|
||||
height: "auto",
|
||||
},
|
||||
p: {
|
||||
mt: 3,
|
||||
},
|
||||
pre: {
|
||||
fontFamily: "monospace",
|
||||
fontSize: 1,
|
||||
|
|
|
@ -77,7 +77,7 @@ const DocumentationPage = ({
|
|||
]}
|
||||
/>
|
||||
<div
|
||||
sx={{ display: 'flex', flexDirection: ['column', 'row'], flex: '1' }}
|
||||
sx={{ display: 'flex', flexDirection: ['column', 'row'], flex: '1', overflow: 'auto' }}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
|
|
|
@ -1876,6 +1876,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@theme-ui/css/-/css-0.3.1.tgz#b85c7e8fae948dc0de65aa30b853368993e25cb3"
|
||||
integrity sha512-QB2/fZBpo4inaLHL3OrB8NOBgNfwnj8GtHzXWHb9iQSRjmtNX8zPXBe32jLT7qQP0+y8JxPT4YChZIkm5ZyIdg==
|
||||
|
||||
"@theme-ui/match-media@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@theme-ui/match-media/-/match-media-0.3.1.tgz#05af6a71cf14368e1b3bd7180fc382c72d5ba53b"
|
||||
integrity sha512-PHvSRB1vqUgDnPkGlXLa+qadmOMOy3LKSOzovwpTi+wzCUyyOGAsUY/fJQ7nufBrmU3vdYeUTrKplLn5VIEmlg==
|
||||
|
||||
"@theme-ui/mdx@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@theme-ui/mdx/-/mdx-0.3.0.tgz#8bb1342204acfaa69914d6b6567c5c49d9a8c1e6"
|
||||
|
@ -4745,6 +4750,94 @@ cyclist@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
d3-color@1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.0.tgz#89c45a995ed773b13314f06460df26d60ba0ecaf"
|
||||
integrity sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==
|
||||
|
||||
d3-dispatch@1, d3-dispatch@^1.0.3:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
|
||||
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
|
||||
|
||||
d3-drag@1:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70"
|
||||
integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-selection "1"
|
||||
|
||||
d3-ease@1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.6.tgz#ebdb6da22dfac0a22222f2d4da06f66c416a0ec0"
|
||||
integrity sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==
|
||||
|
||||
d3-format@^1.2.0:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.3.tgz#4e8eb4dff3fdcb891a8489ec6e698601c41b96f1"
|
||||
integrity sha512-mm/nE2Y9HgGyjP+rKIekeITVgBtX97o1nrvHCWX8F/yBYyevUTvu9vb5pUnKwrcSw7o7GuwMOWjS9gFDs4O+uQ==
|
||||
|
||||
d3-graphviz@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-graphviz/-/d3-graphviz-2.6.1.tgz#61b93fe330e6339198fd2090f8080d7d4282c514"
|
||||
integrity sha512-878AFSagQyr5tTOrM7YiVYeUC2/NoFcOB3/oew+LAML0xekyJSw9j3WOCUMBsc95KYe9XBYZ+SKKuObVya1tJQ==
|
||||
dependencies:
|
||||
d3-dispatch "^1.0.3"
|
||||
d3-format "^1.2.0"
|
||||
d3-interpolate "^1.1.5"
|
||||
d3-path "^1.0.5"
|
||||
d3-selection "^1.1.0"
|
||||
d3-timer "^1.0.6"
|
||||
d3-transition "^1.1.1"
|
||||
d3-zoom "^1.5.0"
|
||||
viz.js "^1.8.2"
|
||||
|
||||
d3-interpolate@1, d3-interpolate@^1.1.5:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
|
||||
integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
|
||||
d3-path@^1.0.5:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
d3-selection@1, d3-selection@^1.1.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.1.tgz#98eedbbe085fbda5bafa2f9e3f3a2f4d7d622a98"
|
||||
integrity sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==
|
||||
|
||||
d3-timer@1, d3-timer@^1.0.6:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
|
||||
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
|
||||
|
||||
d3-transition@1, d3-transition@^1.1.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398"
|
||||
integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
d3-dispatch "1"
|
||||
d3-ease "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "^1.1.0"
|
||||
d3-timer "1"
|
||||
|
||||
d3-zoom@^1.5.0:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a"
|
||||
integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-drag "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "1"
|
||||
d3-transition "1"
|
||||
|
||||
damerau-levenshtein@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
|
||||
|
@ -6724,6 +6817,13 @@ gatsby-plugin-feed@^2.3.28:
|
|||
lodash.merge "^4.6.2"
|
||||
rss "^1.2.2"
|
||||
|
||||
gatsby-plugin-google-analytics@^2.1.38:
|
||||
version "2.1.38"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-google-analytics/-/gatsby-plugin-google-analytics-2.1.38.tgz#37907b2000abf19dcba06da31ccca99abf3fbbc2"
|
||||
integrity sha512-Ce5E1qoD1jQkDcQIm4qPmu0L66ujqwHua0Vy+UC4rUw2GdIn5dunpBklgYaDFF2DG1gDyMwJ4v+XElLaltXcDQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.7"
|
||||
|
||||
gatsby-plugin-manifest@2.2.48:
|
||||
version "2.2.48"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.2.48.tgz#a082addd697a9d7c27ce51f1c08690b8215c8283"
|
||||
|
@ -6912,6 +7012,20 @@ gatsby-remark-check-links@^2.1.0:
|
|||
dependencies:
|
||||
unist-util-visit "^1.4.1"
|
||||
|
||||
gatsby-remark-copy-linked-files@^2.1.40:
|
||||
version "2.1.40"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.1.40.tgz#7c2790be10ef5a4d16b69587e5dc53f8107f7551"
|
||||
integrity sha512-htZTd5rD46rg4j6KykJJE/GnV+ONidanyDlZWBJyvmIM97Jmcgh6FLpwy68PCzjw32JBdow3Wu2H//vvGYdBYw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.7"
|
||||
cheerio "^1.0.0-rc.3"
|
||||
fs-extra "^8.1.0"
|
||||
is-relative-url "^3.0.0"
|
||||
lodash "^4.17.15"
|
||||
path-is-inside "^1.0.2"
|
||||
probe-image-size "^4.1.1"
|
||||
unist-util-visit "^1.4.1"
|
||||
|
||||
gatsby-remark-images@^3.1.50:
|
||||
version "3.1.50"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-images/-/gatsby-remark-images-3.1.50.tgz#eeaba2407346b7579ab6faa63d9ecfc1a4798ea1"
|
||||
|
@ -7541,6 +7655,14 @@ graphql@^14.6.0:
|
|||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
|
||||
graphviz-react@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/graphviz-react/-/graphviz-react-1.0.4.tgz#86af1f88bf50e7ead4addc0886eb1af7404061e1"
|
||||
integrity sha512-pD9BlD7By+mLL3F4MHWdZhlBJNKR9aqmX6awysQSs1+ztNcELaVbnsUn2WZmcd0c49O6EWUye3aXGw25ypSnWQ==
|
||||
dependencies:
|
||||
d3-graphviz "^2.6.0"
|
||||
react "^16.6.0"
|
||||
|
||||
gray-matter@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.2.tgz#9aa379e3acaf421193fce7d2a28cebd4518ac454"
|
||||
|
@ -12339,7 +12461,7 @@ react@15:
|
|||
object-assign "^4.1.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react@^16.13.0, react@^16.8.0, react@^16.8.6:
|
||||
react@^16.13.0, react@^16.6.0, react@^16.8.0, react@^16.8.6:
|
||||
version "16.13.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7"
|
||||
integrity sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==
|
||||
|
@ -15320,6 +15442,11 @@ vinyl@^2.2.0:
|
|||
remove-trailing-separator "^1.0.1"
|
||||
replace-ext "^1.0.0"
|
||||
|
||||
viz.js@^1.8.2:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/viz.js/-/viz.js-1.8.2.tgz#d9cc04cd99f98ec986bf9054db76a6cbcdc5d97a"
|
||||
integrity sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==
|
||||
|
||||
vm-browserify@^1.0.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
|
|
Loading…
Reference in New Issue