Dependencies.swift - Add XCFramework option for Carthage (#2532)

* [carthage-xcframeworks] add - `CarthageController`

* [carthage-xcframeworks] add - `MockCarthageController`

* [carthage-xcframeworks] change - use `TusitSupport.CarthageController` in `TuistDependencies.CarthageInteractor`

* [carthage-xcframeworks] add - `produceXCFrameworks` argument to `CarthageCommandGenerating.command` method

* [carthage-xcframeworks] change - redesign `Depedencies` model (ProjectDescription&TuistGraph)

* [carthage-xcframeworks] change - adapt `app_with_framework_and_tests_and_dependencies` to changes

* [carthage-xcframeworks] change - update docs

* [carthage-xcframeworks] change - produce XCFrameworks when required

* [carthage-xcframeworks] add - more logs

* [carthage-xcframeworks] fix - acceptance test

* [carthage-xcframeworks] add - documentation

* [carthage-xcframeworks] change - typos

* [carthage-xcframeworks] change - run `bundle exec rake style_correct`

* [carthage-xcframeworks] fix - swiftlint violations

* [carthage-xcframeworks] change - update changelog

* [carthage-xcframeworks] change - redesign `ProjectDescription.Dependencies` model

* [carthage-xcframeworks] fix - acceptance tests

* [carthage-xcframeworks] change - update documentation

* [carthage-xcframeworks] change - run `bundle exec rake style_correct`

* [carthage-xcframeworks] fix - typos

* [carthage-xcframeworks] change - update docs

* [carthage-xcframeworks] change - run `bundle exec rake style_correct`

* [carthage-xcframeworks] fix - typos
This commit is contained in:
Kamil Harasimowicz 2021-02-23 10:38:57 +01:00 committed by GitHub
parent 34a0c82492
commit 31f3aba514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 957 additions and 668 deletions

View File

@ -4,7 +4,10 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
## Next
### Added
- Enable Main Thread Checker by default [#2549](https://github.com/tuist/tuist/pull/2549) by [@myihsan](https://github.com/myihsan)
- Add option for enabling XCFrameworks production for Carthage in `Dependencies.swift`. [#2532](https://github.com/tuist/tuist/pull/2532) by [@laxmorek](https://github.com/laxmorek)
### Fixed

View File

@ -1,14 +0,0 @@
import Foundation
/// A `Dependencies` manifest allows for defining external dependencies for Tuist.
public struct Dependencies: Codable, Equatable {
/// List of dependencies.
public let dependencies: [Dependency]
/// Initializes a new `Dependencies` manifest instance.
/// - Parameter dependencies: List of dependencies.
public init(_ dependencies: [Dependency] = []) {
self.dependencies = dependencies
dumpIfNeeded(self)
}
}

View File

@ -1,76 +1,120 @@
import Foundation
/// Contains the description of external dependency that can by installed using Tuist.
public enum Dependency: Codable, Equatable {
/// Origin of the Carthage dependency
public enum CarthageOrigin: Codable, Equatable {
/// Mimics `github` keyword from `Cartfile`. GitHub repositories (both GitHub.com and GitHub Enterprise).
case github(path: String)
/// Mimics `git` keyword from `Cartfile`. Other Git repositories.
case git(path: String)
/// Mimics `binary` keyword from `Cartfile`. Dependencies that are only available as compiled binary `.frameworks`.
case binary(path: String)
/// Contains the description of a dependency that can be installed using Carthage.
public struct CarthageDependencies: Codable, Equatable {
/// List of depedencies that can be installed using Carthage.
public let dependencies: [Dependency]
/// List of platforms for which you want to install depedencies. Refers to `--platform` Carthage flag.
public let platforms: Set<Platform>
/// Indicates whether Carthage produces XCFrameworks or regular frameworks. Refers to `--use-xcframeworks` Carthage flag.
/// Note: It requires Carthage in version at least 0.37.0.
public let useXCFrameworks: Bool
/// Initializes a new `CarthageDependencies` instance.
/// - Parameters:
/// - dependencies: List of depedencies that can be installed using Carthage.
/// - platforms: List of platforms for which you want to install depedencies. Refers to `--platform` Carthage flag.
/// - useXCFrameworks: Indicates whether Carthage produces XCFrameworks or regular frameworks. Refers to `--use-xcframeworks` Carthage flag. Note: It requires Carthage in version at least 0.37.0.
init(
dependencies: [Dependency],
platforms: Set<Platform> = Set(Platform.allCases),
useXCFrameworks: Bool = false
) {
self.dependencies = dependencies
self.platforms = platforms
self.useXCFrameworks = useXCFrameworks
}
/// Requirement for the Carthage dependency
public enum CarthageRequirement: Codable, Equatable {
/// Mimics `== 1.0` from `Cartfile`.
case exact(Version)
/// Mimics `~> 1.0` from `Cartfile`.
case upToNext(Version)
/// Mimics `>= 1.0` from `Cartfile`.
case atLeast(Version)
/// Mimics `"branch"` from `Cartfile`.
case branch(String)
/// Mimics `"revision"` from `Cartfile`.
case revision(String)
/// Creates `CarthageDependencies` instance.
/// - Parameters:
/// - dependencies: List of depedencies that can be installed using Carthage.
/// - platforms: List of platforms for which you want to install depedencies. Refers to `--platform` Carthage flag.
/// - useXCFrameworks: Indicates whether Carthage produces XCFrameworks or regular frameworks. Refers to `--use-xcframeworks` Carthage flag. Note: It requires Carthage in version at least 0.37.0.
public static func carthage(
_ dependencies: [Dependency],
platforms: Set<Platform> = Set(Platform.allCases),
useXCFrameworks: Bool = false
) -> Self {
.init(dependencies: dependencies, platforms: platforms, useXCFrameworks: useXCFrameworks)
}
/// Contains the description of dependency that can by installed using Carthage. More: https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md
case carthage(origin: CarthageOrigin, requirement: CarthageRequirement, platforms: Set<Platform>)
}
// MARK: - Dependency: Codable
// MARK: - CarthageDependencies.Dependency & CarthageDependencies.Requirement
extension Dependency {
public extension CarthageDependencies {
/// Specifies origin of Carthage dependency.
enum Dependency: Codable, Equatable {
case github(path: String, requirement: Requirement)
case git(path: String, requirement: Requirement)
case binary(path: String, requirement: Requirement)
}
/// Specifies version requirement for Carthage depedency.
enum Requirement: Codable, Equatable {
case exact(Version)
case upToNext(Version)
case atLeast(Version)
case branch(String)
case revision(String)
}
}
// MARK: - CarthageDependencies.Dependency: Codable
extension CarthageDependencies.Dependency {
private enum Kind: String, Codable {
case carthage
case github
case git
case binary
}
private enum CodingKeys: String, CodingKey {
case kind
case origin
case path
case requirement
case platforms
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(Kind.self, forKey: .kind)
switch kind {
case .carthage:
let origin = try container.decode(CarthageOrigin.self, forKey: .origin)
let requirement = try container.decode(CarthageRequirement.self, forKey: .requirement)
let platforms = try container.decode(Set<Platform>.self, forKey: .platforms)
self = .carthage(origin: origin, requirement: requirement, platforms: platforms)
case .github:
let path = try container.decode(String.self, forKey: .path)
let requirement = try container.decode(CarthageDependencies.Requirement.self, forKey: .requirement)
self = .github(path: path, requirement: requirement)
case .git:
let path = try container.decode(String.self, forKey: .path)
let requirement = try container.decode(CarthageDependencies.Requirement.self, forKey: .requirement)
self = .git(path: path, requirement: requirement)
case .binary:
let path = try container.decode(String.self, forKey: .path)
let requirement = try container.decode(CarthageDependencies.Requirement.self, forKey: .requirement)
self = .binary(path: path, requirement: requirement)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .carthage(origin, requirement, platforms):
try container.encode(Kind.carthage, forKey: .kind)
try container.encode(origin, forKey: .origin)
case let .github(path, requirement):
try container.encode(Kind.github, forKey: .kind)
try container.encode(path, forKey: .path)
try container.encode(requirement, forKey: .requirement)
case let .git(path, requirement):
try container.encode(Kind.git, forKey: .kind)
try container.encode(path, forKey: .path)
try container.encode(requirement, forKey: .requirement)
case let .binary(path, requirement):
try container.encode(Kind.binary, forKey: .kind)
try container.encode(path, forKey: .path)
try container.encode(requirement, forKey: .requirement)
try container.encode(platforms, forKey: .platforms)
}
}
}
// MARK: - Dependency.CarthageRequirement: Codable
// MARK: - CarthageDependencies.Requirement: Codoable
extension Dependency.CarthageRequirement {
extension CarthageDependencies.Requirement {
private enum Kind: String, Codable {
case exact
case upToNext
@ -129,49 +173,3 @@ extension Dependency.CarthageRequirement {
}
}
}
// MARK: - Dependency.CarthageOrigin: Codable
extension Dependency.CarthageOrigin {
private enum Kind: String, Codable {
case github
case git
case binary
}
private enum CodingKeys: String, CodingKey {
case kind
case path
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(Kind.self, forKey: .kind)
switch kind {
case .github:
let path = try container.decode(String.self, forKey: .path)
self = .github(path: path)
case .git:
let path = try container.decode(String.self, forKey: .path)
self = .git(path: path)
case .binary:
let path = try container.decode(String.self, forKey: .path)
self = .binary(path: path)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .github(path):
try container.encode(Kind.github, forKey: .kind)
try container.encode(path, forKey: .path)
case let .git(path):
try container.encode(Kind.git, forKey: .kind)
try container.encode(path, forKey: .path)
case let .binary(path):
try container.encode(Kind.binary, forKey: .kind)
try container.encode(path, forKey: .path)
}
}
}

View File

@ -0,0 +1,14 @@
import Foundation
/// A `Dependencies` manifest allows for defining external dependencies for Tuist.
public struct Dependencies: Codable, Equatable {
/// The description of dependency that can be installed using Carthage.
public let carthage: CarthageDependencies?
/// Initializes a new `Dependencies` manifest instance.
/// - Parameter carthage: The description of dependencies that can be installed using Carthage. Pass `nil` if you don't have dependencies from Carthage.
public init(carthage: CarthageDependencies? = nil) {
self.carthage = carthage
dumpIfNeeded(self)
}
}

View File

@ -2,7 +2,7 @@ import Foundation
// MARK: - Platform
public enum Platform: String, Codable, Equatable {
public enum Platform: String, Codable, Equatable, CaseIterable {
case iOS = "ios"
case macOS = "macos"
case watchOS = "watchos"

View File

@ -1,5 +1,6 @@
import RxBlocking
import TSCBasic
import TSCUtility
import TuistCore
import TuistGraph
import TuistSupport
@ -13,11 +14,16 @@ enum CarthageInteractorError: FatalError, Equatable {
case cartfileNotFound
/// Thrown when `Carthage/Build` directory cannont be found in temporary directory after Carthage installation.
case buildDirectoryNotFound
/// Thrown when version of Carthage installed in environment does not support XCFrameworks production.
case xcFrameworksProductionNotSupported
/// Error type.
var type: ErrorType {
switch self {
case .carthageNotFound, .cartfileNotFound, .buildDirectoryNotFound:
case .carthageNotFound,
.cartfileNotFound,
.buildDirectoryNotFound,
.xcFrameworksProductionNotSupported:
return .abort
}
}
@ -31,6 +37,11 @@ enum CarthageInteractorError: FatalError, Equatable {
return "Cartfile was not found after Carthage installation."
case .buildDirectoryNotFound:
return "Carthage/Build directory was not found after Carthage installation."
case .xcFrameworksProductionNotSupported:
return """
The version of Carthage installed in your environment doesn't suppport production of XCFrameworks.
Update the tool or disbale XCFrameworks in your Dependencies.swift manifest.
"""
}
}
}
@ -42,38 +53,34 @@ public protocol CarthageInteracting {
/// - Parameter dependenciesDirectory: The path to the directory that contains the `Tuist/Dependencies/` directory.
/// - Parameter method: Installation method.
/// - Parameter dependencies: List of dependencies to intall using `Carthage`.
func fetch(dependenciesDirectory: AbsolutePath, dependencies: [CarthageDependency]) throws
func fetch(dependenciesDirectory: AbsolutePath, dependencies: CarthageDependencies) throws
}
// MARK: - Carthage Interactor
public final class CarthageInteractor: CarthageInteracting {
private let fileHandler: FileHandling
private let carthageController: CarthageControlling
private let carthageCommandGenerator: CarthageCommandGenerating
private let cartfileContentGenerator: CartfileContentGenerating
public init(
fileHandler: FileHandling = FileHandler.shared,
carthageCommandGenerator: CarthageCommandGenerating = CarthageCommandGenerator(),
cartfileContentGenerator: CartfileContentGenerating = CartfileContentGenerator()
carthageController: CarthageControlling = CarthageController.shared,
carthageCommandGenerator: CarthageCommandGenerating = CarthageCommandGenerator()
) {
self.fileHandler = fileHandler
self.carthageController = carthageController
self.carthageCommandGenerator = carthageCommandGenerator
self.cartfileContentGenerator = cartfileContentGenerator
}
public func fetch(dependenciesDirectory: AbsolutePath, dependencies: [CarthageDependency]) throws {
public func fetch(dependenciesDirectory: AbsolutePath, dependencies: CarthageDependencies) throws {
logger.info("We are starting to fetch the Carthage dependencies.", metadata: .section)
// check availability of `carthage`
guard canUseSystemCarthage() else {
guard carthageController.canUseSystemCarthage() else {
throw CarthageInteractorError.carthageNotFound
}
// determine platforms
let platforms: Set<Platform> = dependencies
.reduce(Set<Platform>()) { platforms, dependency in platforms.union(dependency.platforms) }
try fileHandler.inTemporaryDirectory { temporaryDirectoryPath in
// prepare paths
let pathsProvider = CarthagePathsProvider(dependenciesDirectory: dependenciesDirectory, temporaryDirectoryPath: temporaryDirectoryPath)
@ -82,30 +89,43 @@ public final class CarthageInteractor: CarthageInteracting {
try prepareForInstallation(pathsProvider: pathsProvider, dependencies: dependencies)
// create `carthage` shell command
let command = carthageCommandGenerator.command(path: temporaryDirectoryPath, platforms: platforms)
let command = carthageCommandGenerator.command(
path: temporaryDirectoryPath,
produceXCFrameworks: try shouldProduceXCFrameworks(dependencies: dependencies),
platforms: dependencies.platforms
)
// log
logger.info("Command:", metadata: .subsection)
logger.info("\(command.joined(separator: " "))")
// run `carthage`
logger.info("Carthage:", metadata: .subsection)
try System.shared.runAndPrint(command)
// post intallation actions
try postInstallationActions(pathsProvider: pathsProvider)
}
logger.info("Carthage dependencies were fetched successfully.", metadata: .success)
logger.info("Carthage dependencies were fetched successfully.", metadata: .subsection)
}
// MARK: - Installation
private func prepareForInstallation(pathsProvider: CarthagePathsProvider, dependencies: [CarthageDependency]) throws {
private func prepareForInstallation(pathsProvider: CarthagePathsProvider, dependencies: CarthageDependencies) throws {
// copy build directory from previous run if exist
if fileHandler.exists(pathsProvider.destinationCarthageDirectory) {
try copyDirectory(from: pathsProvider.destinationCarthageDirectory, to: pathsProvider.temporaryCarthageBuildDirectory)
}
// create `Cartfile`
let cartfileContent = cartfileContentGenerator.cartfileContent(for: dependencies)
let cartfileContent = dependencies.cartfileValue()
let cartfilePath = pathsProvider.temporaryDirectoryPath.appending(component: "Cartfile")
try fileHandler.write(cartfileContent, path: cartfilePath, atomically: true)
// log
logger.info("Cartfile:", metadata: .subsection)
logger.info("\(cartfileContent)")
}
private func postInstallationActions(pathsProvider: CarthagePathsProvider) throws {
@ -125,6 +145,17 @@ public final class CarthageInteractor: CarthageInteracting {
// MARK: - Helpers
private func shouldProduceXCFrameworks(dependencies: CarthageDependencies) throws -> Bool {
guard dependencies.useXCFrameworks else {
return false
}
guard try carthageController.isXCFrameworksProductionSupported() else {
throw CarthageInteractorError.xcFrameworksProductionNotSupported
}
return true
}
private func copyFile(from fromPath: AbsolutePath, to toPath: AbsolutePath) throws {
try fileHandler.createFolder(toPath.removingLastComponent())
@ -144,17 +175,6 @@ public final class CarthageInteractor: CarthageInteracting {
try fileHandler.copy(from: fromPath, to: toPath)
}
/// Returns true if Carthage is avaiable in the environment.
/// - Returns: True if Carthege is available globally in the system.
private func canUseSystemCarthage() -> Bool {
do {
_ = try System.shared.which("carthage")
return true
} catch {
return false
}
}
}
// MARK: - Models

View File

@ -1,24 +0,0 @@
import TSCBasic
import TuistCore
import TuistGraph
import TuistSupport
// MARK: - Cartfile Content Generating
public protocol CartfileContentGenerating {
/// Generates content for `Cartfile`.
/// - Parameter dependencies: The dependencies whose will be installed.
func cartfileContent(for dependencies: [CarthageDependency]) -> String
}
// MARK: - Cartfile Content Generator
public final class CartfileContentGenerator: CartfileContentGenerating {
public init() {}
public func cartfileContent(for dependencies: [CarthageDependency]) -> String {
dependencies
.map(\.cartfileValue)
.joined(separator: "\n")
}
}

View File

@ -9,8 +9,9 @@ public protocol CarthageCommandGenerating {
/// Builds `Carthage` command.
/// - Parameters:
/// - path: Directory whose project's dependencies will be installed.
/// - produceXCFrameworks: Indicates whether `Carthage` produces XCFrameworks instead of regular frameworks.
/// - platforms: The platforms to build for.
func command(path: AbsolutePath, platforms: Set<Platform>?) -> [String]
func command(path: AbsolutePath, produceXCFrameworks: Bool, platforms: Set<Platform>?) -> [String]
}
// MARK: - Carthage Command Generator
@ -18,7 +19,7 @@ public protocol CarthageCommandGenerating {
public final class CarthageCommandGenerator: CarthageCommandGenerating {
public init() {}
public func command(path: AbsolutePath, platforms: Set<Platform>?) -> [String] {
public func command(path: AbsolutePath, produceXCFrameworks: Bool, platforms: Set<Platform>?) -> [String] {
var commandComponents: [String] = []
commandComponents.append("carthage")
commandComponents.append("bootstrap")
@ -35,6 +36,7 @@ public final class CarthageCommandGenerator: CarthageCommandGenerating {
commandComponents.append(
platforms
.map(\.caseValue)
.sorted()
.joined(separator: ",")
)
}
@ -45,6 +47,10 @@ public final class CarthageCommandGenerator: CarthageCommandGenerating {
commandComponents.append("--cache-builds")
commandComponents.append("--new-resolver")
if produceXCFrameworks {
commandComponents.append("--use-xcframeworks")
}
// Return
return commandComponents

View File

@ -39,6 +39,8 @@ public final class DependenciesController: DependenciesControlling {
.appending(component: Constants.tuistDirectoryName)
.appending(component: Constants.DependenciesDirectory.name)
try carthageInteractor.fetch(dependenciesDirectory: dependenciesDirectory, dependencies: dependencies.carthageDependencies)
if let depedencies = dependencies.carthage {
try carthageInteractor.fetch(dependenciesDirectory: dependenciesDirectory, dependencies: depedencies)
}
}
}

View File

@ -1,26 +0,0 @@
import TSCBasic
import TuistGraph
@testable import TuistDependencies
public final class MockCartfileContentGenerator: CartfileContentGenerating {
public init() {}
var invokedCartfileContent = false
var invokedCartfileContentCount = 0
var invokedCartfileContentParameters: [CarthageDependency]?
var invokedCartfileContentParametersList = [[CarthageDependency]]()
var cartfileContentStub: (([CarthageDependency]) -> String)?
public func cartfileContent(for dependencies: [CarthageDependency]) -> String {
invokedCartfileContent = true
invokedCartfileContentCount += 1
invokedCartfileContentParameters = dependencies
invokedCartfileContentParametersList.append(dependencies)
if let stub = cartfileContentStub {
return stub(dependencies)
} else {
return ""
}
}
}

View File

@ -8,17 +8,17 @@ public final class MockCarthageCommandGenerator: CarthageCommandGenerating {
var invokedCommand = false
var invokedCommandCount = 0
var invokedCommandParameters: (path: AbsolutePath, platforms: Set<Platform>?)?
var invokedCommandParametersList = [(path: AbsolutePath, platforms: Set<Platform>?)]()
var commandStub: ((AbsolutePath, Set<Platform>?) -> [String])?
var invokedCommandParameters: (path: AbsolutePath, produceXCFrameworks: Bool, platforms: Set<Platform>?)?
var invokedCommandParametersList = [(path: AbsolutePath, produceXCFrameworks: Bool, platforms: Set<Platform>?)]()
var commandStub: ((AbsolutePath, Bool, Set<Platform>?) -> [String])?
public func command(path: AbsolutePath, platforms: Set<Platform>?) -> [String] {
public func command(path: AbsolutePath, produceXCFrameworks: Bool, platforms: Set<Platform>?) -> [String] {
invokedCommand = true
invokedCommandCount += 1
invokedCommandParameters = (path, platforms)
invokedCommandParametersList.append((path, platforms))
invokedCommandParameters = (path, produceXCFrameworks, platforms)
invokedCommandParametersList.append((path, produceXCFrameworks, platforms))
if let stub = commandStub {
return stub(path, platforms)
return stub(path, produceXCFrameworks, platforms)
} else {
return []
}

View File

@ -8,11 +8,11 @@ public final class MockCarthageInteractor: CarthageInteracting {
var invokedFetch = false
var invokedFetchCount = 0
var invokedFetchParameters: (dependenciesDirectory: AbsolutePath, dependencies: [CarthageDependency])?
var invokedFetchParametersList = [(dependenciesDirectory: AbsolutePath, dependencies: [CarthageDependency])]()
var invokedFetchParameters: (dependenciesDirectory: AbsolutePath, dependencies: CarthageDependencies)?
var invokedFetchParametersList = [(dependenciesDirectory: AbsolutePath, dependencies: CarthageDependencies)]()
var stubbedFetchError: Error?
public func fetch(dependenciesDirectory: AbsolutePath, dependencies: [CarthageDependency]) throws {
public func fetch(dependenciesDirectory: AbsolutePath, dependencies: CarthageDependencies) throws {
invokedFetch = true
invokedFetchCount += 1
invokedFetchParameters = (dependenciesDirectory, dependencies)

View File

@ -0,0 +1,69 @@
import Foundation
/// Contains descriptions of dependencies to be fetched with Carthage.
public struct CarthageDependencies: Equatable {
/// List of depedencies that can be installed using Carthage.
public let dependencies: [Dependency]
/// List of platforms for which you want to install depedencies.
public let platforms: Set<Platform>
/// Indicates whether Carthage produces XCFrameworks or regular frameworks.
public let useXCFrameworks: Bool
/// Initializes the carthage dependency with its attributes.
public init(_ dependencies: [Dependency], platforms: Set<Platform>, useXCFrameworks: Bool) {
self.dependencies = dependencies
self.platforms = platforms
self.useXCFrameworks = useXCFrameworks
}
/// Returns `Cartfile` representation.
public func cartfileValue() -> String {
dependencies
.map(\.cartfileValue)
.joined(separator: "\n")
}
}
public extension CarthageDependencies {
enum Dependency: Equatable {
case github(path: String, requirement: Requirement)
case git(path: String, requirement: Requirement)
case binary(path: String, requirement: Requirement)
/// Returns `Cartfile` representation.
public var cartfileValue: String {
switch self {
case let .github(path, requirement):
return #"github "\#(path)" \#(requirement.cartfileValue)"#
case let .git(path, requirement):
return #"git "\#(path)" \#(requirement.cartfileValue)"#
case let .binary(path, requirement):
return #"binary "\#(path)" \#(requirement.cartfileValue)"#
}
}
}
enum Requirement: Equatable {
case exact(String)
case upToNext(String)
case atLeast(String)
case branch(String)
case revision(String)
/// Returns `Cartfile` representation.
public var cartfileValue: String {
switch self {
case let .exact(version):
return "== \(version)"
case let .upToNext(version):
return "~> \(version)"
case let .atLeast(version):
return ">= \(version)"
case let .branch(branch):
return #""\#(branch)""#
case let .revision(revision):
return #""\#(revision)""#
}
}
}
}

View File

@ -1,84 +0,0 @@
import Foundation
// MARK: - Carthage Dependency
/// CarthageDependency contains the description of a dependency to be fetched with Carthage.
public struct CarthageDependency: Equatable {
/// Origin of the dependency
public let origin: Origin
/// Type of requirement for the given dependency
public let requirement: Requirement
/// Set of platforms for the given dependency
public let platforms: Set<Platform>
/// Initializes the carthage dependency with its attributes.
///
/// - Parameters:
/// - origin: Origin of the dependency
/// - requirement: Type of requirement for the given dependency
/// - platforms: Set of platforms for the given dependency
public init(
origin: Origin,
requirement: Requirement,
platforms: Set<Platform>
) {
self.origin = origin
self.requirement = requirement
self.platforms = platforms
}
/// Returns `Cartfile` representation.
public var cartfileValue: String {
origin.cartfileValue + " " + requirement.cartfileValue
}
}
public extension CarthageDependency {
enum Origin: Equatable {
case github(path: String)
case git(path: String)
case binary(path: String)
/// Returns `Cartfile` representation.
public var cartfileValue: String {
switch self {
case let .github(path):
return #"github "\#(path)""#
case let .git(path):
return #"git "\#(path)""#
case let .binary(path):
return #"binary "\#(path)""#
}
}
}
}
// MARK: - Requirement
public extension CarthageDependency {
enum Requirement: Equatable {
case exact(String)
case upToNext(String)
case atLeast(String)
case branch(String)
case revision(String)
/// Returns `Cartfile` representation.
public var cartfileValue: String {
switch self {
case let .exact(version):
return "== \(version)"
case let .upToNext(version):
return "~> \(version)"
case let .atLeast(version):
return ">= \(version)"
case let .branch(branch):
return #""\#(branch)""#
case let .revision(revision):
return #""\#(revision)""#
}
}
}
}

View File

@ -1,11 +1,9 @@
import Foundation
public struct Dependencies: Equatable {
public let carthageDependencies: [CarthageDependency]
public let carthage: CarthageDependencies?
public init(
carthageDependencies: [CarthageDependency]
) {
self.carthageDependencies = carthageDependencies
public init(carthage: CarthageDependencies?) {
self.carthage = carthage
}
}

View File

@ -17,7 +17,7 @@ final class DependenciesFetchService {
}
func run(path: String?) throws {
logger.info("We are starting to fetch/update the dependencies.", metadata: .section)
logger.info("We are starting to fetch the dependencies.", metadata: .section)
let path = self.path(path)
let dependencies = try dependenciesModelLoader.loadDependencies(at: path)

View File

@ -0,0 +1,47 @@
import Foundation
import ProjectDescription
import TSCBasic
import TuistCore
import TuistGraph
import TuistSupport
extension TuistGraph.CarthageDependencies {
/// Creates `TuistGraph.CarthageDependencies` instance from `ProjectDescription.CarthageDependencies` instance.
static func from(manifest: ProjectDescription.CarthageDependencies) throws -> Self {
let dependencies = manifest.dependencies.map { TuistGraph.CarthageDependencies.Dependency.from(manifest: $0) }
let platforms = try manifest.platforms.map { try TuistGraph.Platform.from(manifest: $0) }
return .init(dependencies, platforms: Set(platforms), useXCFrameworks: manifest.useXCFrameworks)
}
}
extension TuistGraph.CarthageDependencies.Dependency {
/// Creates `TuistGraph.CarthageDependencies.Dependency` instance from `ProjectDescription.CarthageDependencies.Dependency` instance.
static func from(manifest: ProjectDescription.CarthageDependencies.Dependency) -> Self {
switch manifest {
case let .github(path, requirement):
return .github(path: path, requirement: .from(manifest: requirement))
case let .git(path, requirement):
return .git(path: path, requirement: .from(manifest: requirement))
case let .binary(path, requirement):
return .binary(path: path, requirement: .from(manifest: requirement))
}
}
}
extension TuistGraph.CarthageDependencies.Requirement {
/// Creates `TuistGraph.CarthageDependencies.Requirement` instance from `ProjectDescription.CarthageDependencies.Requirement` instance.
static func from(manifest: ProjectDescription.CarthageDependencies.Requirement) -> Self {
switch manifest {
case let .exact(version):
return .exact(version.description)
case let .upToNext(version):
return .upToNext(version.description)
case let .atLeast(version):
return .atLeast(version.description)
case let .branch(branch):
return .branch(branch)
case let .revision(revision):
return .revision(revision)
}
}
}

View File

@ -1,36 +0,0 @@
import Foundation
import ProjectDescription
import TSCBasic
import TuistCore
import TuistGraph
import TuistSupport
extension TuistGraph.CarthageDependency.Origin {
static func from(manifest: ProjectDescription.Dependency.CarthageOrigin) throws -> Self {
switch manifest {
case let .github(path):
return .github(path: path)
case let .git(path):
return .git(path: path)
case let .binary(path):
return .binary(path: path)
}
}
}
extension TuistGraph.CarthageDependency.Requirement {
static func from(manifest: ProjectDescription.Dependency.CarthageRequirement) throws -> Self {
switch manifest {
case let .exact(version):
return .exact(version.description)
case let .upToNext(version):
return .upToNext(version.description)
case let .atLeast(version):
return .atLeast(version.description)
case let .branch(branch):
return .branch(branch)
case let .revision(revision):
return .revision(revision)
}
}
}

View File

@ -7,16 +7,13 @@ import TuistSupport
extension TuistGraph.Dependencies {
static func from(manifest: ProjectDescription.Dependencies) throws -> Self {
let carthageDependencies = try manifest.dependencies.reduce(into: [CarthageDependency]()) { result, dependency in
switch dependency {
case let .carthage(origin, requirement, platforms):
let origin = try TuistGraph.CarthageDependency.Origin.from(manifest: origin)
let requirement = try TuistGraph.CarthageDependency.Requirement.from(manifest: requirement)
let platforms = try platforms.map { try TuistGraph.Platform.from(manifest: $0) }
result.append(CarthageDependency(origin: origin, requirement: requirement, platforms: Set(platforms)))
let carthage: TuistGraph.CarthageDependencies? = try {
guard let carthage = manifest.carthage else {
return nil
}
}
return try TuistGraph.CarthageDependencies.from(manifest: carthage)
}()
return Self(carthageDependencies: carthageDependencies)
return Self(carthage: carthage)
}
}

View File

@ -22,7 +22,7 @@ public class MockDependenciesModelLoader: DependenciesModelLoading {
if let stub = loadDependenciesStub {
return try stub(path)
} else {
return Dependencies(carthageDependencies: [])
return Dependencies(carthage: nil)
}
}
}

View File

@ -163,8 +163,8 @@ extension Arguments {
}
extension Dependencies {
public static func test(dependencies: [Dependency] = []) -> Dependencies {
Dependencies(dependencies)
public static func test(carthageDependencies: CarthageDependencies? = nil) -> Dependencies {
Dependencies(carthage: carthageDependencies)
}
}

View File

@ -0,0 +1,89 @@
import Foundation
import TSCBasic
import TSCUtility
// MARK: - Carthage Controller Error
enum CarthageControllerError: FatalError, Equatable {
/// Thrown when Carthage cannot be found in the environment.
case carthageNotFound
/// Thrown when version of Carthage cannot be determined.
case unrecognizedCarthageVersion
/// Error type.
var type: ErrorType {
switch self {
case .carthageNotFound,
.unrecognizedCarthageVersion:
return .abort
}
}
/// Error description.
var description: String {
switch self {
case .carthageNotFound:
return "Carthage was not found in the environment. It's possible that the tool is not installed or hasn't been exposed to your environment."
case .unrecognizedCarthageVersion:
return "Version of Carthage cannot be determined. It's possible that the tool is not installed or hasn't been exposed to your environment."
}
}
}
// MARK: - Carthage Controlling
/// Controls `Carthage` that can be found in the environment.
public protocol CarthageControlling {
/// Returns true if Carthage is available in the environment.
func canUseSystemCarthage() -> Bool
/// Return version of Carthage that is available in the environment.
func carthageVersion() throws -> Version
/// Returns true if version of Carthage available in the environment supports producing XCFrameworks.
func isXCFrameworksProductionSupported() throws -> Bool
}
// MARK: - Carthage Controller
public final class CarthageController: CarthageControlling {
/// Shared instance.
public static var shared: CarthageControlling = CarthageController()
/// Cached response of `carthage version` command.
@Atomic
private var cachedCarthageVersion: Version?
public func canUseSystemCarthage() -> Bool {
do {
_ = try System.shared.which("carthage")
return true
} catch {
return false
}
}
public func carthageVersion() throws -> Version {
// Return cached value if available
if let cached = cachedCarthageVersion {
return cached
}
guard let output = try? System.shared.capture("/usr/bin/env", "carthage", "version").spm_chomp() else {
throw CarthageControllerError.carthageNotFound
}
guard let version = Version(string: output) else {
throw CarthageControllerError.unrecognizedCarthageVersion
}
cachedCarthageVersion = version
return version
}
public func isXCFrameworksProductionSupported() throws -> Bool {
// Carthage has supported XCFrameworks production since 0.37.0
// More info here: https://github.com/Carthage/Carthage/releases/tag/0.37.0
try carthageVersion() >= Version(0, 37, 0)
}
}

View File

@ -0,0 +1,49 @@
import Foundation
import TSCUtility
@testable import TuistSupport
public final class MockCarthageController: CarthageControlling {
public init() {}
var invokedCanUseSystemCarthage = false
var invokedCanUseSystemCarthageCount = 0
var canUseSystemCarthageStub: (() -> Bool)?
public func canUseSystemCarthage() -> Bool {
invokedCanUseSystemCarthage = true
invokedCanUseSystemCarthageCount += 1
if let stub = canUseSystemCarthageStub {
return stub()
} else {
return false
}
}
var invokedCarthageVersion = false
var invokedCarthageVersionCount = 0
var carthageVersionStub: (() throws -> Version)?
public func carthageVersion() throws -> Version {
invokedCarthageVersion = true
invokedCarthageVersionCount += 1
if let stub = carthageVersionStub {
return try stub()
} else {
return Version(0, 0, 0)
}
}
var invokedIsXCFrameworksProductionSupported = false
var invokedIsXCFrameworksProductionSupportedCount = 0
var isXCFrameworksProductionSupportedStub: (() -> Bool)?
public func isXCFrameworksProductionSupported() throws -> Bool {
invokedIsXCFrameworksProductionSupported = true
invokedIsXCFrameworksProductionSupportedCount += 1
if let stub = isXCFrameworksProductionSupportedStub {
return stub()
} else {
return false
}
}
}

View File

@ -0,0 +1,62 @@
import Foundation
import XCTest
@testable import ProjectDescription
final class CarthageDependenciesTests: XCTestCase {
func test_carthageDependencies_codable() throws {
let subject: CarthageDependencies = .carthage(
[
.github(path: "Dependency/Dependency", requirement: .revision("xyz")),
.git(path: "Git/Git", requirement: .atLeast("1.2.3")),
],
platforms: [.iOS, .macOS, .tvOS, .watchOS],
useXCFrameworks: true
)
XCTAssertCodable(subject)
}
// MARK: - Carthage Origin tests
func test_carthageDependency_github_codable() throws {
let subject: CarthageDependencies.Dependency = .github(path: "Path/Path", requirement: .branch("branch_name"))
XCTAssertCodable(subject)
}
func test_carthageDependency_git_codable() throws {
let subject: CarthageDependencies.Dependency = .git(path: "Git/Git", requirement: .exact("1.5.123"))
XCTAssertCodable(subject)
}
func test_carthageDependency_binary_codable() throws {
let subject: CarthageDependencies.Dependency = .binary(path: "file:///some/Path/MyFramework.json", requirement: .upToNext("5.6.9"))
XCTAssertCodable(subject)
}
// MARK: - Carthage Requirement tests
func test_carthageRequirement_exact_codable() throws {
let subject: CarthageDependencies.Requirement = .exact("1.0.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_upToNext_codable() throws {
let subject: CarthageDependencies.Requirement = .upToNext("3.2.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_atLeast_codable() throws {
let subject: CarthageDependencies.Requirement = .atLeast("3.2.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_branch_codable() throws {
let subject: CarthageDependencies.Requirement = .branch("branch")
XCTAssertCodable(subject)
}
func test_carthageRequirement_revision_codable() throws {
let subject: CarthageDependencies.Requirement = .revision("revision")
XCTAssertCodable(subject)
}
}

View File

@ -0,0 +1,20 @@
import Foundation
import XCTest
@testable import ProjectDescription
final class DependenciesTests: XCTestCase {
func test_dependencies_codable() throws {
let subject = Dependencies(
carthage: .carthage(
[
.github(path: "Dependency1/Dependency1", requirement: .branch("BranchName")),
.git(path: "Dependency2/Dependency2", requirement: .upToNext("1.2.3")),
],
platforms: [.iOS, .macOS],
useXCFrameworks: true
)
)
XCTAssertCodable(subject)
}
}

View File

@ -1,14 +0,0 @@
import Foundation
import XCTest
@testable import ProjectDescription
final class DependenciesTests: XCTestCase {
func test_dependencies_codable() throws {
let subject = Dependencies([
.carthage(origin: .github(path: "Dependency1/Dependency1"), requirement: .branch("BranchName"), platforms: [.iOS]),
.carthage(origin: .git(path: "Dependency2/Dependency2"), requirement: .upToNext("1.2.3"), platforms: [.tvOS, .macOS]),
])
XCTAssertCodable(subject)
}
}

View File

@ -1,55 +0,0 @@
import Foundation
import XCTest
@testable import ProjectDescription
final class DependencyTests: XCTestCase {
func test_dependency_carthage_codable() throws {
let subject: Dependency = .carthage(origin: .github(path: "Dependency/Dependency"), requirement: .revision("xyz"), platforms: [.iOS, .macOS, .tvOS, .watchOS])
XCTAssertCodable(subject)
}
// MARK: - Carthage Origin tests
func test_carthageOrigin_github_codable() throws {
let subject: Dependency.CarthageOrigin = .github(path: "Path/Path")
XCTAssertCodable(subject)
}
func test_carthageOrigin_git_codable() throws {
let subject: Dependency.CarthageOrigin = .git(path: "Git/Git")
XCTAssertCodable(subject)
}
func test_carthageOrigin_binary_codable() throws {
let subject: Dependency.CarthageOrigin = .binary(path: "file:///some/Path/MyFramework.json")
XCTAssertCodable(subject)
}
// MARK: - Carthage Requirement tests
func test_carthageRequirement_exact_codable() throws {
let subject: Dependency.CarthageRequirement = .exact("1.0.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_upToNext_codable() throws {
let subject: Dependency.CarthageRequirement = .upToNext("3.2.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_atLeast_codable() throws {
let subject: Dependency.CarthageRequirement = .atLeast("3.2.0")
XCTAssertCodable(subject)
}
func test_carthageRequirement_branch_codable() throws {
let subject: Dependency.CarthageRequirement = .branch("branch")
XCTAssertCodable(subject)
}
func test_carthageRequirement_revision_codable() throws {
let subject: Dependency.CarthageRequirement = .revision("revision")
XCTAssertCodable(subject)
}
}

View File

@ -12,8 +12,8 @@ final class CarthageInteractorTests: TuistUnitTestCase {
private var subject: CarthageInteractor!
private var fileHandlerMock: MockFileHandler!
private var carthageController: MockCarthageController!
private var carthageCommandGenerator: MockCarthageCommandGenerator!
private var cartfileContentGenerator: MockCartfileContentGenerator!
private var temporaryDirectoryPath: AbsolutePath!
@ -27,18 +27,18 @@ final class CarthageInteractorTests: TuistUnitTestCase {
}
fileHandlerMock = MockFileHandler(temporaryDirectory: { self.temporaryDirectoryPath })
carthageController = MockCarthageController()
carthageCommandGenerator = MockCarthageCommandGenerator()
cartfileContentGenerator = MockCartfileContentGenerator()
subject = CarthageInteractor(fileHandler: fileHandlerMock,
carthageCommandGenerator: carthageCommandGenerator,
cartfileContentGenerator: cartfileContentGenerator)
carthageController: carthageController,
carthageCommandGenerator: carthageCommandGenerator)
}
override func tearDown() {
fileHandlerMock = nil
carthageController = nil
carthageCommandGenerator = nil
cartfileContentGenerator = nil
temporaryDirectoryPath = nil
@ -47,8 +47,54 @@ final class CarthageInteractorTests: TuistUnitTestCase {
super.tearDown()
}
func test_fetch_all_for_platforms() throws {
func test_fetch_throws_when_carthageUnavailableInEnvironment() throws {
// Given
carthageController.canUseSystemCarthageStub = { false }
let rootPath = try temporaryPath()
let dependenciesDirectory = rootPath
.appending(component: Constants.DependenciesDirectory.name)
let dependencies = CarthageDependencies(
[
.github(path: "Moya", requirement: .exact("1.1.1")),
],
platforms: [.iOS],
useXCFrameworks: false
)
// When / Then
XCTAssertThrowsSpecific(
try subject.fetch(dependenciesDirectory: dependenciesDirectory, dependencies: dependencies),
CarthageInteractorError.carthageNotFound
)
}
func test_fetch_throws_when_xcFrameworkdProductionUnsupported_and_useXCFrameworksSpecifiedInOptions() throws {
// Given
carthageController.canUseSystemCarthageStub = { true }
carthageController.isXCFrameworksProductionSupportedStub = { false }
let rootPath = try temporaryPath()
let dependenciesDirectory = rootPath
.appending(component: Constants.DependenciesDirectory.name)
let dependencies = CarthageDependencies(
[
.github(path: "Moya", requirement: .exact("1.1.1")),
],
platforms: [.iOS],
useXCFrameworks: true
)
XCTAssertThrowsSpecific(
try subject.fetch(dependenciesDirectory: dependenciesDirectory, dependencies: dependencies),
CarthageInteractorError.xcFrameworksProductionNotSupported
)
}
func test_fetch_allPlatforms() throws {
// Given
carthageController.canUseSystemCarthageStub = { true }
let rootPath = try temporaryPath()
let temporaryDependenciesDirectory = temporaryDirectoryPath
.appending(component: Constants.DependenciesDirectory.carthageDirectoryName)
@ -70,12 +116,18 @@ final class CarthageInteractorTests: TuistUnitTestCase {
try fileHandler.touch(temporaryDependenciesDirectory.appending(components: "tvOS", "ReactiveMoya.framework", "Info.plist"))
try fileHandler.touch(temporaryDependenciesDirectory.appending(components: "tvOS", "RxMoya.framework", "Info.plist"))
let stubbedDependencies = [
CarthageDependency(origin: .github(path: "Moya"), requirement: .exact("1.1.1"), platforms: [.iOS]),
]
let stubbedCommand = ["carthage", "bootstrap", "--project-directory", temporaryDirectoryPath.pathString, "--platform iOS", "--cache-builds", "--new-resolver"]
let platforms = Set<Platform>([.iOS, .watchOS, .macOS, .tvOS])
let useXCFrameworks = false
let stubbedDependencies = CarthageDependencies(
[
.github(path: "Moya", requirement: .exact("1.1.1")),
],
platforms: platforms,
useXCFrameworks: useXCFrameworks
)
let stubbedCommand = ["carthage", "bootstrap", "--project-directory", temporaryDirectoryPath.pathString, "--platform iOS,macOS,tvOS,watchOS", "--cache-builds", "--new-resolver"]
carthageCommandGenerator.commandStub = { _, _ in stubbedCommand }
carthageCommandGenerator.commandStub = { _, _, _ in stubbedCommand }
system.whichStub = { _ in "1.0.0" }
system.succeedCommand(stubbedCommand)
@ -106,14 +158,14 @@ final class CarthageInteractorTests: TuistUnitTestCase {
XCTAssertTrue(carthageCommandGenerator.invokedCommand)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.path, temporaryDirectoryPath)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.platforms, [.iOS])
XCTAssertTrue(cartfileContentGenerator.invokedCartfileContent)
XCTAssertEqual(cartfileContentGenerator.invokedCartfileContentParameters, stubbedDependencies)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.platforms, platforms)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.produceXCFrameworks, useXCFrameworks)
}
func test_fetch_only_one_platform() throws {
func test_fetch_onePlatform() throws {
// Given
carthageController.canUseSystemCarthageStub = { true }
let rootPath = try temporaryPath()
let temporaryDependenciesDirectory = temporaryDirectoryPath
.appending(component: Constants.DependenciesDirectory.carthageDirectoryName)
@ -126,12 +178,18 @@ final class CarthageInteractorTests: TuistUnitTestCase {
try fileHandler.touch(temporaryDependenciesDirectory.appending(components: "iOS", "ReactiveMoya.framework", "Info.plist"))
try fileHandler.touch(temporaryDependenciesDirectory.appending(components: "iOS", "RxMoya.framework", "Info.plist"))
let stubbedDependencies = [
CarthageDependency(origin: .github(path: "Moya"), requirement: .exact("1.1.1"), platforms: [.iOS]),
]
let platforms = Set<Platform>([.iOS])
let useXCFrameworks = false
let stubbedDependencies = CarthageDependencies(
[
.github(path: "Moya", requirement: .exact("1.1.1")),
],
platforms: platforms,
useXCFrameworks: useXCFrameworks
)
let stubbedCommand = ["carthage", "bootstrap", "--project-directory", temporaryDirectoryPath.pathString, "--platform iOS", "--cache-builds", "--new-resolver"]
carthageCommandGenerator.commandStub = { _, _ in stubbedCommand }
carthageCommandGenerator.commandStub = { _, _, _ in stubbedCommand }
system.whichStub = { _ in "1.0.0" }
system.succeedCommand(stubbedCommand)
@ -162,9 +220,7 @@ final class CarthageInteractorTests: TuistUnitTestCase {
XCTAssertTrue(carthageCommandGenerator.invokedCommand)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.path, temporaryDirectoryPath)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.platforms, [.iOS])
XCTAssertTrue(cartfileContentGenerator.invokedCartfileContent)
XCTAssertEqual(cartfileContentGenerator.invokedCartfileContentParameters, stubbedDependencies)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.platforms, platforms)
XCTAssertEqual(carthageCommandGenerator.invokedCommandParameters?.produceXCFrameworks, useXCFrameworks)
}
}

View File

@ -1,77 +0,0 @@
import TuistCore
import TuistGraph
import TuistSupport
import XCTest
@testable import TuistDependencies
@testable import TuistSupportTesting
final class CartfileContentGenetatorTests: TuistUnitTestCase {
private var subject: CartfileContentGenerator!
override func setUp() {
super.setUp()
subject = CartfileContentGenerator()
}
override func tearDown() {
subject = nil
super.tearDown()
}
func test_build_no_dependencies() throws {
// Given
let dependencies: [CarthageDependency] = []
let expected = """
"""
// When
let got = subject.cartfileContent(for: dependencies)
// Then
XCTAssertEqual(got, expected)
}
func test_build_single_dependency() throws {
// Given
let dependencies: [CarthageDependency] = [
.init(origin: .github(path: "Dependency/Dependency"), requirement: .exact("1.1.1"), platforms: [.iOS]),
]
let expected = """
github "Dependency/Dependency" == 1.1.1
"""
// When
let got = subject.cartfileContent(for: dependencies)
// Then
XCTAssertEqual(got, expected)
}
func test_build_multiple_dependencies() throws {
// Given
let dependencies: [CarthageDependency] = [
.init(origin: .github(path: "Dependency/Dependency"), requirement: .exact("2.1.1"), platforms: [.iOS]),
.init(origin: .github(path: "XYZ/Foo"), requirement: .revision("revision"), platforms: [.tvOS, .macOS]),
.init(origin: .git(path: "Foo/Bar"), requirement: .atLeast("1.0.1"), platforms: [.iOS]),
.init(origin: .github(path: "Qwerty/bar"), requirement: .branch("develop"), platforms: [.watchOS]),
.init(origin: .github(path: "XYZ/Bar"), requirement: .upToNext("1.1.1"), platforms: [.iOS]),
.init(origin: .binary(path: "https://my.domain.com/release/MyFramework.json"), requirement: .upToNext("1.0.1"), platforms: [.iOS]),
.init(origin: .binary(path: "file:///some/local/path/MyFramework.json"), requirement: .atLeast("1.1.0"), platforms: [.iOS]),
]
let expected = """
github "Dependency/Dependency" == 2.1.1
github "XYZ/Foo" "revision"
git "Foo/Bar" >= 1.0.1
github "Qwerty/bar" "develop"
github "XYZ/Bar" ~> 1.1.1
binary "https://my.domain.com/release/MyFramework.json" ~> 1.0.1
binary "file:///some/local/path/MyFramework.json" >= 1.1.0
"""
// When
let got = subject.cartfileContent(for: dependencies)
// Then
XCTAssertEqual(got, expected)
}
}

View File

@ -25,7 +25,7 @@ final class CarthageCommandGeneratorTests: TuistUnitTestCase {
// When
let got = subject
.command(path: stubbedPath, platforms: nil)
.command(path: stubbedPath, produceXCFrameworks: false, platforms: nil)
.joined(separator: " ")
// Then
@ -39,7 +39,21 @@ final class CarthageCommandGeneratorTests: TuistUnitTestCase {
// When
let got = subject
.command(path: stubbedPath, platforms: [.iOS])
.command(path: stubbedPath, produceXCFrameworks: false, platforms: [.iOS])
.joined(separator: " ")
// Then
XCTAssertEqual(got, expected)
}
func test_command_with_platforms_and_xcframeworks() throws {
// Given
let stubbedPath = try temporaryPath()
let expected = "carthage bootstrap --project-directory \(stubbedPath.pathString) --platform iOS,macOS,tvOS,watchOS --use-netrc --cache-builds --new-resolver --use-xcframeworks"
// When
let got = subject
.command(path: stubbedPath, produceXCFrameworks: true, platforms: [.iOS, .tvOS, .macOS, .watchOS])
.joined(separator: " ")
// Then

View File

@ -43,11 +43,15 @@ final class DependenciesControllerTests: TuistUnitTestCase {
.appending(component: Constants.tuistDirectoryName)
.appending(component: Constants.DependenciesDirectory.name)
let stubbedCarthageDependencies = [
CarthageDependency(origin: .github(path: "Moya"), requirement: .exact("1.1.1"), platforms: [.iOS]),
CarthageDependency(origin: .github(path: "RxSwift"), requirement: .exact("2.0.0"), platforms: [.iOS]),
]
let stubbedDependencies = Dependencies(carthageDependencies: stubbedCarthageDependencies)
let stubbedCarthageDependencies = CarthageDependencies(
[
.github(path: "Moya", requirement: .exact("1.1.1")),
.github(path: "RxSwift", requirement: .exact("2.0.0")),
],
platforms: [.iOS],
useXCFrameworks: true
)
let stubbedDependencies = Dependencies(carthage: stubbedCarthageDependencies)
// When
try subject.fetch(at: rootPath, dependencies: stubbedDependencies)

View File

@ -0,0 +1,158 @@
import Foundation
import XCTest
@testable import TuistGraph
@testable import TuistSupportTesting
final class CarthageDependenciesTests: TuistUnitTestCase {
func test_cartfileValue_singleDependency() {
// Given
let carthageDependencies: CarthageDependencies = .init(
[
.github(path: "Dependency/Dependency", requirement: .exact("1.1.1")),
],
platforms: [.iOS],
useXCFrameworks: false
)
let expected = """
github "Dependency/Dependency" == 1.1.1
"""
// When
let got = carthageDependencies.cartfileValue()
// Then
XCTAssertEqual(got, expected)
}
func test_cartfileValue_multipleDependencies() {
// Given
let carthageDependencies: CarthageDependencies = .init(
[
.github(path: "Dependency/Dependency", requirement: .exact("2.1.1")),
.github(path: "XYZ/Foo", requirement: .revision("revision")),
.git(path: "Foo/Bar", requirement: .atLeast("1.0.1")),
.github(path: "Qwerty/bar", requirement: .branch("develop")),
.github(path: "XYZ/Bar", requirement: .upToNext("1.1.1")),
.binary(path: "https://my.domain.com/release/MyFramework.json", requirement: .upToNext("1.0.1")),
.binary(path: "file:///some/local/path/MyFramework.json", requirement: .atLeast("1.1.0")),
],
platforms: [.iOS],
useXCFrameworks: false
)
let expected = """
github "Dependency/Dependency" == 2.1.1
github "XYZ/Foo" "revision"
git "Foo/Bar" >= 1.0.1
github "Qwerty/bar" "develop"
github "XYZ/Bar" ~> 1.1.1
binary "https://my.domain.com/release/MyFramework.json" ~> 1.0.1
binary "file:///some/local/path/MyFramework.json" >= 1.1.0
"""
// When
let got = carthageDependencies.cartfileValue()
// Then
XCTAssertEqual(got, expected)
}
// MARK: - CarthageDependency.Dependency tests
func test_dependency_cartfileValue_github() {
// Given
let origin: CarthageDependencies.Dependency = .github(path: "Alamofire/Alamofire", requirement: .exact("1.2.3"))
let expected = #"github "Alamofire/Alamofire" == 1.2.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_dependency_cartfileValue_git() {
// Given
let origin: CarthageDependencies.Dependency = .git(path: "https://enterprise.local/desktop/git-error-translations2.git", requirement: .atLeast("5.4.3"))
let expected = #"git "https://enterprise.local/desktop/git-error-translations2.git" >= 5.4.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_dependency_cartfileValue_binary() {
// Given
let origin: CarthageDependencies.Dependency = .binary(path: "file:///some/local/path/MyFramework.json", requirement: .upToNext("5.0.0"))
let expected = #"binary "file:///some/local/path/MyFramework.json" ~> 5.0.0"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
// MARK: - CarthageDependencies.Requirement tests
func test_requirement_cartfileValue_exact() {
// Given
let origin: CarthageDependencies.Requirement = .exact("1.2.3")
let expected = #"== 1.2.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_upToNext() {
// Given
let origin: CarthageDependencies.Requirement = .upToNext("3.2.3")
let expected = #"~> 3.2.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_atLeast() {
// Given
let origin: CarthageDependencies.Requirement = .atLeast("1.2.1")
let expected = #">= 1.2.1"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_branch() {
// Given
let origin: CarthageDependencies.Requirement = .branch("develop")
let expected = #""develop""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_revision() {
// Given
let origin: CarthageDependencies.Requirement = .revision("1234567898765432qwerty")
let expected = #""1234567898765432qwerty""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
}

View File

@ -1,118 +0,0 @@
import Foundation
import XCTest
@testable import TuistGraph
@testable import TuistSupportTesting
final class CarthageDependencyTests: TuistUnitTestCase {
func test_cartfileValue_github_exact() throws {
// Given
let dependency = CarthageDependency(origin: .github(path: "Alamofire/Alamofire"), requirement: .exact("1.2.3"), platforms: [.iOS])
let expected = #"github "Alamofire/Alamofire" == 1.2.3"#
// When
let got = dependency.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
// MARK: - CarthageDependency.Origin tests
func test_origin_cartfileValue_github() {
// Given
let origin: CarthageDependency.Origin = .github(path: "Alamofire/Alamofire")
let expected = #"github "Alamofire/Alamofire""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_origin_cartfileValue_git() {
// Given
let origin: CarthageDependency.Origin = .git(path: "https://enterprise.local/desktop/git-error-translations2.git")
let expected = #"git "https://enterprise.local/desktop/git-error-translations2.git""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_origin_cartfileValue_binary() {
// Given
let origin: CarthageDependency.Origin = .binary(path: "file:///some/local/path/MyFramework.json")
let expected = #"binary "file:///some/local/path/MyFramework.json""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
// MARK: - CarthageDependency.Requirement tests
func test_requirement_cartfileValue_exact() {
// Given
let origin: CarthageDependency.Requirement = .exact("1.2.3")
let expected = #"== 1.2.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_upToNext() {
// Given
let origin: CarthageDependency.Requirement = .upToNext("3.2.3")
let expected = #"~> 3.2.3"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_atLeast() {
// Given
let origin: CarthageDependency.Requirement = .atLeast("1.2.1")
let expected = #">= 1.2.1"#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_branch() {
// Given
let origin: CarthageDependency.Requirement = .branch("develop")
let expected = #""develop""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
func test_requirement_cartfileValue_revision() {
// Given
let origin: CarthageDependency.Requirement = .revision("1234567898765432qwerty")
let expected = #""1234567898765432qwerty""#
// When
let got = origin.cartfileValue
// Then
XCTAssertEqual(got, expected)
}
}

View File

@ -40,9 +40,13 @@ final class DependenciesFetchServiceTests: TuistUnitTestCase {
// Given
let stubbedPath = try temporaryPath()
let stubbedDependencies = Dependencies(
carthageDependencies: [
CarthageDependency(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: [.iOS, .macOS]),
]
carthage: .init(
[
.github(path: "Dependency1", requirement: .exact("1.1.1")),
],
platforms: [.iOS, .macOS],
useXCFrameworks: false
)
)
dependenciesModelLoader.loadDependenciesStub = { _ in stubbedDependencies }

View File

@ -40,9 +40,13 @@ final class DependenciesUpdateServiceTests: TuistUnitTestCase {
// Given
let stubbedPath = try temporaryPath()
let stubbedDependencies = Dependencies(
carthageDependencies: [
CarthageDependency(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: [.iOS, .macOS]),
]
carthage: .init(
[
.git(path: "Dependency1", requirement: .exact("1.1.1")),
],
platforms: [.iOS, .macOS],
useXCFrameworks: false
)
)
dependenciesModelLoader.loadDependenciesStub = { _ in stubbedDependencies }

View File

@ -34,22 +34,32 @@ final class DependenciesModelLoaderTests: TuistUnitTestCase {
// Given
let stubbedPath = try temporaryPath()
manifestLoader.loadDependenciesStub = { _ in
Dependencies([
.carthage(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: [.iOS]),
.carthage(origin: .git(path: "Dependency1"), requirement: .exact("2.3.4"), platforms: [.macOS, .tvOS]),
])
Dependencies(
carthage: .carthage(
[
.github(path: "Dependency1", requirement: .exact("1.1.1")),
.git(path: "Dependency1", requirement: .exact("2.3.4")),
],
platforms: [.iOS, .macOS],
useXCFrameworks: true
)
)
}
// When
let model = try subject.loadDependencies(at: stubbedPath)
// Then
let expectedCarthageModels: [TuistGraph.CarthageDependency] = [
CarthageDependency(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: Set([.iOS])),
CarthageDependency(origin: .git(path: "Dependency1"), requirement: .exact("2.3.4"), platforms: Set([.macOS, .tvOS])),
]
let expectedDependenciesModel = TuistGraph.Dependencies(carthageDependencies: expectedCarthageModels)
XCTAssertEqual(model, expectedDependenciesModel)
let expected: TuistGraph.Dependencies = .init(
carthage: .init(
[
.github(path: "Dependency1", requirement: .exact("1.1.1")),
.git(path: "Dependency1", requirement: .exact("2.3.4")),
],
platforms: [.iOS, .macOS],
useXCFrameworks: true
)
)
XCTAssertEqual(model, expected)
}
}

View File

@ -10,22 +10,35 @@ import XCTest
@testable import TuistSupportTesting
final class DependenciesManifestMapperTests: TuistUnitTestCase {
func test_dependencies() throws {
func test_from() throws {
// Given
let manifest: ProjectDescription.Dependencies = Dependencies([
.carthage(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: [.iOS]),
.carthage(origin: .git(path: "Dependency.git"), requirement: .branch("BranchName"), platforms: [.macOS]),
.carthage(origin: .binary(path: "DependencyXYZ"), requirement: .atLeast("2.3.1"), platforms: [.tvOS]),
])
let manifest: ProjectDescription.Dependencies = Dependencies(
carthage: .carthage(
[
.github(path: "Dependency1", requirement: .exact("1.1.1")),
.git(path: "Dependency.git", requirement: .branch("BranchName")),
.binary(path: "DependencyXYZ", requirement: .atLeast("2.3.1")),
],
platforms: [.iOS, .macOS, .tvOS],
useXCFrameworks: true
)
)
// When
let model = try TuistGraph.Dependencies.from(manifest: manifest)
// Then
XCTAssertEqual(model.carthageDependencies, [
TuistGraph.CarthageDependency(origin: .github(path: "Dependency1"), requirement: .exact("1.1.1"), platforms: Set([.iOS])),
TuistGraph.CarthageDependency(origin: .git(path: "Dependency.git"), requirement: .branch("BranchName"), platforms: Set([.macOS])),
TuistGraph.CarthageDependency(origin: .binary(path: "DependencyXYZ"), requirement: .atLeast("2.3.1"), platforms: Set([.tvOS])),
])
let expected: TuistGraph.Dependencies = .init(
carthage: .init(
[
.github(path: "Dependency1", requirement: .exact("1.1.1")),
.git(path: "Dependency.git", requirement: .branch("BranchName")),
.binary(path: "DependencyXYZ", requirement: .atLeast("2.3.1")),
],
platforms: [.iOS, .macOS, .tvOS],
useXCFrameworks: true
)
)
XCTAssertEqual(model, expected)
}
}

View File

@ -0,0 +1,68 @@
import Foundation
import struct TSCUtility.Version
import XCTest
@testable import TuistSupport
@testable import TuistSupportTesting
final class CarthageControllerTests: TuistUnitTestCase {
private var subject: CarthageController!
override func setUp() {
super.setUp()
subject = CarthageController()
}
override func tearDown() {
subject = nil
super.tearDown()
}
func test_canUseSystemCarthage_available() {
// Given
system.whichStub = { _ in "path" }
// When / Then
XCTAssertTrue(subject.canUseSystemCarthage())
}
func test_canUseSystemCarthage_unavailable() {
// Given
system.whichStub = { _ in throw NSError.test() }
// When / Then
XCTAssertFalse(subject.canUseSystemCarthage())
}
func test_carthageVersion_carthageNotFound() {
// Given
system.errorCommand("/usr/bin/env", "carthage", "version")
// When / Then
XCTAssertThrowsSpecific(try subject.carthageVersion(), CarthageControllerError.carthageNotFound)
}
func test_carthageVersion_success() {
// Given
system.stubs["/usr/bin/env carthage version"] = (stderror: nil, stdout: "0.37.0", exitstatus: 0)
// When / Then
XCTAssertEqual(try subject.carthageVersion(), Version(0, 37, 0))
}
func test_isXCFrameworksProductionSupported_notSupported() {
// Given
system.stubs["/usr/bin/env carthage version"] = (stderror: nil, stdout: "0.36.1", exitstatus: 0)
// When / Then
XCTAssertFalse(try subject.isXCFrameworksProductionSupported())
}
func test_isXCFrameworksProductionSupported_supported() {
// Given
system.stubs["/usr/bin/env carthage version"] = (stderror: nil, stdout: "0.37.0", exitstatus: 0)
// When / Then
XCTAssertTrue(try subject.isXCFrameworksProductionSupported())
}
}

View File

@ -5,7 +5,7 @@ Feature: Install dependencies Tuist.
And I have a working directory
Then I copy the fixture app_with_framework_and_tests_and_dependencies into the working directory
Then tuist fetches dependencies
Then a directory Tuist/Dependencies/Carthage/Mac/Alamofire.framework exists
Then a directory Tuist/Dependencies/Carthage/Alamofire.xcframework exists
Then a file Tuist/Dependencies/Carthage/.Alamofire.version exists
Then a file Tuist/Dependencies/Lockfiles/Cartfile.resolved exists

View File

@ -1,5 +1,11 @@
import ProjectDescription
let dependencies = Dependencies([
.carthage(origin: .github(path: "Alamofire/Alamofire"), requirement: .exact("5.0.4"), platforms: [.macOS])
])
let dependencies = Dependencies(
carthage: .carthage(
[
.github(path: "Alamofire/Alamofire", requirement: .exact("5.0.4"))
],
platforms: [.iOS, .macOS],
useXCFrameworks: true
)
)

View File

@ -32,9 +32,15 @@ The snippet below shows an example `Dependencies.swift` manifest file:
```swift
import ProjectDescription
let dependencies = Dependencies([
.carthage(origin: .github(path: "Alamofire/Alamofire"), requirement: .exact("5.0.4"), platforms: [.macOS])
])
let dependencies = Dependencies(
carthage: .carthage(
[
.github(path: "Alamofire/Alamofire", requirement: .exact("5.0.4"))
],
platforms: [.iOS, .macOS],
useXCFrameworks: true
)
)
```
### Step 2: "tuist dependencies" commands
@ -55,10 +61,7 @@ Tuist
|- Podfile.lock # coming soon
|- Package.resolved # coming soon
|- Carthage # stores content of `Carthage/Build` generated by `carthage`
|- iOS
|- Alamofire.framework
|- tvOS
|- Alamofire.framework
|- Alamofire.xcframework
|- Cocoapods # coming soon
|- RxSwift
|- SwiftPackageManager # coming soon
@ -67,10 +70,33 @@ Tuist
### Step 3: Link dependencies
You can link third-party dependencies like local ones.
Link pulled dependencies in your project manifest file.
1. Run `tuist edit` to start editing your manifest files.
2. Open `Project.swift`.
3. Define your project with dependencies.
The snippet below shows an example `Project.swift` manifest file:
```swift
let target = Target(dependencies: [
.framework("Tuist/Dependencies/Carthage/iOS/Alamofire.framework"),
])
import ProjectDescription
let project = Project(
name: "App",
organizationName: "tuist.io",
targets: [
Target(
name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.app",
deploymentTarget: .iOS(targetVersion: "13.0", devices: .iphone),
infoPlist: .default,
sources: ["Targets/App/Sources/**"],
dependencies: [
.xcFramework("Tuist/Dependencies/Carthage/Alamofire.xcframework"),
]
),
]
)
```