Merge branch 'main' into cli_tool
This commit is contained in:
commit
9916aa3d40
|
@ -35,3 +35,4 @@ jobs:
|
|||
uses: fortmarek/tapestry-action@0.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
version = 1
|
||||
|
||||
[update]
|
||||
always = true # default: false
|
||||
require_automerge_label = false # default: true
|
||||
|
||||
[approve]
|
||||
auto_approve_usernames = ["dependabot"]
|
|
@ -4,6 +4,10 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
|
||||
## Next
|
||||
|
||||
- Add `migration list-targets` command to show all targets sorted by number of dependencies [#1732](https://github.com/tuist/tuist/pull/1732) of a given project by [@andreacipriani](https://github.com/andreacipriani).
|
||||
|
||||
## 1.23.0
|
||||
|
||||
### Added
|
||||
|
||||
- Allow specifying Development Region via new `developmentRegion` parameter in `Config`s GenerationOption. [#1062](https://github.com/tuist/tuist/pull/1867) by [@svastven](https://github.com/svastven).
|
||||
|
@ -27,6 +31,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
|
||||
- Some renames in the generation logic to make the generation logic easier to reason about [#1942](https://github.com/tuist/tuist/pull/1942) by [@pepibumur](https://github.com/pepibumur).
|
||||
- Update some Swift dependencies [#1971](https://github.com/tuist/tuist/pull/1971) by [@pepibumur](https://github.com/pepibumur).
|
||||
- Improve hashing logic to account for files generated by mappers [#1977](https://github.com/tuist/tuist/pull/1977) by [@pepibumur](https://github.com/pepibumur).
|
||||
|
||||
## 1.22.0 - Heimat
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -7,7 +7,7 @@ gem "rake", "~> 13.0"
|
|||
gem "byebug", "~> 11.1"
|
||||
gem "minitest", "~> 5.14"
|
||||
gem "simctl", "~> 1.6"
|
||||
gem "rubocop", "~> 1.0.0"
|
||||
gem "rubocop", "~> 1.1.0"
|
||||
gem "encrypted-environment", "~> 0.2.0"
|
||||
gem "google-cloud-storage", "~> 1.29"
|
||||
gem "colorize", "~> 0.8.1"
|
||||
|
|
|
@ -171,16 +171,16 @@ GEM
|
|||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.4)
|
||||
rubocop (1.0.0)
|
||||
rubocop (1.1.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.1.5)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8)
|
||||
rexml
|
||||
rubocop-ast (>= 0.6.0)
|
||||
rubocop-ast (>= 1.0.1)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (1.0.1)
|
||||
rubocop-ast (1.1.0)
|
||||
parser (>= 2.7.1.5)
|
||||
ruby-macho (1.4.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
|
@ -223,7 +223,7 @@ DEPENDENCIES
|
|||
highline (~> 2.0)
|
||||
minitest (~> 5.14)
|
||||
rake (~> 13.0)
|
||||
rubocop (~> 1.0.0)
|
||||
rubocop (~> 1.1.0)
|
||||
rubyzip (~> 2.3.0)
|
||||
simctl (~> 1.6)
|
||||
xcodeproj (~> 1.19)
|
||||
|
|
|
@ -100,6 +100,15 @@
|
|||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Queuer",
|
||||
"repositoryURL": "https://github.com/FabrizioBrancati/Queuer.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "52515108d0ac4616d9e15ffcc7ad986e300d31ff",
|
||||
"version": "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "RxSwift",
|
||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||
|
@ -114,8 +123,8 @@
|
|||
"repositoryURL": "https://github.com/kylef/Spectre.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
|
||||
"version": "0.9.0"
|
||||
"revision": "f717bbce0e19f0129fc001b2b6bed43b70fd8b87",
|
||||
"version": "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -123,7 +132,7 @@
|
|||
"repositoryURL": "https://github.com/stencilproject/Stencil",
|
||||
"state": {
|
||||
"branch": "master",
|
||||
"revision": "124df01d3c5defdce07872fe1828c764bb969b38",
|
||||
"revision": "22440c53690c84603cea018a5204c0f1e770461d",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
|
|
|
@ -43,11 +43,12 @@ let package = Package(
|
|||
.package(url: "https://github.com/tuist/GraphViz.git", .branch("tuist")),
|
||||
.package(url: "https://github.com/fortmarek/SwiftGen", .branch("stable")),
|
||||
.package(url: "https://github.com/fortmarek/StencilSwiftKit.git", .branch("stable")),
|
||||
.package(url: "https://github.com/FabrizioBrancati/Queuer.git", .upToNextMajor(from: "2.0.0")),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "TuistCore",
|
||||
dependencies: ["SwiftToolsSupport-auto", "TuistSupport", "XcodeProj"]
|
||||
dependencies: ["SwiftToolsSupport-auto", "TuistSupport", "XcodeProj", "Checksum"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistCoreTesting",
|
||||
|
@ -75,11 +76,11 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistKit",
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "ArgumentParser", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "Checksum", "TuistLoader", "TuistInsights", "TuistScaffold", "TuistSigning", "TuistDependencies", "TuistCloud", "TuistDoc", "GraphViz", "TuistMigration"]
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "ArgumentParser", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "TuistLoader", "TuistInsights", "TuistScaffold", "TuistSigning", "TuistDependencies", "TuistCloud", "TuistDoc", "GraphViz", "TuistMigration", "TuistAsyncQueue"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistKitTests",
|
||||
dependencies: ["TuistKit", "TuistAutomation", "TuistSupportTesting", "TuistCoreTesting", "ProjectDescription", "RxBlocking", "TuistLoaderTesting", "TuistCacheTesting", "TuistGeneratorTesting", "TuistScaffoldTesting", "TuistCloudTesting", "TuistAutomationTesting", "TuistSigningTesting", "TuistDependenciesTesting", "TuistMigrationTesting", "TuistDocTesting"]
|
||||
dependencies: ["TuistKit", "TuistAutomation", "TuistSupportTesting", "TuistCoreTesting", "ProjectDescription", "RxBlocking", "TuistLoaderTesting", "TuistCacheTesting", "TuistGeneratorTesting", "TuistScaffoldTesting", "TuistCloudTesting", "TuistAutomationTesting", "TuistSigningTesting", "TuistDependenciesTesting", "TuistMigrationTesting", "TuistDocTesting", "TuistAsyncQueueTesting"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistKitIntegrationTests",
|
||||
|
@ -143,7 +144,7 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistCache",
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "TuistCore", "TuistSupport", "Checksum", "RxSwift"]
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "TuistCore", "TuistSupport", "RxSwift"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistCacheTests",
|
||||
|
@ -155,7 +156,7 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistCloud",
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "TuistCore", "TuistSupport", "Checksum", "RxSwift"]
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "TuistCore", "TuistSupport", "RxSwift"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistCloudTests",
|
||||
|
@ -261,6 +262,18 @@ let package = Package(
|
|||
name: "TuistMigrationIntegrationTests",
|
||||
dependencies: ["TuistMigration", "TuistSupportTesting", "TuistCoreTesting", "TuistMigrationTesting"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistAsyncQueue",
|
||||
dependencies: ["TuistCore", "TuistSupport", "XcodeProj", "SwiftToolsSupport-auto", "Queuer"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistAsyncQueueTesting",
|
||||
dependencies: ["TuistAsyncQueue"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "TuistAsyncQueueTests",
|
||||
dependencies: ["TuistAsyncQueue", "TuistSupportTesting", "TuistCoreTesting", "TuistAsyncQueueTesting", "RxBlocking"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistLoader",
|
||||
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "TuistCore", "TuistSupport", "ProjectDescription"]
|
||||
|
|
12
Rakefile
12
Rakefile
|
@ -81,9 +81,9 @@ task :local_package do
|
|||
end
|
||||
|
||||
desc("Builds, archives, and publishes tuist and tuistenv for release")
|
||||
task :release do
|
||||
task :release, [:version] do |task, options|
|
||||
decrypt_secrets
|
||||
release
|
||||
release(options[:version])
|
||||
end
|
||||
|
||||
desc("Publishes the installation scripts")
|
||||
|
@ -249,8 +249,12 @@ def package
|
|||
FileUtils.cp(".build/release/tuistenv.zip", "build/tuistenv.zip")
|
||||
end
|
||||
|
||||
def release
|
||||
version = cli.ask("Introduce the released version:")
|
||||
def release(version)
|
||||
if version == nil
|
||||
version = cli.ask("Introduce the released version:")
|
||||
end
|
||||
|
||||
puts "Releasing #{version} 🚀"
|
||||
|
||||
package
|
||||
|
||||
|
|
|
@ -8,9 +8,13 @@ public struct Dependency: Codable, Equatable {
|
|||
/// Type of requirement for the given dependency
|
||||
let requirement: Dependency.Requirement
|
||||
|
||||
public init(name: String, requirement: Dependency.Requirement) {
|
||||
/// Dependecy manager used to retrieve the dependecy
|
||||
public let manager: Dependency.Manager
|
||||
|
||||
public init(name: String, requirement: Dependency.Requirement, manager: Dependency.Manager) {
|
||||
self.name = name
|
||||
self.requirement = requirement
|
||||
self.manager = manager
|
||||
}
|
||||
|
||||
/// Carthage dependency initailizer
|
||||
|
@ -18,7 +22,7 @@ public struct Dependency: Codable, Equatable {
|
|||
/// - Parameter requirement: Type of requirement for the given dependency
|
||||
/// - Returns Dependency
|
||||
public static func carthage(name: String, requirement: Dependency.Requirement) -> Dependency {
|
||||
Dependency(name: name, requirement: requirement)
|
||||
Dependency(name: name, requirement: requirement, manager: .carthage)
|
||||
}
|
||||
|
||||
public static func == (lhs: Dependency, rhs: Dependency) -> Bool {
|
||||
|
@ -27,7 +31,7 @@ public struct Dependency: Codable, Equatable {
|
|||
}
|
||||
|
||||
public struct Dependencies: Codable, Equatable {
|
||||
private let dependencies: [Dependency]
|
||||
public let dependencies: [Dependency]
|
||||
|
||||
public init(_ dependencies: [Dependency]) {
|
||||
self.dependencies = dependencies
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import Foundation
|
||||
|
||||
public extension Dependency {
|
||||
enum Manager: String, Codable, Equatable {
|
||||
case carthage
|
||||
// case spm
|
||||
// case cocoapods
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
import Foundation
|
||||
import Queuer
|
||||
import RxSwift
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
public protocol AsyncQueuing {
|
||||
/// It dispatches the given event.
|
||||
/// - Parameter event: Event to be dispatched.
|
||||
func dispatch<T: AsyncQueueEvent>(event: T)
|
||||
}
|
||||
|
||||
public class AsyncQueue: AsyncQueuing {
|
||||
// MARK: - Attributes
|
||||
|
||||
public static var shared: AsyncQueuing!
|
||||
private let disposeBag: DisposeBag = DisposeBag()
|
||||
private let queue: Queuing
|
||||
private let ciChecker: CIChecking
|
||||
private let persistor: AsyncQueuePersisting
|
||||
private let dispatchers: [String: AsyncQueueDispatching]
|
||||
private let executionBlock: () throws -> Void
|
||||
private let persistedEventsSchedulerType: SchedulerType
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public convenience init(dispatchers: [AsyncQueueDispatching],
|
||||
executionBlock: @escaping () throws -> Void) throws
|
||||
{
|
||||
try self.init(queue: Queuer.shared,
|
||||
executionBlock: executionBlock,
|
||||
ciChecker: CIChecker(),
|
||||
persistor: AsyncQueuePersistor(),
|
||||
dispatchers: dispatchers)
|
||||
}
|
||||
|
||||
init(queue: Queuing,
|
||||
executionBlock: @escaping () throws -> Void,
|
||||
ciChecker: CIChecking,
|
||||
persistor: AsyncQueuePersisting,
|
||||
dispatchers: [AsyncQueueDispatching],
|
||||
persistedEventsSchedulerType: SchedulerType = AsyncQueue.schedulerType()) throws
|
||||
{
|
||||
self.queue = queue
|
||||
self.executionBlock = executionBlock
|
||||
self.ciChecker = ciChecker
|
||||
self.persistor = persistor
|
||||
self.dispatchers = dispatchers.reduce(into: [String: AsyncQueueDispatching]()) { $0[$1.identifier] = $1 }
|
||||
self.persistedEventsSchedulerType = persistedEventsSchedulerType
|
||||
try run()
|
||||
}
|
||||
|
||||
// MARK: - AsyncQueuing
|
||||
|
||||
public func dispatch<T: AsyncQueueEvent>(event: T) {
|
||||
guard let dispatcher = dispatchers[event.dispatcherId] else {
|
||||
logger.error("Couldn't find dispatcher with id: \(event.dispatcherId)")
|
||||
return
|
||||
}
|
||||
|
||||
// We persist the event in case the dispatching is halted because Tuist's
|
||||
// process exits. In that case we want to retry again the next time there's
|
||||
// opportunity for that.
|
||||
_ = persistor.write(event: event)
|
||||
|
||||
// Queue event to send
|
||||
let operation = liveDispatchOperation(event: event, dispatcher: dispatcher)
|
||||
queue.addOperation(operation)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func liveDispatchOperation<T: AsyncQueueEvent>(event: T, dispatcher: AsyncQueueDispatching) -> Operation {
|
||||
ConcurrentOperation(name: event.id.uuidString) { operation in
|
||||
logger.debug("Dispatching event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
|
||||
|
||||
do {
|
||||
try dispatcher.dispatch(event: event)
|
||||
operation.success = true
|
||||
|
||||
/// After the dispatching operation finishes, we delete the event locally.
|
||||
_ = self.persistor.delete(event: event)
|
||||
} catch {
|
||||
operation.success = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dispatchPersisted(eventTuple: AsyncQueueEventTuple) {
|
||||
guard let dispatcher = dispatchers.first(where: { $0.key == eventTuple.dispatcherId })?.value else {
|
||||
deletePersistedEvent(filename: eventTuple.filename)
|
||||
logger.error("Couldn't find dispatcher for persisted event with id: \(eventTuple.dispatcherId)")
|
||||
return
|
||||
}
|
||||
|
||||
let operation = persistedDispatchOperation(event: eventTuple, dispatcher: dispatcher)
|
||||
queue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func persistedDispatchOperation(event: AsyncQueueEventTuple,
|
||||
dispatcher: AsyncQueueDispatching) -> Operation
|
||||
{
|
||||
ConcurrentOperation(name: event.id.uuidString) { _ in
|
||||
/// After the dispatching operation finishes, we delete the event locally.
|
||||
defer { self.deletePersistedEvent(filename: event.filename) }
|
||||
|
||||
do {
|
||||
logger.debug("Dispatching persisted event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
|
||||
try dispatcher.dispatchPersisted(data: event.data)
|
||||
} catch {
|
||||
logger.debug("Failed to dispatch persisted event with ID '\(event.id.uuidString)' to '\(dispatcher.identifier)'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func run() throws {
|
||||
start()
|
||||
do {
|
||||
try executionBlock()
|
||||
waitIfCI()
|
||||
} catch {
|
||||
waitIfCI()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private func start() {
|
||||
loadEvents()
|
||||
queue.resume()
|
||||
}
|
||||
|
||||
private func waitIfCI() {
|
||||
if !ciChecker.isCI() { return }
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
}
|
||||
|
||||
private func loadEvents() {
|
||||
persistor
|
||||
.readAll()
|
||||
.subscribeOn(persistedEventsSchedulerType)
|
||||
.subscribe(onSuccess: { events in
|
||||
events.forEach(self.dispatchPersisted)
|
||||
}, onError: { error in
|
||||
logger.debug("Error loading persisted events: \(error)")
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func deletePersistedEvent(filename: String) {
|
||||
persistor.delete(filename: filename).subscribe().disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
// MARK: Private & Static
|
||||
|
||||
private static func dispatchQueue() -> DispatchQueue {
|
||||
DispatchQueue(label: "io.tuist.async-queue", qos: .background)
|
||||
}
|
||||
|
||||
private static func schedulerType() -> SchedulerType {
|
||||
ConcurrentDispatchQueueScheduler(queue: dispatchQueue())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
public typealias AsyncQueueEventTuple = (dispatcherId: String, id: UUID, date: Date, data: Data, filename: String)
|
||||
|
||||
public protocol AsyncQueuePersisting {
|
||||
/// Reads all the persisted events and returns them.
|
||||
func readAll() -> Single<[AsyncQueueEventTuple]>
|
||||
|
||||
/// Persiss a given event.
|
||||
/// - Parameter event: Event to be persisted.
|
||||
func write<T: AsyncQueueEvent>(event: T) -> Completable
|
||||
|
||||
/// Deletes the given event from disk.
|
||||
/// - Parameter event: Event to be deleted.
|
||||
func delete<T: AsyncQueueEvent>(event: T) -> Completable
|
||||
|
||||
/// Deletes the given file name from disk.
|
||||
/// - Parameter filename: Name of the file to be deleted.
|
||||
func delete(filename: String) -> Completable
|
||||
}
|
||||
|
||||
final class AsyncQueuePersistor: AsyncQueuePersisting {
|
||||
// MARK: - Attributes
|
||||
|
||||
let directory: AbsolutePath
|
||||
let jsonEncoder: JSONEncoder = JSONEncoder()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(directory: AbsolutePath = Environment.shared.queueDirectory) {
|
||||
self.directory = directory
|
||||
}
|
||||
|
||||
func write<T: AsyncQueueEvent>(event: T) -> Completable {
|
||||
Completable.create { (observer) -> Disposable in
|
||||
let path = self.directory.appending(component: self.filename(event: event))
|
||||
do {
|
||||
let data = try self.jsonEncoder.encode(event)
|
||||
try data.write(to: path.url)
|
||||
observer(.completed)
|
||||
} catch {
|
||||
observer(.error(error))
|
||||
}
|
||||
return Disposables.create()
|
||||
}
|
||||
}
|
||||
|
||||
func delete<T: AsyncQueueEvent>(event: T) -> Completable {
|
||||
delete(filename: filename(event: event))
|
||||
}
|
||||
|
||||
func delete(filename: String) -> Completable {
|
||||
Completable.create { (observer) -> Disposable in
|
||||
let path = self.directory.appending(component: filename)
|
||||
guard FileHandler.shared.exists(path) else { return Disposables.create() }
|
||||
do {
|
||||
try FileHandler.shared.delete(path)
|
||||
observer(.completed)
|
||||
} catch {
|
||||
observer(.error(error))
|
||||
}
|
||||
return Disposables.create()
|
||||
}
|
||||
}
|
||||
|
||||
func readAll() -> Single<[AsyncQueueEventTuple]> {
|
||||
Single.create { (observer) -> Disposable in
|
||||
let paths = FileHandler.shared.glob(self.directory, glob: "*.json")
|
||||
var events: [AsyncQueueEventTuple] = []
|
||||
paths.forEach { eventPath in
|
||||
let fileName = eventPath.basenameWithoutExt
|
||||
let components = fileName.split(separator: ".")
|
||||
guard components.count == 3,
|
||||
let timestamp = Double(components[0]),
|
||||
let id = UUID(uuidString: String(components[2]))
|
||||
else {
|
||||
/// Changing the naming convention is a breaking change. When detected
|
||||
/// we delete the event.
|
||||
try? FileHandler.shared.delete(eventPath)
|
||||
return
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: eventPath.url)
|
||||
let event = (dispatcherId: String(components[1]),
|
||||
id: id,
|
||||
date: Date(timeIntervalSince1970: timestamp),
|
||||
data: data,
|
||||
filename: eventPath.basename)
|
||||
events.append(event)
|
||||
} catch {
|
||||
try? FileHandler.shared.delete(eventPath)
|
||||
}
|
||||
}
|
||||
observer(.success(events))
|
||||
return Disposables.create()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func filename<T: AsyncQueueEvent>(event: T) -> String {
|
||||
"\(Int(event.date.timeIntervalSince1970)).\(event.dispatcherId).\(event.id.uuidString).json"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import TuistSupport
|
||||
let logger = Logger(label: "io.tuist.async-queue")
|
|
@ -0,0 +1,10 @@
|
|||
import Foundation
|
||||
import Queuer
|
||||
|
||||
public protocol Queuing {
|
||||
func addOperation(_ operation: Operation)
|
||||
func resume()
|
||||
func waitUntilAllOperationsAreFinished()
|
||||
}
|
||||
|
||||
extension Queuer: Queuing {}
|
|
@ -0,0 +1,59 @@
|
|||
import Foundation
|
||||
import TuistAsyncQueue
|
||||
import TuistCore
|
||||
|
||||
public enum MockAsyncQueueDispatcherError: Error {
|
||||
case dispatchError
|
||||
}
|
||||
|
||||
public class MockAsyncQueueDispatcher: AsyncQueueDispatching {
|
||||
public init() {}
|
||||
|
||||
public var invokedIdentifierGetter = false
|
||||
public var invokedIdentifierGetterCount = 0
|
||||
public var stubbedIdentifier: String! = ""
|
||||
|
||||
public var identifier: String {
|
||||
invokedIdentifierGetter = true
|
||||
invokedIdentifierGetterCount += 1
|
||||
return stubbedIdentifier
|
||||
}
|
||||
|
||||
public var invokedDispatch = false
|
||||
public var invokedDispatchCallBack: () -> Void = {}
|
||||
public var invokedDispatchCount = 0
|
||||
public var invokedDispatchParameterEvent: AsyncQueueEvent?
|
||||
public var invokedDispatchParametersEventsList = [AsyncQueueEvent]()
|
||||
public var stubbedDispatchError: Error?
|
||||
|
||||
public func dispatch(event: AsyncQueueEvent) throws {
|
||||
invokedDispatch = true
|
||||
invokedDispatchCount += 1
|
||||
invokedDispatchParameterEvent = event
|
||||
invokedDispatchParametersEventsList.append(event)
|
||||
if let error = stubbedDispatchError {
|
||||
invokedDispatchCallBack()
|
||||
throw error
|
||||
}
|
||||
invokedDispatchCallBack()
|
||||
}
|
||||
|
||||
public var invokedDispatchPersisted = false
|
||||
public var invokedDispatchPersistedCount = 0
|
||||
public var invokedDispatchPersistedCallBack: () -> Void = {}
|
||||
public var invokedDispatchPersistedDataParameter: Data?
|
||||
public var invokedDispatchPersistedParametersDataList = [Data]()
|
||||
public var stubbedDispatchPersistedError: Error?
|
||||
|
||||
public func dispatchPersisted(data: Data) throws {
|
||||
invokedDispatchPersisted = true
|
||||
invokedDispatchPersistedCount += 1
|
||||
invokedDispatchPersistedDataParameter = data
|
||||
invokedDispatchPersistedParametersDataList.append(data)
|
||||
if let error = stubbedDispatchPersistedError {
|
||||
invokedDispatchPersistedCallBack()
|
||||
throw error
|
||||
}
|
||||
invokedDispatchPersistedCallBack()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import TuistAsyncQueue
|
||||
import TuistCore
|
||||
|
||||
public final class MockAsyncQueuePersistor<U: AsyncQueueEvent>: AsyncQueuePersisting {
|
||||
public init() {}
|
||||
|
||||
public var invokedReadAll = false
|
||||
public var invokedReadAllCount = 0
|
||||
public var stubbedReadAllResult: Single<[AsyncQueueEventTuple]> = Single.just([])
|
||||
|
||||
public func readAll() -> Single<[AsyncQueueEventTuple]> {
|
||||
invokedReadAll = true
|
||||
invokedReadAllCount += 1
|
||||
return stubbedReadAllResult
|
||||
}
|
||||
|
||||
public var invokedWrite = false
|
||||
public var invokedWriteCount = 0
|
||||
public var invokedWriteEvent: U?
|
||||
public var invokedWriteEvents = [U]()
|
||||
public var stubbedWriteResult: Completable = .empty()
|
||||
|
||||
public func write<T: AsyncQueueEvent>(event: T) -> Completable {
|
||||
invokedWrite = true
|
||||
invokedWriteCount += 1
|
||||
if let event = event as? U {
|
||||
invokedWriteEvent = event
|
||||
invokedWriteEvents.append(event)
|
||||
}
|
||||
return stubbedWriteResult
|
||||
}
|
||||
|
||||
public var invokedDeleteEventCount = 0
|
||||
public var invokedDeleteCallBack: () -> Void = {}
|
||||
public var invokedDeleteEvent: U?
|
||||
public var invokedDeleteEvents = [U]()
|
||||
public var stubbedDeleteEventResult: Completable = .empty()
|
||||
|
||||
public func delete<T: AsyncQueueEvent>(event: T) -> Completable {
|
||||
invokedDeleteEventCount += 1
|
||||
if let event = event as? U {
|
||||
invokedDeleteEvent = event
|
||||
invokedDeleteEvents.append(event)
|
||||
}
|
||||
invokedDeleteCallBack()
|
||||
return stubbedDeleteEventResult
|
||||
}
|
||||
|
||||
public var invokedDeleteFilename = false
|
||||
public var invokedDeleteFilenameCount = 0
|
||||
public var invokedDeleteFilenameParameter: String?
|
||||
public var invokedDeleteFilenameParametersList = [String]()
|
||||
public var stubbedDeleteFilenameResult: Completable = .empty()
|
||||
|
||||
public func delete(filename: String) -> Completable {
|
||||
invokedDeleteFilename = true
|
||||
invokedDeleteFilenameCount += 1
|
||||
invokedDeleteFilenameParameter = filename
|
||||
invokedDeleteFilenameParametersList.append(filename)
|
||||
return stubbedDeleteFilenameResult
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Foundation
|
||||
import TuistAsyncQueue
|
||||
import TuistCore
|
||||
|
||||
public class MockAsyncQueuer: AsyncQueuing {
|
||||
public init() {}
|
||||
|
||||
public var invokedDispatch = false
|
||||
public var invokedDispatchCount = 0
|
||||
public var invokedDispatchParameters: (event: Any, Void)?
|
||||
public var invokedDispatchParametersList = [(event: Any, Void)]()
|
||||
|
||||
public func dispatch<T: AsyncQueueEvent>(event: T) {
|
||||
invokedDispatch = true
|
||||
invokedDispatchCount += 1
|
||||
invokedDispatchParameters = (event, ())
|
||||
invokedDispatchParametersList.append((event, ()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import Foundation
|
||||
import TuistAsyncQueue
|
||||
|
||||
public final class MockQueuer: Queuing {
|
||||
public init() {}
|
||||
|
||||
public var invokedAddOperation = false
|
||||
public var invokedAddOperationCount = 0
|
||||
public var invokedAddOperationParameterOperation: Operation?
|
||||
public var invokedAddOperationParametersOperationsList = [Operation]()
|
||||
|
||||
public func addOperation(_ operation: Operation) {
|
||||
invokedAddOperation = true
|
||||
invokedAddOperationCount += 1
|
||||
invokedAddOperationParameterOperation = operation
|
||||
invokedAddOperationParametersOperationsList.append(operation)
|
||||
}
|
||||
|
||||
public var invokedResume = false
|
||||
public var invokedResumeCount = 0
|
||||
|
||||
public func resume() {
|
||||
invokedResume = true
|
||||
invokedResumeCount += 1
|
||||
}
|
||||
|
||||
public var invokedWaitUntilAllOperationsAreFinished = false
|
||||
public var invokedWaitUntilAllOperationsAreFinishedCount = 0
|
||||
|
||||
public func waitUntilAllOperationsAreFinished() {
|
||||
invokedWaitUntilAllOperationsAreFinished = true
|
||||
invokedWaitUntilAllOperationsAreFinishedCount += 1
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
|
||||
/// `CacheContentHasher`
|
||||
/// is a wrapper on top of `ContentHasher` that adds an in-memory cache to avoid re-computing the same hashes
|
||||
|
@ -13,6 +14,10 @@ public final class CacheContentHasher: ContentHashing {
|
|||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
public func hash(_ data: Data) throws -> String {
|
||||
try contentHasher.hash(data)
|
||||
}
|
||||
|
||||
public func hash(_ string: String) throws -> String {
|
||||
try contentHasher.hash(string)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import Checksum
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
|
@ -16,9 +15,12 @@ public final class GraphContentHasher: GraphContentHashing {
|
|||
|
||||
// MARK: - Init
|
||||
|
||||
public init(
|
||||
targetContentHasher: TargetContentHashing = TargetContentHasher()
|
||||
) {
|
||||
public convenience init(contentHasher: ContentHashing) {
|
||||
let targetContentHasher = TargetContentHasher(contentHasher: contentHasher)
|
||||
self.init(contentHasher: contentHasher, targetContentHasher: targetContentHasher)
|
||||
}
|
||||
|
||||
public init(contentHasher _: ContentHashing, targetContentHasher: TargetContentHashing) {
|
||||
self.targetContentHasher = targetContentHasher
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
import TuistCore
|
||||
|
||||
public protocol SourceFilesContentHashing {
|
||||
func hash(sources: [Target.SourceFile]) throws -> String
|
||||
func hash(sources: [SourceFile]) throws -> String
|
||||
}
|
||||
|
||||
/// `SourceFilesContentHasher`
|
||||
|
@ -21,14 +21,18 @@ public final class SourceFilesContentHasher: SourceFilesContentHashing {
|
|||
/// Returns a unique hash that identifies an arry of sourceFiles
|
||||
/// First it hashes the content of every file and append to every hash the compiler flags of the file. It assumes the files are always sorted the same way.
|
||||
/// Then it hashes again all partial hashes to get a unique identifier that represents a group of source files together with their compiler flags
|
||||
public func hash(sources: [Target.SourceFile]) throws -> String {
|
||||
public func hash(sources: [SourceFile]) throws -> String {
|
||||
var stringsToHash: [String] = []
|
||||
for source in sources.sorted(by: { $0.path < $1.path }) {
|
||||
var sourceHash = try contentHasher.hash(path: source.path)
|
||||
if let compilerFlags = source.compilerFlags {
|
||||
sourceHash += try contentHasher.hash(compilerFlags)
|
||||
if let hash = source.contentHash {
|
||||
stringsToHash.append(hash)
|
||||
} else {
|
||||
var sourceHash = try contentHasher.hash(path: source.path)
|
||||
if let compilerFlags = source.compilerFlags {
|
||||
sourceHash += try contentHasher.hash(compilerFlags)
|
||||
}
|
||||
stringsToHash.append(sourceHash)
|
||||
}
|
||||
stringsToHash.append(sourceHash)
|
||||
}
|
||||
return try contentHasher.hash(stringsToHash)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
|
||||
// MARK: - Init
|
||||
|
||||
public convenience init(contentHasher: ContentHashing = CacheContentHasher()) {
|
||||
public convenience init(contentHasher: ContentHashing) {
|
||||
self.init(
|
||||
contentHasher: contentHasher,
|
||||
sourceFilesContentHasher: SourceFilesContentHasher(contentHasher: contentHasher),
|
||||
|
|
|
@ -34,11 +34,12 @@ public class CacheMapper: GraphMapping {
|
|||
public convenience init(config: Config,
|
||||
cacheStorageProvider: CacheStorageProviding,
|
||||
sources: Set<String>,
|
||||
cacheOutputType: CacheOutputType)
|
||||
cacheOutputType: CacheOutputType,
|
||||
contentHasher: ContentHashing)
|
||||
{
|
||||
self.init(config: config,
|
||||
cache: Cache(storageProvider: cacheStorageProvider),
|
||||
graphContentHasher: GraphContentHasher(),
|
||||
graphContentHasher: GraphContentHasher(contentHasher: contentHasher),
|
||||
sources: sources,
|
||||
cacheOutputType: cacheOutputType)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
/// Async queue dispatcher.
|
||||
public protocol AsyncQueueDispatching {
|
||||
/// Identifier.
|
||||
var identifier: String { get }
|
||||
|
||||
/// Dispatches a given event.
|
||||
/// - Parameter event: Event to be dispatched.
|
||||
func dispatch(event: AsyncQueueEvent) throws
|
||||
|
||||
/// Dispatch a persisted event.
|
||||
/// - Parameter data: Serialized data of the event.
|
||||
func dispatchPersisted(data: Data) throws
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import Foundation
|
||||
|
||||
public protocol AsyncQueueEvent: Codable {
|
||||
/// Unique identifier.
|
||||
var id: UUID { get }
|
||||
|
||||
/// The identifier of the dispatcher that should process this event.
|
||||
var dispatcherId: String { get }
|
||||
|
||||
/// Event date.
|
||||
var date: Date { get }
|
||||
}
|
||||
|
||||
public struct AnyAsyncQueueEvent: AsyncQueueEvent {
|
||||
public let id: UUID
|
||||
public let dispatcherId: String
|
||||
public let date: Date
|
||||
|
||||
public init(id: UUID = UUID(),
|
||||
dispatcherId: String,
|
||||
date: Date = Date())
|
||||
{
|
||||
self.id = id
|
||||
self.dispatcherId = dispatcherId
|
||||
self.date = date
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
public protocol FileContentHashing {
|
||||
func hash(path: AbsolutePath) throws -> String
|
||||
}
|
|
@ -1,18 +1,8 @@
|
|||
import Checksum
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
public protocol FileContentHashing {
|
||||
func hash(path: AbsolutePath) throws -> String
|
||||
}
|
||||
|
||||
public protocol ContentHashing: FileContentHashing {
|
||||
func hash(_ string: String) throws -> String
|
||||
func hash(_ strings: [String]) throws -> String
|
||||
func hash(_ dictionary: [String: String]) throws -> String
|
||||
}
|
||||
|
||||
/// `ContentHasher`
|
||||
/// is the single source of truth for hashing content.
|
||||
/// It uses md5 checksum to uniquely hash strings and data
|
||||
|
@ -26,6 +16,13 @@ public final class ContentHasher: ContentHashing {
|
|||
|
||||
// MARK: - ContentHashing
|
||||
|
||||
public func hash(_ data: Data) throws -> String {
|
||||
guard let hash = data.checksum(algorithm: .md5) else {
|
||||
throw ContentHashingError.dataHashingFailed
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
public func hash(_ string: String) throws -> String {
|
||||
guard let hash = string.checksum(algorithm: .md5) else {
|
||||
throw ContentHashingError.stringHashingFailed(string)
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
public protocol ContentHashing: FileContentHashing {
|
||||
func hash(_ data: Data) throws -> String
|
||||
func hash(_ string: String) throws -> String
|
||||
func hash(_ strings: [String]) throws -> String
|
||||
func hash(_ dictionary: [String: String]) throws -> String
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
/// `ContentHashingError`
|
||||
/// defines all the errors that can happen while cashing the content of a target
|
||||
enum ContentHashingError: FatalError, Equatable {
|
||||
public enum ContentHashingError: FatalError, Equatable {
|
||||
case failedToReadFile(AbsolutePath)
|
||||
case fileHashingFailed(AbsolutePath)
|
||||
case stringHashingFailed(String)
|
||||
case dataHashingFailed
|
||||
|
||||
var type: ErrorType {
|
||||
public var type: ErrorType {
|
||||
.abort
|
||||
}
|
||||
|
||||
var description: String {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .failedToReadFile(path):
|
||||
return "Couldn't find file to calculate hash at path \(path.pathString)"
|
||||
|
@ -22,10 +22,12 @@ enum ContentHashingError: FatalError, Equatable {
|
|||
return "Couldn't calculate hash of file at path \(path.pathString)"
|
||||
case let .stringHashingFailed(string):
|
||||
return "Couldn't calculate hash of string \(string) for caching."
|
||||
case .dataHashingFailed:
|
||||
return "Couldn't get the hash of a data object."
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: ContentHashingError, rhs: ContentHashingError) -> Bool {
|
||||
public static func == (lhs: ContentHashingError, rhs: ContentHashingError) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.failedToReadFile(lhsPath), .failedToReadFile(rhsPath)):
|
||||
return lhsPath == rhsPath
|
||||
|
@ -33,6 +35,8 @@ enum ContentHashingError: FatalError, Equatable {
|
|||
return lhsPath == rhsPath
|
||||
case let (.stringHashingFailed(lhsPath), .stringHashingFailed(rhsPath)):
|
||||
return lhsPath == rhsPath
|
||||
case (.dataHashingFailed, .dataHashingFailed):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
/// A type that represents a source file.
|
||||
public struct SourceFile: ExpressibleByStringLiteral, Equatable {
|
||||
/// Source file path.
|
||||
public var path: AbsolutePath
|
||||
|
||||
/// Compiler flags
|
||||
/// When source files are added to a target, they can contain compiler flags that Xcode's build system
|
||||
/// passes to the compiler when compiling those files. By default none is passed.
|
||||
public var compilerFlags: String?
|
||||
|
||||
/// This is intended to be used by the mappers that generate files through side effects.
|
||||
/// This attribute is used by the content hasher used by the caching functionality.
|
||||
public var contentHash: String?
|
||||
|
||||
public init(path: AbsolutePath,
|
||||
compilerFlags: String? = nil,
|
||||
contentHash: String? = nil)
|
||||
{
|
||||
self.path = path
|
||||
self.compilerFlags = compilerFlags
|
||||
self.contentHash = contentHash
|
||||
}
|
||||
|
||||
// MARK: - ExpressibleByStringLiteral
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
path = AbsolutePath(value)
|
||||
compilerFlags = nil
|
||||
contentHash = nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import Foundation
|
||||
|
||||
/// A type that represents a list of source files defined by a glob.
|
||||
public struct SourceFileGlob: Equatable {
|
||||
/// Glob pattern to unfold all the source files.
|
||||
public var glob: String
|
||||
|
||||
/// Glob pattern used for filtering out files
|
||||
public var excluding: [String]
|
||||
|
||||
/// Compiler flags.
|
||||
public var compilerFlags: String?
|
||||
|
||||
/// Initializes the source file glob.
|
||||
/// - Parameters:
|
||||
/// - glob: Glob pattern to unfold all the source files.
|
||||
/// - excluding: Glob pattern used for filtering out files.
|
||||
/// - compilerFlags: Compiler flags.
|
||||
public init(glob: String,
|
||||
excluding: [String] = [],
|
||||
compilerFlags: String? = nil)
|
||||
{
|
||||
self.glob = glob
|
||||
self.excluding = excluding
|
||||
self.compilerFlags = compilerFlags
|
||||
}
|
||||
}
|
|
@ -16,9 +16,6 @@ public enum TargetError: FatalError, Equatable {
|
|||
}
|
||||
|
||||
public struct Target: Equatable, Hashable, Comparable {
|
||||
public typealias SourceFile = (path: AbsolutePath, compilerFlags: String?)
|
||||
public typealias SourceFileGlob = (glob: String, excluding: [String], compilerFlags: String?)
|
||||
|
||||
// MARK: - Static
|
||||
|
||||
public static let validSourceExtensions: [String] = ["m", "swift", "mm", "cpp", "c", "d", "intentdefinition", "xcmappingmodel", "metal"]
|
||||
|
@ -197,8 +194,8 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
/// This method unfolds the source file globs subtracting the paths that are excluded and ignoring
|
||||
/// the files that don't have a supported source extension.
|
||||
/// - Parameter sources: List of source file glob to be unfolded.
|
||||
public static func sources(targetName: String, sources: [SourceFileGlob]) throws -> [TuistCore.Target.SourceFile] {
|
||||
var sourceFiles: [AbsolutePath: TuistCore.Target.SourceFile] = [:]
|
||||
public static func sources(targetName: String, sources: [SourceFileGlob]) throws -> [TuistCore.SourceFile] {
|
||||
var sourceFiles: [AbsolutePath: TuistCore.SourceFile] = [:]
|
||||
var invalidGlobs: [InvalidGlob] = []
|
||||
|
||||
try sources.forEach { source in
|
||||
|
@ -229,7 +226,7 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
return true
|
||||
}
|
||||
return false
|
||||
}.forEach { sourceFiles[$0] = (path: $0, compilerFlags: source.compilerFlags) }
|
||||
}.forEach { sourceFiles[$0] = SourceFile(path: $0, compilerFlags: source.compilerFlags) }
|
||||
}
|
||||
|
||||
if !invalidGlobs.isEmpty {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
public final class MockAsyncQueueDispatcher: AsyncQueueDispatching {
|
||||
init() {}
|
||||
|
||||
public var invokedIdentifierGetter = false
|
||||
public var invokedIdentifierGetterCount = 0
|
||||
public var stubbedIdentifier: String! = ""
|
||||
|
||||
public var identifier: String {
|
||||
invokedIdentifierGetter = true
|
||||
invokedIdentifierGetterCount += 1
|
||||
return stubbedIdentifier
|
||||
}
|
||||
|
||||
public var invokedDispatch = false
|
||||
public var invokedDispatchCount = 0
|
||||
public var invokedDispatchParameters: (event: AsyncQueueEvent, Void)?
|
||||
public var invokedDispatchParametersList = [(event: AsyncQueueEvent, Void)]()
|
||||
public var stubbedDispatchError: Error?
|
||||
|
||||
public func dispatch(event: AsyncQueueEvent) throws {
|
||||
invokedDispatch = true
|
||||
invokedDispatchCount += 1
|
||||
invokedDispatchParameters = (event, ())
|
||||
invokedDispatchParametersList.append((event, ()))
|
||||
if let error = stubbedDispatchError {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public var invokedDispatchPersisted = false
|
||||
public var invokedDispatchPersistedCount = 0
|
||||
public var invokedDispatchPersistedParameters: (data: Data, Void)?
|
||||
public var invokedDispatchPersistedParametersList = [(data: Data, Void)]()
|
||||
public var stubbedDispatchPersistedError: Error?
|
||||
|
||||
public func dispatchPersisted(data: Data) throws {
|
||||
invokedDispatchPersisted = true
|
||||
invokedDispatchPersistedCount += 1
|
||||
invokedDispatchPersistedParameters = (data, ())
|
||||
invokedDispatchPersistedParametersList.append((data, ()))
|
||||
if let error = stubbedDispatchPersistedError {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,18 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
|
||||
public class MockContentHashing: ContentHashing {
|
||||
public init() {}
|
||||
|
||||
public var hashDataSpy: Data?
|
||||
public var hashDataCallCount = 0
|
||||
public func hash(_ data: Data) throws -> String {
|
||||
hashDataSpy = data
|
||||
hashDataCallCount += 1
|
||||
return "\(String(describing: hashDataSpy?.base64EncodedString()))-hash"
|
||||
}
|
||||
|
||||
public var hashStringSpy: String?
|
||||
public var hashStringCallCount = 0
|
||||
public func hash(_ string: String) throws -> String {
|
|
@ -14,7 +14,7 @@ public extension Target {
|
|||
infoPlist: InfoPlist? = nil,
|
||||
entitlements: AbsolutePath? = nil,
|
||||
settings: Settings? = Settings.test(),
|
||||
sources: [Target.SourceFile] = [],
|
||||
sources: [SourceFile] = [],
|
||||
resources: [FileElement] = [],
|
||||
coreDataModels: [CoreDataModel] = [],
|
||||
headers: Headers? = nil,
|
||||
|
@ -54,7 +54,7 @@ public extension Target {
|
|||
infoPlist: InfoPlist? = nil,
|
||||
entitlements: AbsolutePath? = nil,
|
||||
settings: Settings? = nil,
|
||||
sources: [Target.SourceFile] = [],
|
||||
sources: [SourceFile] = [],
|
||||
resources: [FileElement] = [],
|
||||
coreDataModels: [CoreDataModel] = [],
|
||||
headers: Headers? = nil,
|
||||
|
|
|
@ -152,7 +152,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating {
|
|||
}
|
||||
}
|
||||
|
||||
func generateSourcesBuildPhase(files: [Target.SourceFile],
|
||||
func generateSourcesBuildPhase(files: [SourceFile],
|
||||
coreDataModels: [CoreDataModel],
|
||||
pbxTarget: PBXTarget,
|
||||
fileElements: ProjectFileElements,
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ResourcesProjectMapper: ProjectMapping {
|
|||
|
||||
if target.supportsSources {
|
||||
let (filePath, fileDescriptors) = synthesizedFile(bundleName: bundleName, target: target, project: project)
|
||||
modifiedTarget.sources.append((path: filePath, compilerFlags: nil))
|
||||
modifiedTarget.sources.append(SourceFile(path: filePath, compilerFlags: nil))
|
||||
sideEffects.append(contentsOf: fileDescriptors)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,19 @@ import TuistSupport
|
|||
/// A project mapper that synthezies resource interfaces
|
||||
public final class SynthesizedResourceInterfaceProjectMapper: ProjectMapping {
|
||||
private let synthesizedResourceInterfacesGenerator: SynthesizedResourceInterfacesGenerating
|
||||
private let contentHasher: ContentHashing
|
||||
|
||||
public convenience init() {
|
||||
self.init(synthesizedResourceInterfacesGenerator: SynthesizedResourceInterfacesGenerator())
|
||||
public convenience init(contentHasher: ContentHashing) {
|
||||
self.init(synthesizedResourceInterfacesGenerator: SynthesizedResourceInterfacesGenerator(),
|
||||
contentHasher: contentHasher)
|
||||
}
|
||||
|
||||
init(
|
||||
synthesizedResourceInterfacesGenerator: SynthesizedResourceInterfacesGenerating
|
||||
synthesizedResourceInterfacesGenerator: SynthesizedResourceInterfacesGenerating,
|
||||
contentHasher: ContentHashing
|
||||
) {
|
||||
self.synthesizedResourceInterfacesGenerator = synthesizedResourceInterfacesGenerator
|
||||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
public func map(project: Project) throws -> (Project, [SideEffectDescriptor]) {
|
||||
|
@ -145,9 +149,11 @@ public final class SynthesizedResourceInterfaceProjectMapper: ProjectMapping {
|
|||
|
||||
var target = target
|
||||
|
||||
target.sources += renderedResources
|
||||
.map(\.path)
|
||||
.map { (path: $0, compilerFlags: nil) }
|
||||
target.sources += try renderedResources
|
||||
.map { resource in
|
||||
let hash = try resource.contents.map(contentHasher.hash)
|
||||
return SourceFile(path: resource.path, contentHash: hash)
|
||||
}
|
||||
|
||||
let sideEffects = renderedResources
|
||||
.map { FileDescriptor(path: $0.path, contents: $0.contents) }
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
import TuistAsyncQueue
|
||||
|
||||
public extension AsyncQueue {
|
||||
class func run(executionBlock: @escaping () throws -> Void) throws {
|
||||
try AsyncQueue.shared = AsyncQueue(dispatchers: [], executionBlock: executionBlock)
|
||||
}
|
||||
}
|
|
@ -13,8 +13,13 @@ import TuistSupport
|
|||
/// A provider that concatenates the default mappers, to the mapper that adds the build phase
|
||||
/// to locate the built products directory.
|
||||
class CacheControllerProjectMapperProvider: ProjectMapperProviding {
|
||||
fileprivate let contentHasher: ContentHashing
|
||||
init(contentHasher: ContentHashing) {
|
||||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
func mapper(config: Config) -> ProjectMapping {
|
||||
let defaultProjectMapperProvider = ProjectMapperProvider()
|
||||
let defaultProjectMapperProvider = ProjectMapperProvider(contentHasher: contentHasher)
|
||||
let defaultMapper = defaultProjectMapperProvider.mapper(config: config)
|
||||
return SequentialProjectMapper(mappers: [defaultMapper, CacheBuildPhaseProjectMapper()])
|
||||
}
|
||||
|
@ -28,10 +33,18 @@ protocol CacheControllerProjectGeneratorProviding {
|
|||
|
||||
/// A provider that returns the project generator that should be used by the cache controller.
|
||||
class CacheControllerProjectGeneratorProvider: CacheControllerProjectGeneratorProviding {
|
||||
fileprivate let contentHasher: ContentHashing
|
||||
init(contentHasher: ContentHashing) {
|
||||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
func generator() -> Generating {
|
||||
let projectMapperProvider = CacheControllerProjectMapperProvider()
|
||||
let contentHasher = CacheContentHasher()
|
||||
let projectMapperProvider = CacheControllerProjectMapperProvider(contentHasher: contentHasher)
|
||||
return Generator(projectMapperProvider: projectMapperProvider,
|
||||
workspaceMapperProvider: WorkspaceMapperProvider(projectMapperProvider: projectMapperProvider))
|
||||
graphMapperProvider: GraphMapperProvider(),
|
||||
workspaceMapperProvider: WorkspaceMapperProvider(contentHasher: contentHasher),
|
||||
manifestLoaderFactory: ManifestLoaderFactory())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,11 +68,14 @@ final class CacheController: CacheControlling {
|
|||
/// Cache.
|
||||
private let cache: CacheStoring
|
||||
|
||||
convenience init(cache: CacheStoring, artifactBuilder: CacheArtifactBuilding) {
|
||||
convenience init(cache: CacheStoring,
|
||||
artifactBuilder: CacheArtifactBuilding,
|
||||
contentHasher: ContentHashing)
|
||||
{
|
||||
self.init(cache: cache,
|
||||
artifactBuilder: artifactBuilder,
|
||||
projectGeneratorProvider: CacheControllerProjectGeneratorProvider(),
|
||||
graphContentHasher: GraphContentHasher())
|
||||
projectGeneratorProvider: CacheControllerProjectGeneratorProvider(contentHasher: contentHasher),
|
||||
graphContentHasher: GraphContentHasher(contentHasher: contentHasher))
|
||||
}
|
||||
|
||||
init(cache: CacheStoring,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import TuistAutomation
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
|
||||
/// A factory that returns cache controllers for different type of pre-built artifacts.
|
||||
final class CacheControllerFactory {
|
||||
|
@ -13,16 +14,18 @@ final class CacheControllerFactory {
|
|||
}
|
||||
|
||||
/// Returns a cache controller that uses frameworks built for the simulator architecture.
|
||||
/// - Parameter contentHasher: Content hasher.
|
||||
/// - Returns: A cache controller instance.
|
||||
func makeForSimulatorFramework() -> CacheControlling {
|
||||
func makeForSimulatorFramework(contentHasher: ContentHashing) -> CacheControlling {
|
||||
let frameworkBuilder = CacheFrameworkBuilder(xcodeBuildController: XcodeBuildController())
|
||||
return CacheController(cache: cache, artifactBuilder: frameworkBuilder)
|
||||
return CacheController(cache: cache, artifactBuilder: frameworkBuilder, contentHasher: contentHasher)
|
||||
}
|
||||
|
||||
/// Returns a cache controller that uses xcframeworks built for the simulator and device architectures.
|
||||
/// - Returns: A cache controller instance.
|
||||
func makeForXCFramework() -> CacheControlling {
|
||||
/// - Parameter contentHasher: Content hasher.
|
||||
/// - Returns: Instance of the cache controller.
|
||||
func makeForXCFramework(contentHasher: ContentHashing) -> CacheControlling {
|
||||
let frameworkBuilder = CacheXCFrameworkBuilder(xcodeBuildController: XcodeBuildController())
|
||||
return CacheController(cache: cache, artifactBuilder: frameworkBuilder)
|
||||
return CacheController(cache: cache, artifactBuilder: frameworkBuilder, contentHasher: contentHasher)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import ArgumentParser
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
struct MigrationTargetsByDependenciesCommand: ParsableCommand {
|
||||
static var configuration: CommandConfiguration {
|
||||
CommandConfiguration(commandName: "list-targets",
|
||||
abstract: "It lists the targets of a project sorted by number of dependencies.")
|
||||
}
|
||||
|
||||
@Option(
|
||||
name: [.customShort("p"), .long],
|
||||
help: "The path to the Xcode project",
|
||||
completion: .directory
|
||||
)
|
||||
var xcodeprojPath: String
|
||||
|
||||
func run() throws {
|
||||
try MigrationTargetsByDependenciesService().run(xcodeprojPath: AbsolutePath(xcodeprojPath, relativeTo: FileHandler.shared.currentPath))
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ struct MigrationCommand: ParsableCommand {
|
|||
abstract: "A set of utilities to assist on the migration of Xcode projects to Tuist.", subcommands: [
|
||||
MigrationSettingsToXCConfigCommand.self,
|
||||
MigrationCheckEmptyBuildSettingsCommand.self,
|
||||
MigrationTargetsByDependenciesCommand.self,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,17 @@ class Generator: Generating {
|
|||
private let workspaceMapperProvider: WorkspaceMapperProviding
|
||||
private let manifestLoader: ManifestLoading
|
||||
|
||||
init(graphMapperProvider: GraphMapperProviding = GraphMapperProvider(),
|
||||
projectMapperProvider: ProjectMapperProviding = ProjectMapperProvider(),
|
||||
workspaceMapperProvider: WorkspaceMapperProviding = WorkspaceMapperProvider(),
|
||||
manifestLoaderFactory: ManifestLoaderFactory = ManifestLoaderFactory())
|
||||
convenience init(contentHasher: ContentHashing) {
|
||||
self.init(projectMapperProvider: ProjectMapperProvider(contentHasher: contentHasher),
|
||||
graphMapperProvider: GraphMapperProvider(),
|
||||
workspaceMapperProvider: WorkspaceMapperProvider(contentHasher: contentHasher),
|
||||
manifestLoaderFactory: ManifestLoaderFactory())
|
||||
}
|
||||
|
||||
init(projectMapperProvider: ProjectMapperProviding,
|
||||
graphMapperProvider: GraphMapperProviding,
|
||||
workspaceMapperProvider: WorkspaceMapperProviding,
|
||||
manifestLoaderFactory: ManifestLoaderFactory)
|
||||
{
|
||||
let manifestLoader = manifestLoaderFactory.createManifestLoader()
|
||||
recursiveManifestLoader = RecursiveManifestLoader(manifestLoader: manifestLoader)
|
||||
|
|
|
@ -13,6 +13,15 @@ protocol ProjectMapperProviding {
|
|||
}
|
||||
|
||||
final class ProjectMapperProvider: ProjectMapperProviding {
|
||||
/// Content hasher.
|
||||
private let contentHasher: ContentHashing
|
||||
|
||||
/// Initializes the project mapper provider.
|
||||
/// - Parameter contentHasher: Content hasher.
|
||||
init(contentHasher: ContentHashing) {
|
||||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
func mapper(config: Config) -> ProjectMapping {
|
||||
var mappers: [ProjectMapping] = []
|
||||
|
||||
|
@ -26,7 +35,7 @@ final class ProjectMapperProvider: ProjectMapperProviding {
|
|||
|
||||
// Namespace generator
|
||||
if !config.generationOptions.contains(.disableSynthesizedResourceAccessors) {
|
||||
mappers.append(SynthesizedResourceInterfaceProjectMapper())
|
||||
mappers.append(SynthesizedResourceInterfaceProjectMapper(contentHasher: contentHasher))
|
||||
}
|
||||
|
||||
// Logfile noise suppression
|
||||
|
|
|
@ -8,7 +8,12 @@ protocol WorkspaceMapperProviding {
|
|||
|
||||
final class WorkspaceMapperProvider: WorkspaceMapperProviding {
|
||||
private let projectMapperProvider: ProjectMapperProviding
|
||||
init(projectMapperProvider: ProjectMapperProviding = ProjectMapperProvider()) {
|
||||
|
||||
convenience init(contentHasher: ContentHashing) {
|
||||
self.init(projectMapperProvider: ProjectMapperProvider(contentHasher: contentHasher))
|
||||
}
|
||||
|
||||
init(projectMapperProvider: ProjectMapperProviding) {
|
||||
self.projectMapperProvider = projectMapperProvider
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@ enum ProjectEditorError: FatalError, Equatable {
|
|||
protocol ProjectEditing: AnyObject {
|
||||
/// Generates an Xcode project to edit the Project defined in the given directory.
|
||||
/// - Parameters:
|
||||
/// - at: Directory whose project will be edited.
|
||||
/// - editingPath: Directory whose project will be edited.
|
||||
/// - destinationDirectory: Directory in which the Xcode project will be generated.
|
||||
/// - Returns: The path to the generated Xcode project.
|
||||
func edit(at: AbsolutePath, in destinationDirectory: AbsolutePath) throws -> AbsolutePath
|
||||
func edit(at editingPath: AbsolutePath, in destinationDirectory: AbsolutePath) throws -> AbsolutePath
|
||||
}
|
||||
|
||||
final class ProjectEditor: ProjectEditing {
|
||||
|
@ -87,36 +87,38 @@ final class ProjectEditor: ProjectEditing {
|
|||
self.sideEffectDescriptorExecutor = sideEffectDescriptorExecutor
|
||||
}
|
||||
|
||||
func edit(at: AbsolutePath, in dstDirectory: AbsolutePath) throws -> AbsolutePath {
|
||||
func edit(at editingPath: AbsolutePath, in dstDirectory: AbsolutePath) throws -> AbsolutePath {
|
||||
let xcodeprojPath = dstDirectory.appending(component: "Manifests.xcodeproj")
|
||||
|
||||
let projectDesciptionPath = try resourceLocator.projectDescription()
|
||||
let manifests = manifestFilesLocator.locateAllProjectManifests(at: at)
|
||||
let configPath = manifestFilesLocator.locateConfig(at: at)
|
||||
let setupPath = manifestFilesLocator.locateSetup(at: at)
|
||||
let manifests = manifestFilesLocator.locateAllProjectManifests(at: editingPath)
|
||||
let configPath = manifestFilesLocator.locateConfig(at: editingPath)
|
||||
let dependenciesPath = manifestFilesLocator.locateDependencies(at: editingPath)
|
||||
let setupPath = manifestFilesLocator.locateSetup(at: editingPath)
|
||||
var helpers: [AbsolutePath] = []
|
||||
if let helpersDirectory = helpersDirectoryLocator.locate(at: at) {
|
||||
if let helpersDirectory = helpersDirectoryLocator.locate(at: editingPath) {
|
||||
helpers = FileHandler.shared.glob(helpersDirectory, glob: "**/*.swift")
|
||||
}
|
||||
var templates: [AbsolutePath] = []
|
||||
if let templatesDirectory = templatesDirectoryLocator.locateUserTemplates(at: at) {
|
||||
if let templatesDirectory = templatesDirectoryLocator.locateUserTemplates(at: editingPath) {
|
||||
templates = FileHandler.shared.glob(templatesDirectory, glob: "**/*.swift")
|
||||
+ FileHandler.shared.glob(templatesDirectory, glob: "**/*.stencil")
|
||||
}
|
||||
|
||||
/// We error if the user tries to edit a project in a directory where there are no editable files.
|
||||
if manifests.isEmpty, helpers.isEmpty, templates.isEmpty {
|
||||
throw ProjectEditorError.noEditableFiles(at)
|
||||
throw ProjectEditorError.noEditableFiles(editingPath)
|
||||
}
|
||||
|
||||
// To be sure that we are using the same binary of Tuist that invoked `edit`
|
||||
let tuistPath = AbsolutePath(TuistCommand.processArguments()!.first!)
|
||||
|
||||
let (project, graph) = try projectEditorMapper.map(tuistPath: tuistPath,
|
||||
sourceRootPath: at,
|
||||
sourceRootPath: editingPath,
|
||||
xcodeProjPath: xcodeprojPath,
|
||||
setupPath: setupPath,
|
||||
configPath: configPath,
|
||||
dependenciesPath: dependenciesPath,
|
||||
manifests: manifests.map { $0.1 },
|
||||
helpers: helpers,
|
||||
templates: templates,
|
||||
|
|
|
@ -9,6 +9,7 @@ protocol ProjectEditorMapping: AnyObject {
|
|||
xcodeProjPath: AbsolutePath,
|
||||
setupPath: AbsolutePath?,
|
||||
configPath: AbsolutePath?,
|
||||
dependenciesPath: AbsolutePath?,
|
||||
manifests: [AbsolutePath],
|
||||
helpers: [AbsolutePath],
|
||||
templates: [AbsolutePath],
|
||||
|
@ -22,6 +23,7 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
xcodeProjPath: AbsolutePath,
|
||||
setupPath: AbsolutePath?,
|
||||
configPath: AbsolutePath?,
|
||||
dependenciesPath: AbsolutePath?,
|
||||
manifests: [AbsolutePath],
|
||||
helpers: [AbsolutePath],
|
||||
templates: [AbsolutePath],
|
||||
|
@ -43,41 +45,42 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
manifestsDependencies = [.target(name: "ProjectDescriptionHelpers")]
|
||||
}
|
||||
|
||||
let manifestsTargets = named(manifests: manifests).map { name, manifest in
|
||||
Target(name: name,
|
||||
platform: .macOS,
|
||||
product: .staticFramework,
|
||||
productName: name,
|
||||
bundleId: "io.tuist.${PRODUCT_NAME:rfc1034identifier}",
|
||||
settings: targetSettings,
|
||||
sources: [(path: manifest, compilerFlags: nil)],
|
||||
filesGroup: .group(name: "Manifests"),
|
||||
dependencies: manifestsDependencies)
|
||||
let manifestsTargets = named(manifests: manifests).map { name, manifestSourcePath in
|
||||
editorHelperTarget(name: name,
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [manifestSourcePath],
|
||||
dependencies: manifestsDependencies)
|
||||
}
|
||||
|
||||
var helpersTarget: Target?
|
||||
if !helpers.isEmpty {
|
||||
helpersTarget = Target.editorHelperTarget(name: "ProjectDescriptionHelpers",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: helpers)
|
||||
helpersTarget = editorHelperTarget(name: "ProjectDescriptionHelpers",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: helpers)
|
||||
}
|
||||
var templatesTarget: Target?
|
||||
if !templates.isEmpty {
|
||||
templatesTarget = Target.editorHelperTarget(name: "Templates",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: templates)
|
||||
templatesTarget = editorHelperTarget(name: "Templates",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: templates)
|
||||
}
|
||||
var setupTarget: Target?
|
||||
if let setupPath = setupPath {
|
||||
setupTarget = Target.editorHelperTarget(name: "Setup",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [setupPath])
|
||||
setupTarget = editorHelperTarget(name: "Setup",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [setupPath])
|
||||
}
|
||||
var configTarget: Target?
|
||||
if let configPath = configPath {
|
||||
configTarget = Target.editorHelperTarget(name: "Config",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [configPath])
|
||||
configTarget = editorHelperTarget(name: "Config",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [configPath])
|
||||
}
|
||||
var dependenciesTarget: Target?
|
||||
if let dependenciesPath = dependenciesPath {
|
||||
dependenciesTarget = editorHelperTarget(name: "Dependencies",
|
||||
targetSettings: targetSettings,
|
||||
sourcePaths: [dependenciesPath])
|
||||
}
|
||||
|
||||
var targets: [Target] = []
|
||||
|
@ -86,6 +89,7 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
if let templatesTarget = templatesTarget { targets.append(templatesTarget) }
|
||||
if let setupTarget = setupTarget { targets.append(setupTarget) }
|
||||
if let configTarget = configTarget { targets.append(configTarget) }
|
||||
if let dependenciesTarget = dependenciesTarget { targets.append(dependenciesTarget) }
|
||||
|
||||
// Run Scheme
|
||||
let buildAction = BuildAction(targets: targets.map { TargetReference(projectPath: sourceRootPath, name: $0.name) })
|
||||
|
@ -126,6 +130,10 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
let configNode = TargetNode(project: project, target: configTarget, dependencies: [])
|
||||
dependencies.append(configNode)
|
||||
}
|
||||
if let dependenciesTarget = dependenciesTarget {
|
||||
let dependenciesNode = TargetNode(project: project, target: dependenciesTarget, dependencies: [])
|
||||
dependencies.append(dependenciesNode)
|
||||
}
|
||||
|
||||
let manifestTargetNodes = manifestsTargets.map { TargetNode(project: project, target: $0, dependencies: dependencies) }
|
||||
let workspace = Workspace(path: project.path, name: "Manifests", projects: [project.path])
|
||||
|
@ -149,7 +157,7 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
/// It returns the build settings that should be used in the manifests target.
|
||||
/// - Parameter projectDescriptionPath: Path to the ProjectDescription framework.
|
||||
/// - Parameter swiftVersion: The system's Swift version.
|
||||
fileprivate func settings(projectDescriptionPath: AbsolutePath, swiftVersion: String) -> SettingsDictionary {
|
||||
private func settings(projectDescriptionPath: AbsolutePath, swiftVersion: String) -> SettingsDictionary {
|
||||
let frameworkParentDirectory = projectDescriptionPath.parentDirectory
|
||||
var buildSettings = SettingsDictionary()
|
||||
buildSettings["FRAMEWORK_SEARCH_PATHS"] = .string(frameworkParentDirectory.pathString)
|
||||
|
@ -162,7 +170,7 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
/// It returns a dictionary with unique name as key for each Manifest file
|
||||
/// - Parameter manifests: Manifest files to assign an unique name
|
||||
/// - Returns: Dictionary composed by unique name as key and Manifest file as value.
|
||||
fileprivate func named(manifests: [AbsolutePath]) -> [String: AbsolutePath] {
|
||||
private func named(manifests: [AbsolutePath]) -> [String: AbsolutePath] {
|
||||
manifests.reduce(into: [String: AbsolutePath]()) { result, manifest in
|
||||
var name = "\(manifest.parentDirectory.basename)Manifests"
|
||||
while result[name] != nil {
|
||||
|
@ -171,21 +179,23 @@ final class ProjectEditorMapper: ProjectEditorMapping {
|
|||
result[name] = manifest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Target {
|
||||
/// Target for edit project
|
||||
static func editorHelperTarget(name: String,
|
||||
targetSettings: Settings,
|
||||
sourcePaths: [AbsolutePath]) -> Target
|
||||
{
|
||||
/// It returns a target for edit project.
|
||||
/// - Parameters:
|
||||
/// - name: Name for the target.
|
||||
/// - targetSettings: Target's settings.
|
||||
/// - sourcePaths: Target's sources.
|
||||
/// - dependencies: Target's dependencies.
|
||||
/// - Returns: Target for edit project.
|
||||
private func editorHelperTarget(name: String, targetSettings: Settings, sourcePaths: [AbsolutePath], dependencies: [Dependency] = []) -> Target {
|
||||
Target(name: name,
|
||||
platform: .macOS,
|
||||
product: .staticFramework,
|
||||
productName: name,
|
||||
bundleId: "io.tuist.${PRODUCT_NAME:rfc1034identifier}",
|
||||
settings: targetSettings,
|
||||
sources: sourcePaths.map { (path: $0, compilerFlags: nil) },
|
||||
filesGroup: .group(name: "Manifests"))
|
||||
sources: sourcePaths.map { SourceFile(path: $0, compilerFlags: nil) },
|
||||
filesGroup: .group(name: "Manifests"),
|
||||
dependencies: dependencies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import RxBlocking
|
||||
import TSCBasic
|
||||
import TuistAutomation
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
|
@ -40,7 +41,7 @@ final class BuildService {
|
|||
/// Build graph inspector.
|
||||
let buildGraphInspector: BuildGraphInspecting
|
||||
|
||||
init(generator: Generating = Generator(),
|
||||
init(generator: Generating = Generator(contentHasher: CacheContentHasher()),
|
||||
xcodebuildController: XcodeBuildControlling = XcodeBuildController(),
|
||||
buildGraphInspector: BuildGraphInspecting = BuildGraphInspector())
|
||||
{
|
||||
|
|
|
@ -12,10 +12,13 @@ final class CachePrintHashesService {
|
|||
let graphContentHasher: GraphContentHashing
|
||||
private let clock: Clock
|
||||
|
||||
init(generator: Generating = Generator(),
|
||||
graphContentHasher: GraphContentHashing = GraphContentHasher(),
|
||||
clock: Clock = WallClock())
|
||||
{
|
||||
convenience init(contentHasher: ContentHashing = CacheContentHasher()) {
|
||||
self.init(generator: Generator(contentHasher: contentHasher),
|
||||
graphContentHasher: GraphContentHasher(contentHasher: contentHasher),
|
||||
clock: WallClock())
|
||||
}
|
||||
|
||||
init(generator: Generating, graphContentHasher: GraphContentHashing, clock: Clock) {
|
||||
self.generator = generator
|
||||
self.graphContentHasher = graphContentHasher
|
||||
self.clock = clock
|
||||
|
|
|
@ -21,9 +21,9 @@ final class CacheWarmService {
|
|||
let config = try generatorModelLoader.loadConfig(at: currentPath)
|
||||
let cache = Cache(storageProvider: CacheStorageProvider(config: config))
|
||||
let cacheControllerFactory = CacheControllerFactory(cache: cache)
|
||||
|
||||
let cacheController = xcframeworks ? cacheControllerFactory.makeForXCFramework()
|
||||
: cacheControllerFactory.makeForSimulatorFramework()
|
||||
let contentHasher = CacheContentHasher()
|
||||
let cacheController = xcframeworks ? cacheControllerFactory.makeForXCFramework(contentHasher: contentHasher)
|
||||
: cacheControllerFactory.makeForSimulatorFramework(contentHasher: contentHasher)
|
||||
try cacheController.cache(path: path)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import RxBlocking
|
||||
import Signals
|
||||
import TSCBasic
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
import TuistDoc
|
||||
import TuistSupport
|
||||
|
@ -59,7 +60,7 @@ final class DocService {
|
|||
/// Semaphore to block the execution
|
||||
private let semaphore: Semaphoring
|
||||
|
||||
init(generator: Generating = Generator(),
|
||||
init(generator: Generating = Generator(contentHasher: CacheContentHasher()),
|
||||
swiftDocController: SwiftDocControlling = SwiftDocController(),
|
||||
swiftDocServer: SwiftDocServing = SwiftDocServer(),
|
||||
fileHandler: FileHandling = FileHandler.shared,
|
||||
|
|
|
@ -7,12 +7,15 @@ final class FocusGraphMapperProvider: GraphMapperProviding {
|
|||
private let cacheSources: Set<String>
|
||||
private let cache: Bool
|
||||
private let cacheOutputType: CacheOutputType
|
||||
private let contentHasher: ContentHashing
|
||||
|
||||
init(cache: Bool,
|
||||
init(contentHasher: ContentHashing,
|
||||
cache: Bool,
|
||||
cacheSources: Set<String>,
|
||||
cacheOutputType: CacheOutputType,
|
||||
defaultProvider: GraphMapperProviding = GraphMapperProvider())
|
||||
{
|
||||
self.contentHasher = contentHasher
|
||||
self.cacheSources = cacheSources
|
||||
self.cache = cache
|
||||
self.defaultProvider = defaultProvider
|
||||
|
@ -28,7 +31,8 @@ final class FocusGraphMapperProvider: GraphMapperProviding {
|
|||
let cacheMapper = CacheMapper(config: config,
|
||||
cacheStorageProvider: CacheStorageProvider(config: config),
|
||||
sources: cacheSources,
|
||||
cacheOutputType: cacheOutputType)
|
||||
cacheOutputType: cacheOutputType,
|
||||
contentHasher: contentHasher)
|
||||
mappers.append(cacheMapper)
|
||||
mappers.append(CacheTreeShakingGraphMapper())
|
||||
}
|
||||
|
|
|
@ -13,11 +13,19 @@ protocol FocusServiceProjectGeneratorFactorying {
|
|||
}
|
||||
|
||||
final class FocusServiceProjectGeneratorFactory: FocusServiceProjectGeneratorFactorying {
|
||||
init() {}
|
||||
|
||||
func generator(sources: Set<String>, xcframeworks: Bool, ignoreCache: Bool) -> Generating {
|
||||
let graphMapperProvider = FocusGraphMapperProvider(cache: !ignoreCache,
|
||||
let contentHasher = CacheContentHasher()
|
||||
let graphMapperProvider = FocusGraphMapperProvider(contentHasher: contentHasher,
|
||||
cache: !ignoreCache,
|
||||
cacheSources: sources,
|
||||
cacheOutputType: xcframeworks ? .xcframework : .framework)
|
||||
return Generator(graphMapperProvider: graphMapperProvider)
|
||||
let projectMapperProvider = ProjectMapperProvider(contentHasher: contentHasher)
|
||||
return Generator(projectMapperProvider: projectMapperProvider,
|
||||
graphMapperProvider: graphMapperProvider,
|
||||
workspaceMapperProvider: WorkspaceMapperProvider(contentHasher: contentHasher),
|
||||
manifestLoaderFactory: ManifestLoaderFactory())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import TSCBasic
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
import TuistGenerator
|
||||
import TuistLoader
|
||||
import TuistSupport
|
||||
|
@ -9,7 +11,12 @@ protocol GenerateServiceProjectGeneratorFactorying {
|
|||
|
||||
final class GenerateServiceProjectGeneratorFactory: GenerateServiceProjectGeneratorFactorying {
|
||||
func generator() -> Generating {
|
||||
Generator(graphMapperProvider: GraphMapperProvider())
|
||||
let contentHasher = CacheContentHasher()
|
||||
let projectMapperProvider = ProjectMapperProvider(contentHasher: contentHasher)
|
||||
return Generator(projectMapperProvider: projectMapperProvider,
|
||||
graphMapperProvider: GraphMapperProvider(),
|
||||
workspaceMapperProvider: WorkspaceMapperProvider(contentHasher: contentHasher),
|
||||
manifestLoaderFactory: ManifestLoaderFactory())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistMigration
|
||||
import TuistSupport
|
||||
|
||||
final class MigrationTargetsByDependenciesService {
|
||||
// MARK: - Attributes
|
||||
|
||||
private let targetsExtractor: TargetsExtracting
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(targetsExtractor: TargetsExtracting = TargetsExtractor()) {
|
||||
self.targetsExtractor = targetsExtractor
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
func run(xcodeprojPath: AbsolutePath) throws {
|
||||
let sortedTargets = try targetsExtractor.targetsSortedByDependencies(xcodeprojPath: xcodeprojPath)
|
||||
let sortedTargetsJson = try makeJson(from: sortedTargets)
|
||||
logger.info("\(sortedTargetsJson)")
|
||||
}
|
||||
|
||||
private func makeJson(from sortedTargets: [TargetDependencyCount]) throws -> String {
|
||||
let jsonEncoder = JSONEncoder()
|
||||
jsonEncoder.outputFormatting = .prettyPrinted
|
||||
let targetsData = try jsonEncoder.encode(sortedTargets)
|
||||
guard let jsonString = String(data: targetsData, encoding: .utf8) else {
|
||||
throw TargetsExtractorError.failedToEncode
|
||||
}
|
||||
return jsonString
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import RxBlocking
|
|||
import TSCBasic
|
||||
import struct TSCUtility.Version
|
||||
import TuistAutomation
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
|
@ -45,7 +46,7 @@ final class TestService {
|
|||
let simulatorController: SimulatorControlling
|
||||
|
||||
init(
|
||||
generator: Generating = Generator(),
|
||||
generator: Generating = Generator(contentHasher: ContentHasher()),
|
||||
xcodebuildController: XcodeBuildControlling = XcodeBuildController(),
|
||||
buildGraphInspector: BuildGraphInspecting = BuildGraphInspector(),
|
||||
simulatorController: SimulatorControlling = SimulatorController()
|
||||
|
|
|
@ -41,7 +41,7 @@ extension TuistCore.Target {
|
|||
let sources = try TuistCore.Target.sources(targetName: name, sources: manifest.sources?.globs.map { (glob: ProjectDescription.SourceFileGlob) in
|
||||
let globPath = try generatorPaths.resolve(path: glob.glob).pathString
|
||||
let excluding: [String] = try glob.excluding.compactMap { try generatorPaths.resolve(path: $0).pathString }
|
||||
return (glob: globPath, excluding: excluding, compilerFlags: glob.compilerFlags)
|
||||
return TuistCore.SourceFileGlob(glob: globPath, excluding: excluding, compilerFlags: glob.compilerFlags)
|
||||
} ?? [])
|
||||
|
||||
let resourceFilter = { (path: AbsolutePath) -> Bool in
|
||||
|
|
|
@ -6,21 +6,25 @@ import TuistSupport
|
|||
public protocol ManifestFilesLocating: AnyObject {
|
||||
/// It locates the manifest files that are have a connection with the
|
||||
/// definitions in the current directory.
|
||||
/// - Parameter at: Directory for which the manifest files will be obtained.
|
||||
func locateProjectManifests(at: AbsolutePath) -> [(Manifest, AbsolutePath)]
|
||||
/// - Parameter locatingPath: Directory for which the manifest files will be obtained.
|
||||
func locateProjectManifests(at locatingPath: AbsolutePath) -> [(Manifest, AbsolutePath)]
|
||||
|
||||
/// It locates all manifest files under the root project folder
|
||||
/// - Parameter at: Directory for which the **project** manifest files will
|
||||
/// - Parameter locatingPath: Directory for which the **project** manifest files will
|
||||
/// be obtained
|
||||
func locateAllProjectManifests(at: AbsolutePath) -> [(Manifest, AbsolutePath)]
|
||||
func locateAllProjectManifests(at locatingPath: AbsolutePath) -> [(Manifest, AbsolutePath)]
|
||||
|
||||
/// It traverses up the directory hierarchy until it finds a `Config.swift` file.
|
||||
/// - Parameter at: Path from where to do the lookup.
|
||||
func locateConfig(at: AbsolutePath) -> AbsolutePath?
|
||||
/// - Parameter locatingPath: Path from where to do the lookup.
|
||||
func locateConfig(at locatingPath: AbsolutePath) -> AbsolutePath?
|
||||
|
||||
/// It traverses up the directory hierarchy until it finds a `Dependencies.swift` file.
|
||||
/// - Parameter locatingPath: Path from where to do the lookup.
|
||||
func locateDependencies(at locatingPath: AbsolutePath) -> AbsolutePath?
|
||||
|
||||
/// It traverses up the directory hierarchy until it finds a `Setup.swift` file.
|
||||
/// - Parameter at: Path from where to do the lookup.
|
||||
func locateSetup(at: AbsolutePath) -> AbsolutePath?
|
||||
/// - Parameter locatingPath: Path from where to do the lookup.
|
||||
func locateSetup(at locatingPath: AbsolutePath) -> AbsolutePath?
|
||||
}
|
||||
|
||||
public final class ManifestFilesLocator: ManifestFilesLocating {
|
||||
|
@ -39,32 +43,37 @@ public final class ManifestFilesLocator: ManifestFilesLocating {
|
|||
}
|
||||
}
|
||||
|
||||
public func locateAllProjectManifests(at: AbsolutePath) -> [(Manifest, AbsolutePath)] {
|
||||
guard let rootPath = rootDirectoryLocator.locate(from: at) else { return locateProjectManifests(at: at) }
|
||||
let projectsPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.project.fileName(at))").map { (Manifest.project, $0) }
|
||||
let workspacesPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.workspace.fileName(at))").map { (Manifest.workspace, $0) }
|
||||
public func locateAllProjectManifests(at locatingPath: AbsolutePath) -> [(Manifest, AbsolutePath)] {
|
||||
guard let rootPath = rootDirectoryLocator.locate(from: locatingPath) else { return locateProjectManifests(at: locatingPath) }
|
||||
let projectsPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.project.fileName(locatingPath))").map { (Manifest.project, $0) }
|
||||
let workspacesPaths = FileHandler.shared.glob(rootPath, glob: "**/\(Manifest.workspace.fileName(locatingPath))").map { (Manifest.workspace, $0) }
|
||||
return projectsPaths + workspacesPaths
|
||||
}
|
||||
|
||||
public func locateConfig(at: AbsolutePath) -> AbsolutePath? {
|
||||
let subPath = RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName(at))")
|
||||
return traverseAndLocate(at: at, appending: subPath)
|
||||
public func locateConfig(at locatingPath: AbsolutePath) -> AbsolutePath? {
|
||||
let subPath = RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.config.fileName(locatingPath))")
|
||||
return traverseAndLocate(at: locatingPath, appending: subPath)
|
||||
}
|
||||
|
||||
public func locateSetup(at: AbsolutePath) -> AbsolutePath? {
|
||||
let subPath = RelativePath(Manifest.setup.fileName(at))
|
||||
return traverseAndLocate(at: at, appending: subPath)
|
||||
public func locateDependencies(at locatingPath: AbsolutePath) -> AbsolutePath? {
|
||||
let subPath = RelativePath("\(Constants.tuistDirectoryName)/\(Manifest.dependencies.fileName(locatingPath))")
|
||||
return traverseAndLocate(at: locatingPath, appending: subPath)
|
||||
}
|
||||
|
||||
public func locateSetup(at locatingPath: AbsolutePath) -> AbsolutePath? {
|
||||
let subPath = RelativePath(Manifest.setup.fileName(locatingPath))
|
||||
return traverseAndLocate(at: locatingPath, appending: subPath)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func traverseAndLocate(at: AbsolutePath, appending subpath: RelativePath) -> AbsolutePath? {
|
||||
let manifestPath = at.appending(subpath)
|
||||
private func traverseAndLocate(at locatingPath: AbsolutePath, appending subpath: RelativePath) -> AbsolutePath? {
|
||||
let manifestPath = locatingPath.appending(subpath)
|
||||
|
||||
if FileHandler.shared.exists(manifestPath) {
|
||||
return manifestPath
|
||||
} else if at != .root {
|
||||
return traverseAndLocate(at: at.parentDirectory, appending: subpath)
|
||||
} else if locatingPath != .root {
|
||||
return traverseAndLocate(at: locatingPath.parentDirectory, appending: subpath)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -167,6 +167,6 @@ extension Dependencies {
|
|||
public static func test(name: String = "Any Dependency",
|
||||
requirement: Dependency.Requirement = .exact("1.4.0")) -> Dependencies
|
||||
{
|
||||
Dependencies([Dependency(name: name, requirement: requirement)])
|
||||
Dependencies([.carthage(name: name, requirement: requirement)])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ public final class MockManifestFilesLocator: ManifestFilesLocating {
|
|||
public var locateAllProjectManifestsArgs: [AbsolutePath] = []
|
||||
public var locateConfigStub: AbsolutePath?
|
||||
public var locateConfigArgs: [AbsolutePath] = []
|
||||
public var locateDependenciesStub: AbsolutePath?
|
||||
public var locateDependenciesArgs: [AbsolutePath] = []
|
||||
public var locateSetupStub: AbsolutePath?
|
||||
public var locateSetupArgs: [AbsolutePath] = []
|
||||
|
||||
|
@ -27,6 +29,11 @@ public final class MockManifestFilesLocator: ManifestFilesLocating {
|
|||
return locateConfigStub ?? at.appending(components: "Tuist", "Config.swift")
|
||||
}
|
||||
|
||||
public func locateDependencies(at: AbsolutePath) -> AbsolutePath? {
|
||||
locateDependenciesArgs.append(at)
|
||||
return locateDependenciesStub ?? at.appending(components: "Tuist", "Dependencies.swift")
|
||||
}
|
||||
|
||||
public func locateSetup(at: AbsolutePath) -> AbsolutePath? {
|
||||
locateSetupArgs.append(at)
|
||||
return locateSetupStub ?? at.appending(component: "Setup.swift")
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import Foundation
|
||||
import PathKit
|
||||
import TSCBasic
|
||||
import TuistSupport
|
||||
import XcodeProj
|
||||
|
||||
public enum TargetsExtractorError: FatalError, Equatable {
|
||||
case missingProject
|
||||
case noTargets
|
||||
case failedToExtractTargets(String)
|
||||
case failedToEncode
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .missingProject: return "The project's pbxproj file contains no projects."
|
||||
case .noTargets: return "The project doesn't have any targets."
|
||||
case .failedToEncode: return "Failed to encode targets into JSON schema"
|
||||
case let .failedToExtractTargets(reason): return "Failed to extract targets for reason: \(reason)."
|
||||
}
|
||||
}
|
||||
|
||||
public var type: ErrorType {
|
||||
switch self {
|
||||
case .missingProject:
|
||||
return .abort
|
||||
case .noTargets:
|
||||
return .abort
|
||||
case .failedToExtractTargets:
|
||||
return .bug
|
||||
case .failedToEncode:
|
||||
return .bug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface to extract all targets from an xcode project, sorted by number of dependencies
|
||||
public protocol TargetsExtracting {
|
||||
/// - Parameters:
|
||||
/// - xcodeprojPath: Path to the Xcode project.
|
||||
func targetsSortedByDependencies(xcodeprojPath: AbsolutePath) throws -> [TargetDependencyCount]
|
||||
}
|
||||
|
||||
public struct TargetDependencyCount: Encodable {
|
||||
public let targetName: String
|
||||
public let dependenciesCount: Int
|
||||
}
|
||||
|
||||
public final class TargetsExtractor: TargetsExtracting {
|
||||
// MARK: - Init
|
||||
|
||||
public init() {}
|
||||
|
||||
// MARK: - EmptyBuildSettingsChecking
|
||||
|
||||
public func targetsSortedByDependencies(xcodeprojPath: AbsolutePath) throws -> [TargetDependencyCount] {
|
||||
guard FileHandler.shared.exists(xcodeprojPath) else { throw TargetsExtractorError.missingProject }
|
||||
let pbxproj = try XcodeProj(path: Path(xcodeprojPath.pathString)).pbxproj
|
||||
let targets = pbxproj.nativeTargets + pbxproj.aggregateTargets + pbxproj.legacyTargets
|
||||
if targets.isEmpty {
|
||||
throw TargetsExtractorError.noTargets
|
||||
}
|
||||
return try sortTargetsByDependenciesCount(targets)
|
||||
}
|
||||
|
||||
private func sortTargetsByDependenciesCount(_ targets: [PBXTarget]) throws -> [TargetDependencyCount] {
|
||||
let sortedTargets = try targets.sorted { lTarget, rTarget -> Bool in
|
||||
let lCount = try countDependencies(of: lTarget)
|
||||
let rCount = try countDependencies(of: rTarget)
|
||||
if lCount == rCount {
|
||||
return lTarget.name < rTarget.name
|
||||
}
|
||||
return lCount < rCount
|
||||
}
|
||||
return try sortedTargets.map { TargetDependencyCount(targetName: $0.name, dependenciesCount: try countDependencies(of: $0)) }
|
||||
}
|
||||
|
||||
private func countDependencies(of target: PBXTarget) throws -> Int {
|
||||
var count = target.dependencies.count
|
||||
if let frameworkFiles = try target.frameworksBuildPhase()?.files {
|
||||
count += frameworkFiles.count
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
|
@ -32,11 +32,16 @@ public struct Constants {
|
|||
public static let signingKeychain = "signing.keychain"
|
||||
}
|
||||
|
||||
public struct AsyncQueue {
|
||||
public static let directoryName: String = "Queue"
|
||||
}
|
||||
|
||||
public struct EnvironmentVariables {
|
||||
public static let verbose = "TUIST_VERBOSE"
|
||||
public static let colouredOutput = "TUIST_COLOURED_OUTPUT"
|
||||
public static let versionsDirectory = "TUIST_VERSIONS_DIRECTORY"
|
||||
public static let cacheDirectory = "TUIST_CACHE_DIRECTORY"
|
||||
public static let queueDirectory = "TUIST_QUEUE_DIRECTORY"
|
||||
public static let cloudToken = "TUIST_CLOUD_TOKEN"
|
||||
public static let cacheManifests = "TUIST_CACHE_MANIFESTS"
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ public protocol Environmenting: AnyObject {
|
|||
|
||||
/// Returns true if Tuist is running with verbose mode enabled.
|
||||
var isVerbose: Bool { get }
|
||||
|
||||
/// Returns the path to the directory where the async queue events are persisted.
|
||||
var queueDirectory: AbsolutePath { get }
|
||||
}
|
||||
|
||||
/// Local environment controller.
|
||||
|
@ -124,6 +127,14 @@ public class Environment: Environmenting {
|
|||
}
|
||||
}
|
||||
|
||||
public var queueDirectory: AbsolutePath {
|
||||
if let envVariable = ProcessInfo.processInfo.environment[Constants.EnvironmentVariables.queueDirectory] {
|
||||
return AbsolutePath(envVariable)
|
||||
} else {
|
||||
return directory.appending(component: Constants.AsyncQueue.directoryName)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all the environment variables that are specific to Tuist (prefixed with TUIST_)
|
||||
public var tuistVariables: [String: String] {
|
||||
ProcessInfo.processInfo.environment.filter { $0.key.hasPrefix("TUIST_") }
|
||||
|
|
|
@ -17,7 +17,7 @@ public class MockEnvironment: Environmenting {
|
|||
|
||||
public var isVerbose: Bool = false
|
||||
public var cacheDirectoryStub: AbsolutePath?
|
||||
|
||||
public var queueDirectoryStub: AbsolutePath?
|
||||
public var shouldOutputBeColoured: Bool = false
|
||||
public var isStandardOutputInteractive: Bool = false
|
||||
public var tuistVariables: [String: String] = [:]
|
||||
|
@ -35,6 +35,10 @@ public class MockEnvironment: Environmenting {
|
|||
cacheDirectoryStub ?? directory.path.appending(component: "Cache")
|
||||
}
|
||||
|
||||
public var queueDirectory: AbsolutePath {
|
||||
queueDirectoryStub ?? directory.path.appending(component: Constants.AsyncQueue.directoryName)
|
||||
}
|
||||
|
||||
public var projectDescriptionHelpersCacheDirectory: AbsolutePath {
|
||||
cacheDirectory.appending(component: "ProjectDescriptionHelpers")
|
||||
}
|
||||
|
|
|
@ -8,4 +8,5 @@ if CommandLine.arguments.contains("--verbose") { try? ProcessEnv.setVar(Constant
|
|||
LogOutput.bootstrap()
|
||||
|
||||
import TuistKit
|
||||
|
||||
TuistCommand.main()
|
||||
|
|
|
@ -12,7 +12,7 @@ let config = TapestryConfig(
|
|||
// .pre(tool: "bundle", arguments: ["exec", "rake", "features"]),
|
||||
.pre(.docsUpdate),
|
||||
.pre(tool: "sudo", arguments: ["xcode-select", "-s", "/Applications/Xcode_11.5.app"]),
|
||||
.post(tool: "bundle", arguments: ["exec", "rake", "release"]),
|
||||
.post(tool: "bundle", arguments: ["exec", "rake", "release[\(Argument.version)]"]),
|
||||
.post(tool: "bundle", arguments: ["exec", "rake", "release_scripts"]),
|
||||
.post(
|
||||
.githubRelease(
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import Foundation
|
||||
import RxBlocking
|
||||
import RxSwift
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistAsyncQueue
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class AsyncQueuePersistorTests: TuistUnitTestCase {
|
||||
var subject: AsyncQueuePersistor!
|
||||
|
||||
override func setUp() {
|
||||
let temporaryDirectory = try! temporaryPath()
|
||||
subject = AsyncQueuePersistor(directory: temporaryDirectory)
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_write() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: "dispatcher")
|
||||
|
||||
// When
|
||||
_ = try subject.write(event: event).toBlocking().last()
|
||||
|
||||
// Then
|
||||
let got = try subject.readAll().toBlocking().last()
|
||||
let gotEvent = try XCTUnwrap(got?.first)
|
||||
XCTAssertEqual(gotEvent.dispatcherId, "dispatcher")
|
||||
XCTAssertEqual(gotEvent.id, event.id)
|
||||
let normalizedDate = Date(timeIntervalSince1970: Double(Int(Double(event.date.timeIntervalSince1970))))
|
||||
XCTAssertEqual(gotEvent.date, normalizedDate)
|
||||
}
|
||||
|
||||
func test_delete() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: "dispatcher")
|
||||
_ = try subject.write(event: event).toBlocking().last()
|
||||
var persistedEvents = try subject.readAll().toBlocking().last()
|
||||
XCTAssertEqual(persistedEvents?.count, 1)
|
||||
|
||||
// When
|
||||
_ = try subject.delete(event: event).toBlocking().last()
|
||||
|
||||
// Then
|
||||
persistedEvents = try subject.readAll().toBlocking().last()
|
||||
XCTAssertEqual(persistedEvents?.count, 0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
import Foundation
|
||||
import Queuer
|
||||
import RxBlocking
|
||||
import RxSwift
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistAsyncQueue
|
||||
@testable import TuistAsyncQueueTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class AsyncQueueTests: TuistUnitTestCase {
|
||||
var subject: AsyncQueue!
|
||||
|
||||
let dispatcher1ID = "Dispatcher1"
|
||||
let dispatcher2ID = "Dispatcher2"
|
||||
|
||||
var mockAsyncQueueDispatcher1: MockAsyncQueueDispatcher!
|
||||
var mockAsyncQueueDispatcher2: MockAsyncQueueDispatcher!
|
||||
|
||||
var mockCIChecker: MockCIChecker!
|
||||
|
||||
var mockPersistor: MockAsyncQueuePersistor<AnyAsyncQueueEvent>!
|
||||
var mockQueuer: MockQueuer!
|
||||
|
||||
let timeout = 3.0
|
||||
|
||||
override func setUp() {
|
||||
mockAsyncQueueDispatcher1 = MockAsyncQueueDispatcher()
|
||||
mockAsyncQueueDispatcher1.stubbedIdentifier = dispatcher1ID
|
||||
|
||||
mockAsyncQueueDispatcher2 = MockAsyncQueueDispatcher()
|
||||
mockAsyncQueueDispatcher2.stubbedIdentifier = dispatcher2ID
|
||||
|
||||
mockCIChecker = MockCIChecker()
|
||||
mockPersistor = MockAsyncQueuePersistor()
|
||||
mockQueuer = MockQueuer()
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
mockAsyncQueueDispatcher1 = nil
|
||||
mockAsyncQueueDispatcher2 = nil
|
||||
mockCIChecker = nil
|
||||
mockPersistor = nil
|
||||
mockQueuer = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func subjectWithExecutionBlock(
|
||||
queue: Queuing? = nil,
|
||||
executionBlock: @escaping () throws -> Void = {},
|
||||
ciChecker: CIChecking? = nil,
|
||||
persistor: AsyncQueuePersisting? = nil,
|
||||
dispatchers: [AsyncQueueDispatching]? = nil
|
||||
) -> AsyncQueue {
|
||||
guard let asyncQueue = try? AsyncQueue(
|
||||
queue: queue ?? mockQueuer,
|
||||
executionBlock: executionBlock,
|
||||
ciChecker: ciChecker ?? mockCIChecker,
|
||||
persistor: persistor ?? mockPersistor,
|
||||
dispatchers: dispatchers ?? [mockAsyncQueueDispatcher1, mockAsyncQueueDispatcher2],
|
||||
persistedEventsSchedulerType: MainScheduler()
|
||||
) else {
|
||||
XCTFail("Could not create subject")
|
||||
return try! AsyncQueue(dispatchers: [mockAsyncQueueDispatcher1, mockAsyncQueueDispatcher2], executionBlock: executionBlock)
|
||||
}
|
||||
return asyncQueue
|
||||
}
|
||||
|
||||
func test_dispatch_eventIsPersisted() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock()
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
guard let persistedEvent = mockPersistor.invokedWriteEvent else {
|
||||
XCTFail("Event not passed to the persistor")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(event.id, persistedEvent.id)
|
||||
}
|
||||
|
||||
func test_dispatch_eventIsQueued() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock()
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
guard let queuedOperation = mockQueuer.invokedAddOperationParameterOperation as? ConcurrentOperation else {
|
||||
XCTFail("Operation not added to the queuer")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(queuedOperation.name, event.id.uuidString)
|
||||
}
|
||||
|
||||
func test_dispatch_eventIsDeletedFromThePersistorOnSendSuccess() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
mockPersistor.invokedDeleteCallBack = {
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
guard let deletedEvent = mockPersistor.invokedDeleteEvent else {
|
||||
XCTFail("Event was not deleted by the persistor")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(event.id, deletedEvent.id)
|
||||
}
|
||||
|
||||
func test_dispatch_eventIsDispatchedByTheRightDispatcher() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
mockAsyncQueueDispatcher1.invokedDispatchCallBack = {
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
guard let dispatchedEvent = mockAsyncQueueDispatcher1.invokedDispatchParameterEvent else {
|
||||
XCTFail("Event was not dispatched")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(event.id, dispatchedEvent.id)
|
||||
XCTAssertEqual(mockAsyncQueueDispatcher1.invokedDispatchCount, 1)
|
||||
XCTAssertEqual(mockAsyncQueueDispatcher2.invokedDispatchCount, 0)
|
||||
XCTAssertNil(mockAsyncQueueDispatcher2.invokedDispatchParameterEvent)
|
||||
}
|
||||
|
||||
func test_dispatch_queuerTriesThreeTimesToDispatch() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
mockAsyncQueueDispatcher1.stubbedDispatchError = MockAsyncQueueDispatcherError.dispatchError
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
|
||||
var count = 0
|
||||
mockAsyncQueueDispatcher1.invokedDispatchCallBack = {
|
||||
count += 1
|
||||
if count == 3 {
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func test_dispatch_doesNotDeleteEventOnError() throws {
|
||||
// Given
|
||||
let event = AnyAsyncQueueEvent(dispatcherId: dispatcher1ID)
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
mockAsyncQueueDispatcher1.stubbedDispatchError = MockAsyncQueueDispatcherError.dispatchError
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
|
||||
var count = 0
|
||||
mockAsyncQueueDispatcher1.invokedDispatchCallBack = {
|
||||
count += 1
|
||||
if count == 3 {
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
subject.dispatch(event: event)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
XCTAssertEqual(count, 3)
|
||||
XCTAssertEqual(mockPersistor.invokedDeleteEventCount, 0)
|
||||
}
|
||||
|
||||
func test_dispatch_readsPersistedEventsInitialization() throws {
|
||||
// Given
|
||||
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: 1)
|
||||
let eventTuple2: AsyncQueueEventTuple = makeEventTuple(id: 2)
|
||||
let eventTuple3: AsyncQueueEventTuple = makeEventTuple(id: 3)
|
||||
mockPersistor.stubbedReadAllResult = .just([eventTuple1, eventTuple2, eventTuple3])
|
||||
|
||||
// When
|
||||
subject = subjectWithExecutionBlock()
|
||||
|
||||
// Then
|
||||
let numberOfOperationsQueued = mockQueuer.invokedAddOperationCount
|
||||
XCTAssertEqual(numberOfOperationsQueued, 3)
|
||||
|
||||
guard let queuedOperation1 = mockQueuer.invokedAddOperationParametersOperationsList[0] as? ConcurrentOperation else {
|
||||
XCTFail("Operation for event tuple 1 not added to the queuer")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(queuedOperation1.name, eventTuple1.id.uuidString)
|
||||
|
||||
guard let queuedOperation2 = mockQueuer.invokedAddOperationParametersOperationsList[1] as? ConcurrentOperation else {
|
||||
XCTFail("Operation for event tuple 2 not added to the queuer")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(queuedOperation2.name, eventTuple2.id.uuidString)
|
||||
|
||||
guard let queuedOperation3 = mockQueuer.invokedAddOperationParametersOperationsList[2] as? ConcurrentOperation else {
|
||||
XCTFail("Operation for event tuple 3 not added to the queuer")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(queuedOperation3.name, eventTuple3.id.uuidString)
|
||||
}
|
||||
|
||||
func test_dispatch_persistedEventIsDispatchedByTheRightDispatcher() throws {
|
||||
// Given
|
||||
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: 1)
|
||||
mockPersistor.stubbedReadAllResult = .just([eventTuple1])
|
||||
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
mockAsyncQueueDispatcher1.invokedDispatchPersistedCallBack = {
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
// When
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
guard let dispatchedEventData = mockAsyncQueueDispatcher1.invokedDispatchPersistedDataParameter else {
|
||||
XCTFail("Data from persisted event was not dispatched")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(eventTuple1.data, dispatchedEventData)
|
||||
XCTAssertEqual(mockAsyncQueueDispatcher1.invokedDispatchPersistedCount, 1)
|
||||
XCTAssertEqual(mockAsyncQueueDispatcher2.invokedDispatchPersistedCount, 0)
|
||||
}
|
||||
|
||||
func test_dispatch_sentPersistedEventIsThenDeleted() throws {
|
||||
// Given
|
||||
let id: UInt = 1
|
||||
let eventTuple1: AsyncQueueEventTuple = makeEventTuple(id: id)
|
||||
mockPersistor.stubbedReadAllResult = .just([eventTuple1])
|
||||
|
||||
let expectation = XCTestExpectation(description: #function)
|
||||
mockAsyncQueueDispatcher1.invokedDispatchPersistedCallBack = {
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
// When
|
||||
subject = subjectWithExecutionBlock(queue: Queuer.shared)
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
guard let filename = mockPersistor.invokedDeleteFilenameParameter else {
|
||||
XCTFail("Sent persisted event was then not deleted")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(filename, self.filename(with: id))
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func makeEventTuple(id: UInt, dispatcherId: String? = nil) -> AsyncQueueEventTuple {
|
||||
(dispatcherId: dispatcherId ?? dispatcher1ID,
|
||||
id: UUID(),
|
||||
date: Date(),
|
||||
data: data(with: id),
|
||||
filename: filename(with: id))
|
||||
}
|
||||
|
||||
private func data(with id: UInt) -> Data {
|
||||
withUnsafeBytes(of: id) { Data($0) }
|
||||
}
|
||||
|
||||
private func filename(with id: UInt) -> String {
|
||||
"filename-\(id)"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
import TuistCore
|
||||
import TuistCoreTesting
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
@ -11,10 +12,10 @@ import XCTest
|
|||
final class ContentHashingIntegrationTests: TuistTestCase {
|
||||
var subject: GraphContentHasher!
|
||||
var temporaryDirectoryPath: String!
|
||||
var source1: Target.SourceFile!
|
||||
var source2: Target.SourceFile!
|
||||
var source3: Target.SourceFile!
|
||||
var source4: Target.SourceFile!
|
||||
var source1: SourceFile!
|
||||
var source2: SourceFile!
|
||||
var source3: SourceFile!
|
||||
var source4: SourceFile!
|
||||
var resourceFile1: FileElement!
|
||||
var resourceFile2: FileElement!
|
||||
var resourceFolderReference1: FileElement!
|
||||
|
@ -42,7 +43,7 @@ final class ContentHashingIntegrationTests: TuistTestCase {
|
|||
} catch {
|
||||
XCTFail("Error while creating files for stub project")
|
||||
}
|
||||
subject = GraphContentHasher()
|
||||
subject = GraphContentHasher(contentHasher: CacheContentHasher())
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -252,11 +253,11 @@ final class ContentHashingIntegrationTests: TuistTestCase {
|
|||
|
||||
// MARK: - Private helpers
|
||||
|
||||
private func createTemporarySourceFile(on temporaryDirectoryPath: AbsolutePath, name: String, content: String) throws -> Target.SourceFile {
|
||||
private func createTemporarySourceFile(on temporaryDirectoryPath: AbsolutePath, name: String, content: String) throws -> SourceFile {
|
||||
let filePath = temporaryDirectoryPath.appending(component: name)
|
||||
try FileHandler.shared.touch(filePath)
|
||||
try FileHandler.shared.write(content, path: filePath, atomically: true)
|
||||
return Target.SourceFile(path: filePath, compilerFlags: nil)
|
||||
return SourceFile(path: filePath, compilerFlags: nil)
|
||||
}
|
||||
|
||||
private func createTemporaryResourceFile(on temporaryDirectoryPath: AbsolutePath, name: String, content: String) throws -> FileElement {
|
||||
|
@ -276,7 +277,7 @@ final class ContentHashingIntegrationTests: TuistTestCase {
|
|||
private func makeFramework(named: String,
|
||||
platform: Platform = .iOS,
|
||||
productName: String? = nil,
|
||||
sources: [Target.SourceFile] = [],
|
||||
sources: [SourceFile] = [],
|
||||
resources: [FileElement] = [],
|
||||
coreDataModels: [CoreDataModel] = [],
|
||||
targetActions: [TargetAction] = []) -> TargetNode
|
||||
|
|
|
@ -3,6 +3,7 @@ import TuistCacheTesting
|
|||
import TuistSupport
|
||||
import XCTest
|
||||
@testable import TuistCache
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class CacheContentHasherTests: TuistUnitTestCase {
|
||||
|
|
|
@ -12,7 +12,7 @@ final class GraphContentHasherTests: TuistUnitTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = GraphContentHasher()
|
||||
subject = GraphContentHasher(contentHasher: ContentHasher())
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
|
|
@ -13,15 +13,15 @@ final class SourceFilesContentHasherTests: TuistUnitTestCase {
|
|||
private var mockContentHasher: MockContentHashing!
|
||||
private let sourceFile1Path = AbsolutePath("/file1")
|
||||
private let sourceFile2Path = AbsolutePath("/file2")
|
||||
private var sourceFile1: Target.SourceFile!
|
||||
private var sourceFile2: Target.SourceFile!
|
||||
private var sourceFile1: SourceFile!
|
||||
private var sourceFile2: SourceFile!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
mockContentHasher = MockContentHashing()
|
||||
subject = SourceFilesContentHasher(contentHasher: mockContentHasher)
|
||||
sourceFile1 = (path: sourceFile1Path, compilerFlags: "-fno-objc-arc")
|
||||
sourceFile2 = (path: sourceFile2Path, compilerFlags: "-print-objc-runtime-info")
|
||||
sourceFile1 = SourceFile(path: sourceFile1Path, compilerFlags: "-fno-objc-arc")
|
||||
sourceFile2 = SourceFile(path: sourceFile2Path, compilerFlags: "-print-objc-runtime-info")
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -34,6 +34,16 @@ final class SourceFilesContentHasherTests: TuistUnitTestCase {
|
|||
|
||||
// MARK: - Tests
|
||||
|
||||
func test_hash_when_the_files_have_a_hash() throws {
|
||||
// When
|
||||
sourceFile1 = SourceFile(path: sourceFile1Path, contentHash: "first")
|
||||
sourceFile2 = SourceFile(path: sourceFile2Path, contentHash: "second")
|
||||
let hash = try subject.hash(sources: [sourceFile1, sourceFile2])
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(hash, "first;second")
|
||||
}
|
||||
|
||||
func test_hash_returnsSameValue() throws {
|
||||
// When
|
||||
let hash = try subject.hash(sources: [sourceFile1, sourceFile2])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
@testable import TuistCache
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class ContentHasherTests: TuistUnitTestCase {
|
|
@ -93,8 +93,8 @@ final class TargetTests: TuistUnitTestCase {
|
|||
|
||||
// When
|
||||
let sources = try Target.sources(targetName: "Target", sources: [
|
||||
(glob: temporaryPath.appending(RelativePath("sources/**")).pathString, excluding: [], compilerFlags: nil),
|
||||
(glob: temporaryPath.appending(RelativePath("sources/**")).pathString, excluding: [], compilerFlags: nil),
|
||||
SourceFileGlob(glob: temporaryPath.appending(RelativePath("sources/**")).pathString, excluding: [], compilerFlags: nil),
|
||||
SourceFileGlob(glob: temporaryPath.appending(RelativePath("sources/**")).pathString, excluding: [], compilerFlags: nil),
|
||||
])
|
||||
|
||||
// Then
|
||||
|
@ -125,9 +125,9 @@ final class TargetTests: TuistUnitTestCase {
|
|||
|
||||
// When
|
||||
let sources = try Target.sources(targetName: "Target", sources: [
|
||||
(glob: temporaryPath.appending(RelativePath("sources/**")).pathString,
|
||||
excluding: [temporaryPath.appending(RelativePath("sources/**/*Tests.swift")).pathString],
|
||||
compilerFlags: nil),
|
||||
SourceFileGlob(glob: temporaryPath.appending(RelativePath("sources/**")).pathString,
|
||||
excluding: [temporaryPath.appending(RelativePath("sources/**/*Tests.swift")).pathString],
|
||||
compilerFlags: nil),
|
||||
])
|
||||
|
||||
// Then
|
||||
|
@ -165,9 +165,9 @@ final class TargetTests: TuistUnitTestCase {
|
|||
|
||||
// When
|
||||
let sources = try Target.sources(targetName: "Target", sources: [
|
||||
(glob: temporaryPath.appending(RelativePath("sources/**")).pathString,
|
||||
excluding: excluding,
|
||||
compilerFlags: nil),
|
||||
SourceFileGlob(glob: temporaryPath.appending(RelativePath("sources/**")).pathString,
|
||||
excluding: excluding,
|
||||
compilerFlags: nil),
|
||||
])
|
||||
|
||||
// Then
|
||||
|
@ -191,9 +191,7 @@ final class TargetTests: TuistUnitTestCase {
|
|||
invalidGlobs: invalidGlobs)
|
||||
// When
|
||||
XCTAssertThrowsSpecific(try Target.sources(targetName: "Target", sources: [
|
||||
(glob: temporaryPath.appending(RelativePath("invalid/path/**")).pathString,
|
||||
excluding: [],
|
||||
compilerFlags: nil),
|
||||
SourceFileGlob(glob: temporaryPath.appending(RelativePath("invalid/path/**")).pathString),
|
||||
]), error)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
let pbxproj = PBXProj()
|
||||
pbxproj.add(object: target)
|
||||
|
||||
let sources: [Target.SourceFile] = [
|
||||
("/test/file1.swift", "flag"),
|
||||
("/test/file2.swift", nil),
|
||||
let sources: [SourceFile] = [
|
||||
SourceFile(path: "/test/file1.swift", compilerFlags: "flag"),
|
||||
SourceFile(path: "/test/file2.swift"),
|
||||
]
|
||||
|
||||
let fileElements = createFileElements(for: sources.map { $0.path })
|
||||
|
@ -111,7 +111,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
pbxproj.add(object: target)
|
||||
let fileElements = ProjectFileElements()
|
||||
|
||||
XCTAssertThrowsError(try subject.generateSourcesBuildPhase(files: [(path: path, compilerFlags: nil)],
|
||||
XCTAssertThrowsError(try subject.generateSourcesBuildPhase(files: [SourceFile(path: path, compilerFlags: nil)],
|
||||
coreDataModels: [],
|
||||
pbxTarget: target,
|
||||
fileElements: fileElements,
|
||||
|
@ -126,9 +126,9 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
let pbxproj = PBXProj()
|
||||
pbxproj.add(object: target)
|
||||
|
||||
let sources: [Target.SourceFile] = [
|
||||
("/path/sources/Base.lproj/OTTSiriExtension.intentdefinition", nil),
|
||||
("/path/sources/en.lproj/OTTSiriExtension.intentdefinition", nil),
|
||||
let sources: [SourceFile] = [
|
||||
SourceFile(path: "/path/sources/Base.lproj/OTTSiriExtension.intentdefinition", compilerFlags: nil),
|
||||
SourceFile(path: "/path/sources/en.lproj/OTTSiriExtension.intentdefinition", compilerFlags: nil),
|
||||
]
|
||||
|
||||
let fileElements = createLocalizedResourceFileElements(for: [
|
||||
|
@ -158,7 +158,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
pbxproj.add(object: target)
|
||||
let fileElements = ProjectFileElements()
|
||||
|
||||
XCTAssertThrowsError(try subject.generateSourcesBuildPhase(files: [(path: path, compilerFlags: nil)],
|
||||
XCTAssertThrowsError(try subject.generateSourcesBuildPhase(files: [SourceFile(path: path, compilerFlags: nil)],
|
||||
coreDataModels: [],
|
||||
pbxTarget: target,
|
||||
fileElements: fileElements,
|
||||
|
@ -225,7 +225,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
fileElements.elements[headerPath] = headerFileReference
|
||||
|
||||
let target = Target.test(platform: .iOS,
|
||||
sources: [(path: "/test/file.swift", compilerFlags: nil)],
|
||||
sources: [SourceFile(path: "/test/file.swift", compilerFlags: nil)],
|
||||
headers: headers)
|
||||
|
||||
let graph = ValueGraph.test(path: tmpDir)
|
||||
|
@ -261,7 +261,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase {
|
|||
|
||||
let target = Target.test(platform: .iOS,
|
||||
product: .framework,
|
||||
sources: [(path: "/test/file.swift", compilerFlags: nil)],
|
||||
sources: [SourceFile(path: "/test/file.swift", compilerFlags: nil)],
|
||||
headers: headers)
|
||||
let graph = ValueGraph.test(path: tmpDir)
|
||||
let graphTraverser = ValueGraphTraverser(graph: graph)
|
||||
|
|
|
@ -291,7 +291,7 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
infoPlist: .file(path: AbsolutePath("/project/info.plist")),
|
||||
entitlements: AbsolutePath("/project/app.entitlements"),
|
||||
settings: settings,
|
||||
sources: [(path: AbsolutePath("/project/file.swift"), compilerFlags: nil)],
|
||||
sources: [SourceFile(path: AbsolutePath("/project/file.swift"))],
|
||||
resources: [
|
||||
.file(path: AbsolutePath("/project/image.png")),
|
||||
.folderReference(path: AbsolutePath("/project/reference")),
|
||||
|
@ -522,12 +522,12 @@ final class ProjectFileElementsTests: TuistUnitTestCase {
|
|||
let group = PBXGroup()
|
||||
let pbxproj = PBXProj()
|
||||
pbxproj.add(object: group)
|
||||
_ = subject.addFileElement(from: from,
|
||||
fileAbsolutePath: fileAbsolutePath,
|
||||
fileRelativePath: fileRelativePath,
|
||||
name: nil,
|
||||
toGroup: group,
|
||||
pbxproj: pbxproj)
|
||||
subject.addFileElement(from: from,
|
||||
fileAbsolutePath: fileAbsolutePath,
|
||||
fileRelativePath: fileRelativePath,
|
||||
name: nil,
|
||||
toGroup: group,
|
||||
pbxproj: pbxproj)
|
||||
let file: PBXFileReference? = group.children.first as? PBXFileReference
|
||||
XCTAssertEqual(file?.path, "file.swift")
|
||||
XCTAssertEqual(file?.sourceTree, .group)
|
||||
|
|
|
@ -136,7 +136,7 @@ final class TargetLinterTests: TuistUnitTestCase {
|
|||
let bundle = Target.empty(platform: .iOS,
|
||||
product: .bundle,
|
||||
sources: [
|
||||
(path: "/path/to/some/source.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/path/to/some/source.swift"),
|
||||
],
|
||||
resources: [])
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import TSCBasic
|
|||
import TuistCore
|
||||
import TuistSupport
|
||||
import XCTest
|
||||
|
||||
@testable import TuistCoreTesting
|
||||
@testable import TuistGenerator
|
||||
@testable import TuistSupportTesting
|
||||
|
@ -10,27 +11,31 @@ import XCTest
|
|||
final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase {
|
||||
private var subject: SynthesizedResourceInterfaceProjectMapper!
|
||||
private var synthesizedResourceInterfacesGenerator: MockSynthesizedResourceInterfaceGenerator!
|
||||
private var contentHasher: ContentHashing!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
synthesizedResourceInterfacesGenerator = MockSynthesizedResourceInterfaceGenerator()
|
||||
contentHasher = ContentHasher()
|
||||
subject = SynthesizedResourceInterfaceProjectMapper(
|
||||
synthesizedResourceInterfacesGenerator: synthesizedResourceInterfacesGenerator
|
||||
synthesizedResourceInterfacesGenerator: synthesizedResourceInterfacesGenerator,
|
||||
contentHasher: contentHasher
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
|
||||
contentHasher = nil
|
||||
synthesizedResourceInterfacesGenerator = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_map() throws {
|
||||
// Given
|
||||
synthesizedResourceInterfacesGenerator.renderStub = { _, _, _ in
|
||||
""
|
||||
synthesizedResourceInterfacesGenerator.renderStub = { _, _, paths in
|
||||
let content = paths.last?.basename
|
||||
return content ?? ""
|
||||
}
|
||||
|
||||
let projectPath = try temporaryPath()
|
||||
|
@ -92,30 +97,29 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase {
|
|||
.file(
|
||||
FileDescriptor(
|
||||
path: derivedSourcesPath.appending(component: "Assets+TargetA.swift"),
|
||||
contents: "".data(using: .utf8)
|
||||
contents: "a.xcassets".data(using: .utf8)
|
||||
)
|
||||
),
|
||||
.file(
|
||||
FileDescriptor(
|
||||
path: derivedSourcesPath.appending(component: "Strings+TargetA.swift"),
|
||||
contents: "".data(using: .utf8)
|
||||
contents: "aStrings.strings".data(using: .utf8)
|
||||
)
|
||||
),
|
||||
.file(
|
||||
FileDescriptor(
|
||||
path: derivedSourcesPath.appending(component: "Environment.swift"),
|
||||
contents: "".data(using: .utf8)
|
||||
contents: "Environment.plist".data(using: .utf8)
|
||||
)
|
||||
),
|
||||
.file(
|
||||
FileDescriptor(
|
||||
path: derivedSourcesPath.appending(component: "Fonts+TargetA.swift"),
|
||||
contents: "".data(using: .utf8)
|
||||
contents: "ttcFont.ttc".data(using: .utf8)
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
mappedProject,
|
||||
Project.test(
|
||||
|
@ -124,18 +128,22 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase {
|
|||
Target.test(
|
||||
name: targetA.name,
|
||||
sources: [
|
||||
(path: derivedSourcesPath
|
||||
SourceFile(path: derivedSourcesPath
|
||||
.appending(component: "Assets+TargetA.swift"),
|
||||
compilerFlags: nil),
|
||||
(path: derivedSourcesPath
|
||||
compilerFlags: nil,
|
||||
contentHash: try contentHasher.hash("a.xcassets".data(using: .utf8)!)),
|
||||
SourceFile(path: derivedSourcesPath
|
||||
.appending(component: "Strings+TargetA.swift"),
|
||||
compilerFlags: nil),
|
||||
(path: derivedSourcesPath
|
||||
compilerFlags: nil,
|
||||
contentHash: try contentHasher.hash("aStrings.strings".data(using: .utf8)!)),
|
||||
SourceFile(path: derivedSourcesPath
|
||||
.appending(component: "Environment.swift"),
|
||||
compilerFlags: nil),
|
||||
(path: derivedSourcesPath
|
||||
compilerFlags: nil,
|
||||
contentHash: try contentHasher.hash("Environment.plist".data(using: .utf8)!)),
|
||||
SourceFile(path: derivedSourcesPath
|
||||
.appending(component: "Fonts+TargetA.swift"),
|
||||
compilerFlags: nil),
|
||||
compilerFlags: nil,
|
||||
contentHash: try contentHasher.hash("ttcFont.ttc".data(using: .utf8)!)),
|
||||
],
|
||||
resources: targetA.resources
|
||||
),
|
||||
|
|
|
@ -364,7 +364,7 @@ final class MultipleConfigurationsIntegrationTests: TuistUnitTestCase {
|
|||
productName: "AppTarget",
|
||||
bundleId: "test.bundle",
|
||||
settings: settings,
|
||||
sources: [(path: try pathTo("App/Sources/AppDelegate.swift"), compilerFlags: nil)],
|
||||
sources: [SourceFile(path: try pathTo("App/Sources/AppDelegate.swift"))],
|
||||
filesGroup: .group(name: "ProjectGroup"))
|
||||
}
|
||||
|
||||
|
|
|
@ -123,10 +123,10 @@ final class TestModelGenerator {
|
|||
dependencies: dependencies.map { Dependency.target(name: $0) })
|
||||
}
|
||||
|
||||
private func createSources(path: AbsolutePath) -> [Target.SourceFile] {
|
||||
let sources: [Target.SourceFile] = (0 ..< config.sources)
|
||||
private func createSources(path: AbsolutePath) -> [SourceFile] {
|
||||
let sources: [SourceFile] = (0 ..< config.sources)
|
||||
.map { "Sources/SourceFile\($0).swift" }
|
||||
.map { (path: path.appending(RelativePath($0)), compilerFlags: nil) }
|
||||
.map { SourceFile(path: path.appending(RelativePath($0))) }
|
||||
.shuffled()
|
||||
return sources
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ final class CacheControllerProjectMapperProviderTests: TuistUnitTestCase {
|
|||
var subject: CacheControllerProjectMapperProvider!
|
||||
|
||||
override func setUp() {
|
||||
subject = CacheControllerProjectMapperProvider()
|
||||
subject = CacheControllerProjectMapperProvider(contentHasher: ContentHasher())
|
||||
}
|
||||
|
||||
func test_mapper_includes_the_cache_build_phase_project_mapper() throws {
|
||||
|
|
|
@ -16,7 +16,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -26,7 +26,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mapper_returns_a_sequential_mapper_with_the_autogenerated_schemes_project_mapper() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(config: Config.test(cloud: .test(options: [])))
|
||||
|
@ -38,7 +38,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mappers_returns_theSigningMapper() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(config: Config.test())
|
||||
|
@ -50,7 +50,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mappers_returns_resources_namespace_project_mapper() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(config: Config.test())
|
||||
|
@ -62,7 +62,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mappers_does_not_returns_resources_namespace_project_mapper_when_disabled_autogenerated_namespace() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(
|
||||
|
@ -80,7 +80,7 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mappers_does_disable_show_environment_vars() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider()
|
||||
subject = ProjectMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Foundation
|
||||
import TuistCache
|
||||
import TuistCore
|
||||
import TuistGenerator
|
||||
import XCTest
|
||||
|
@ -11,7 +12,7 @@ final class WorkspaceMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = WorkspaceMapperProvider()
|
||||
subject = WorkspaceMapperProvider(contentHasher: ContentHasher())
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -21,7 +22,7 @@ final class WorkspaceMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mapper_does_not_return_autogenerated_project_scheme_mapper_when_autogenerated_schemes_are_disabled() throws {
|
||||
// Given
|
||||
subject = WorkspaceMapperProvider()
|
||||
subject = WorkspaceMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mappers(
|
||||
|
@ -38,7 +39,7 @@ final class WorkspaceMapperProviderTests: TuistUnitTestCase {
|
|||
|
||||
func test_mapper_returns_autogenerated_project_scheme_mapper() throws {
|
||||
// Given
|
||||
subject = WorkspaceMapperProvider()
|
||||
subject = WorkspaceMapperProvider(contentHasher: ContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mappers(config: Config.test())
|
||||
|
|
|
@ -13,6 +13,7 @@ final class MockProjectEditorMapper: ProjectEditorMapping {
|
|||
xcodeProjPath: AbsolutePath,
|
||||
setupPath: AbsolutePath?,
|
||||
configPath: AbsolutePath?,
|
||||
dependenciesPath: AbsolutePath?,
|
||||
manifests: [AbsolutePath],
|
||||
helpers: [AbsolutePath],
|
||||
templates: [AbsolutePath],
|
||||
|
@ -24,6 +25,7 @@ final class MockProjectEditorMapper: ProjectEditorMapping {
|
|||
xcodeProjPath: AbsolutePath,
|
||||
setupPath: AbsolutePath?,
|
||||
configPath: AbsolutePath?,
|
||||
dependenciesPath: AbsolutePath?,
|
||||
manifests: [AbsolutePath],
|
||||
helpers: [AbsolutePath],
|
||||
templates: [AbsolutePath],
|
||||
|
@ -34,6 +36,7 @@ final class MockProjectEditorMapper: ProjectEditorMapping {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
setupPath: setupPath,
|
||||
configPath: configPath,
|
||||
dependenciesPath: dependenciesPath,
|
||||
manifests: manifests,
|
||||
helpers: helpers,
|
||||
templates: templates,
|
||||
|
|
|
@ -21,13 +21,14 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_edit_when_there_are_helpers_and_setup_and_config() throws {
|
||||
func test_edit_when_there_are_helpers_and_setup_and_config_and_dependencies() throws {
|
||||
// Given
|
||||
let sourceRootPath = try temporaryPath()
|
||||
let xcodeProjPath = sourceRootPath.appending(component: "Project.xcodeproj")
|
||||
let manifestPaths = [sourceRootPath].map { $0.appending(component: "Project.swift") }
|
||||
let setupPath = sourceRootPath.appending(component: "Setup.swift")
|
||||
let configPath = sourceRootPath.appending(components: Constants.tuistDirectoryName, "Config.swift")
|
||||
let dependenciesPath = sourceRootPath.appending(components: Constants.tuistDirectoryName, "Dependencies.swift")
|
||||
let helperPaths = [sourceRootPath].map { $0.appending(component: "Project+Template.swift") }
|
||||
let templates = [sourceRootPath].map { $0.appending(component: "template") }
|
||||
let projectDescriptionPath = sourceRootPath.appending(component: "ProjectDescription.framework")
|
||||
|
@ -39,6 +40,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
setupPath: setupPath,
|
||||
configPath: configPath,
|
||||
dependenciesPath: dependenciesPath,
|
||||
manifests: manifestPaths,
|
||||
helpers: helperPaths,
|
||||
templates: templates,
|
||||
|
@ -47,7 +49,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
// Then
|
||||
let targetNodes = graph.targets.values.lazy.flatMap { targets in targets.compactMap { $0 } }.sorted(by: { $0.target.name < $1.target.name })
|
||||
|
||||
XCTAssertEqual(targetNodes.count, 5)
|
||||
XCTAssertEqual(targetNodes.count, 6)
|
||||
XCTAssertEqual(Set(targetNodes.last?.dependencies ?? []), Set(targetNodes.dropLast()))
|
||||
|
||||
// Generated Manifests target
|
||||
|
@ -63,7 +65,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
|
||||
// Generated Helpers target
|
||||
let helpersTarget = try XCTUnwrap(project.targets.last(where: { $0.name == "ProjectDescriptionHelpers" }))
|
||||
XCTAssertEqual(targetNodes.dropFirst().first?.target, helpersTarget)
|
||||
XCTAssertEqual(targetNodes.dropFirst().dropFirst().first?.target, helpersTarget)
|
||||
|
||||
XCTAssertEqual(helpersTarget.name, "ProjectDescriptionHelpers")
|
||||
XCTAssertEqual(helpersTarget.platform, .macOS)
|
||||
|
@ -109,6 +111,18 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
XCTAssertEqual(configTarget.filesGroup, .group(name: "Manifests"))
|
||||
XCTAssertEmpty(configTarget.dependencies)
|
||||
|
||||
// Generated Dependencies target
|
||||
let dependenciesTarget = try XCTUnwrap(project.targets.last(where: { $0.name == "Dependencies" }))
|
||||
XCTAssertEqual(targetNodes.dropFirst().first?.target, dependenciesTarget)
|
||||
|
||||
XCTAssertEqual(dependenciesTarget.name, "Dependencies")
|
||||
XCTAssertEqual(dependenciesTarget.platform, .macOS)
|
||||
XCTAssertEqual(dependenciesTarget.product, .staticFramework)
|
||||
XCTAssertEqual(dependenciesTarget.settings, expectedSettings(sourceRootPath: sourceRootPath))
|
||||
XCTAssertEqual(dependenciesTarget.sources.map { $0.path }, [dependenciesPath])
|
||||
XCTAssertEqual(dependenciesTarget.filesGroup, .group(name: "Manifests"))
|
||||
XCTAssertEmpty(dependenciesTarget.dependencies)
|
||||
|
||||
// Generated Project
|
||||
XCTAssertEqual(project.path, sourceRootPath)
|
||||
XCTAssertEqual(project.name, "Manifests")
|
||||
|
@ -132,7 +146,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
XCTAssertEqual(runAction.arguments, Arguments(launchArguments: [generateArgument: true]))
|
||||
}
|
||||
|
||||
func test_edit_when_there_are_no_helpers_and_no_setup_and_no_config() throws {
|
||||
func test_edit_when_there_are_no_helpers_and_no_setup_and_no_config_and_no_dependencies() throws {
|
||||
// Given
|
||||
let sourceRootPath = try temporaryPath()
|
||||
let xcodeProjPath = sourceRootPath.appending(component: "Project.xcodeproj")
|
||||
|
@ -148,6 +162,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
setupPath: nil,
|
||||
configPath: nil,
|
||||
dependenciesPath: nil,
|
||||
manifests: manifestPaths,
|
||||
helpers: helperPaths,
|
||||
templates: templates,
|
||||
|
@ -199,6 +214,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
let xcodeProjPath = sourceRootPath.appending(component: "Project.xcodeproj")
|
||||
let setupPath = sourceRootPath.appending(component: "Setup.swift")
|
||||
let configPath = sourceRootPath.appending(components: Constants.tuistDirectoryName, "Config.swift")
|
||||
let dependenciesPath = sourceRootPath.appending(components: Constants.tuistDirectoryName, "Dependencies.swift")
|
||||
let otherProjectPath = "Module"
|
||||
let manifestPaths = [
|
||||
sourceRootPath.appending(component: "Project.swift"),
|
||||
|
@ -215,6 +231,7 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
setupPath: setupPath,
|
||||
configPath: configPath,
|
||||
dependenciesPath: dependenciesPath,
|
||||
manifests: manifestPaths,
|
||||
helpers: helperPaths,
|
||||
templates: templates,
|
||||
|
@ -223,14 +240,13 @@ final class ProjectEditorMapperTests: TuistUnitTestCase {
|
|||
// Then
|
||||
let targetNodes = graph.targets.values.flatMap { targets in targets.compactMap { $0 } }.sorted(by: { $0.target.name < $1.target.name })
|
||||
|
||||
// expecting `targetNodes == [Config, ModuleManifests, Setup, TemporaryDirectory.XXXMainifest]`
|
||||
XCTAssertEqual(targetNodes.count, 4)
|
||||
XCTAssertEqual(targetNodes.count, 5)
|
||||
XCTAssertEmpty(targetNodes.first?.dependencies ?? [])
|
||||
XCTAssertEqual(Set(targetNodes.last?.dependencies ?? []), Set([targetNodes[0], targetNodes[2]]))
|
||||
XCTAssertEqual(Set(targetNodes.last?.dependencies ?? []), Set([targetNodes[0], targetNodes[1], targetNodes[3]]))
|
||||
|
||||
// Generated Manifests target
|
||||
let manifestOneTarget = try XCTUnwrap(project.targets.first(where: { $0.name == "ModuleManifests" }))
|
||||
XCTAssertEqual(targetNodes.dropFirst().first?.target, manifestOneTarget)
|
||||
XCTAssertEqual(targetNodes.dropFirst().dropFirst().first?.target, manifestOneTarget)
|
||||
|
||||
XCTAssertEqual(manifestOneTarget.name, "ModuleManifests")
|
||||
XCTAssertEqual(manifestOneTarget.platform, .macOS)
|
||||
|
|
|
@ -81,9 +81,15 @@ final class ProjectEditorTests: TuistUnitTestCase {
|
|||
try helpers.forEach { try FileHandler.shared.touch($0) }
|
||||
let manifests: [(Manifest, AbsolutePath)] = [(.project, directory.appending(component: "Project.swift"))]
|
||||
let tuistPath = AbsolutePath(ProcessInfo.processInfo.arguments.first!)
|
||||
let setupPath = directory.appending(components: "Setup.swift")
|
||||
let configPath = directory.appending(components: "Tuist", "Config.swift")
|
||||
let dependenciesPath = directory.appending(components: "Tuist", "Dependencies.swif")
|
||||
|
||||
resourceLocator.projectDescriptionStub = { projectDescriptionPath }
|
||||
manifestFilesLocator.locateProjectManifestsStub = manifests
|
||||
manifestFilesLocator.locateConfigStub = configPath
|
||||
manifestFilesLocator.locateDependenciesStub = dependenciesPath
|
||||
manifestFilesLocator.locateSetupStub = setupPath
|
||||
helpersDirectoryLocator.locateStub = helpersDirectory
|
||||
projectEditorMapper.mapStub = (project, graph)
|
||||
var mappedProject: Project?
|
||||
|
@ -107,6 +113,9 @@ final class ProjectEditorTests: TuistUnitTestCase {
|
|||
XCTAssertEqual(mapArgs?.helpers, helpers)
|
||||
XCTAssertEqual(mapArgs?.sourceRootPath, directory)
|
||||
XCTAssertEqual(mapArgs?.projectDescriptionPath, projectDescriptionPath)
|
||||
XCTAssertEqual(mapArgs?.configPath, configPath)
|
||||
XCTAssertEqual(mapArgs?.setupPath, setupPath)
|
||||
XCTAssertEqual(mapArgs?.dependenciesPath, dependenciesPath)
|
||||
XCTAssertEqual(project, mappedProject)
|
||||
|
||||
XCTAssertEqual(generatedProject, project)
|
||||
|
|
|
@ -101,16 +101,16 @@ final class LintCodeServiceTests: TuistUnitTestCase {
|
|||
manifestLoader.manifestsAtStub = { _ in Set([.workspace]) }
|
||||
|
||||
let target01 = Target.test(sources: [
|
||||
("/target01/file1.swift", nil),
|
||||
("/target01/file2.swift", nil),
|
||||
SourceFile(path: "/target01/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target01/file2.swift", compilerFlags: nil),
|
||||
])
|
||||
let target02 = Target.test(sources: [
|
||||
("/target02/file1.swift", nil),
|
||||
("/target02/file2.swift", nil),
|
||||
("/target02/file3.swift", nil),
|
||||
SourceFile(path: "/target02/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file2.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file3.swift", compilerFlags: nil),
|
||||
])
|
||||
let target03 = Target.test(sources: [
|
||||
("/target03/file1.swift", nil),
|
||||
SourceFile(path: "/target03/file1.swift", compilerFlags: nil),
|
||||
])
|
||||
let graph = Graph.test(
|
||||
entryPath: "/rootPath",
|
||||
|
@ -150,16 +150,16 @@ final class LintCodeServiceTests: TuistUnitTestCase {
|
|||
manifestLoader.manifestsAtStub = { _ in Set([.project]) }
|
||||
|
||||
let target01 = Target.test(sources: [
|
||||
("/target01/file1.swift", nil),
|
||||
("/target01/file2.swift", nil),
|
||||
SourceFile(path: "/target01/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target01/file2.swift", compilerFlags: nil),
|
||||
])
|
||||
let target02 = Target.test(sources: [
|
||||
("/target02/file1.swift", nil),
|
||||
("/target02/file2.swift", nil),
|
||||
("/target02/file3.swift", nil),
|
||||
SourceFile(path: "/target02/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file2.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file3.swift", compilerFlags: nil),
|
||||
])
|
||||
let target03 = Target.test(sources: [
|
||||
("/target03/file1.swift", nil),
|
||||
SourceFile(path: "/target03/file1.swift", compilerFlags: nil),
|
||||
])
|
||||
let graph = Graph.test(
|
||||
entryPath: "/rootPath",
|
||||
|
@ -199,16 +199,16 @@ final class LintCodeServiceTests: TuistUnitTestCase {
|
|||
manifestLoader.manifestsAtStub = { _ in Set([.workspace]) }
|
||||
|
||||
let target01 = Target.test(name: "Target1", sources: [
|
||||
("/target01/file1.swift", nil),
|
||||
("/target01/file2.swift", nil),
|
||||
SourceFile(path: "/target01/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target01/file2.swift", compilerFlags: nil),
|
||||
])
|
||||
let target02 = Target.test(name: "Target2", sources: [
|
||||
("/target02/file1.swift", nil),
|
||||
("/target02/file2.swift", nil),
|
||||
("/target02/file3.swift", nil),
|
||||
SourceFile(path: "/target02/file1.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file2.swift", compilerFlags: nil),
|
||||
SourceFile(path: "/target02/file3.swift", compilerFlags: nil),
|
||||
])
|
||||
let target03 = Target.test(name: "Target3", sources: [
|
||||
("/target03/file1.swift", nil),
|
||||
SourceFile(path: "/target03/file1.swift", compilerFlags: nil),
|
||||
])
|
||||
let graph = Graph.test(targets: [
|
||||
"/path1": [.test(target: target01), .test(target: target02), .test(target: target03)],
|
||||
|
|
|
@ -7,7 +7,7 @@ import XCTest
|
|||
@testable import TuistLoader
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class ManifestFileLocatorTests: TuistUnitTestCase {
|
||||
final class ManifestFilesLocatorTests: TuistUnitTestCase {
|
||||
private var subject: ManifestFilesLocator!
|
||||
|
||||
override func setUp() {
|
||||
|
@ -155,6 +155,104 @@ final class ManifestFileLocatorTests: TuistUnitTestCase {
|
|||
XCTAssertNil(configPath)
|
||||
}
|
||||
|
||||
func test_locateDependencies() throws {
|
||||
// Given
|
||||
let paths = try createFiles([
|
||||
"Module01/File01.swift",
|
||||
"Module01/File02.swift",
|
||||
"Module01/File03.swift",
|
||||
|
||||
"Module02/File01.swift",
|
||||
"Module02/File01.swift",
|
||||
"Module02/Subdir01/File01.swift",
|
||||
"Module02/Subdir01/File02.swift",
|
||||
|
||||
"File01.swift",
|
||||
"File02.swift",
|
||||
"Tuist/Dependencies.swift",
|
||||
])
|
||||
|
||||
// When
|
||||
let dependenciesPath = subject.locateDependencies(at: try temporaryPath())
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(dependenciesPath)
|
||||
XCTAssertEqual(paths.last, dependenciesPath)
|
||||
}
|
||||
|
||||
func test_locateDependencies_traversing() throws {
|
||||
// Given
|
||||
let paths = try createFiles([
|
||||
"Module01/File01.swift",
|
||||
"Module01/File02.swift",
|
||||
"Module01/File03.swift",
|
||||
|
||||
"Module02/File01.swift",
|
||||
"Module02/File01.swift",
|
||||
"Module02/Subdir01/File01.swift",
|
||||
"Module02/Subdir01/File02.swift",
|
||||
|
||||
"File01.swift",
|
||||
"File02.swift",
|
||||
"Tuist/Dependencies.swift",
|
||||
])
|
||||
let locatingPath = paths[5] // "Module02/Subdir01/File01.swift"
|
||||
|
||||
// When
|
||||
let dependenciesPath = subject.locateDependencies(at: locatingPath)
|
||||
|
||||
// Then
|
||||
XCTAssertNotNil(dependenciesPath)
|
||||
XCTAssertEqual(paths.last, dependenciesPath)
|
||||
}
|
||||
|
||||
func test_locateDependencies_where_config_not_exist() throws {
|
||||
// Given
|
||||
try createFiles([
|
||||
"Module01/File01.swift",
|
||||
"Module01/File02.swift",
|
||||
"Module01/File03.swift",
|
||||
|
||||
"Module02/File01.swift",
|
||||
"Module02/File01.swift",
|
||||
"Module02/Subdir01/File01.swift",
|
||||
"Module02/Subdir01/File02.swift",
|
||||
|
||||
"File01.swift",
|
||||
"File02.swift",
|
||||
])
|
||||
|
||||
// When
|
||||
let dependenciesPath = subject.locateDependencies(at: try temporaryPath())
|
||||
|
||||
// Then
|
||||
XCTAssertNil(dependenciesPath)
|
||||
}
|
||||
|
||||
func test_locateDependencies_traversing_where_config_not_exist() throws {
|
||||
// Given
|
||||
let paths = try createFiles([
|
||||
"Module01/File01.swift",
|
||||
"Module01/File02.swift",
|
||||
"Module01/File03.swift",
|
||||
|
||||
"Module02/File01.swift",
|
||||
"Module02/File01.swift",
|
||||
"Module02/Subdir01/File01.swift",
|
||||
"Module02/Subdir01/File02.swift",
|
||||
|
||||
"File01.swift",
|
||||
"File02.swift",
|
||||
])
|
||||
let locatingPath = paths[5] // "Module02/Subdir01/File01.swift"
|
||||
|
||||
// When
|
||||
let dependenciesPath = subject.locateDependencies(at: locatingPath)
|
||||
|
||||
// Then
|
||||
XCTAssertNil(dependenciesPath)
|
||||
}
|
||||
|
||||
func test_locateSetup() throws {
|
||||
// Given
|
||||
let paths = try createFiles([
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Feature: Edit an existing project using Tuist
|
||||
|
||||
Scenario: The project is an application with helpers, sub projects, Config.swift and Project.swift (ios_app_with_helpers)
|
||||
Scenario: The project is an application with helpers, sub projects, Config.swift, Dependencies.swift and Project.swift (ios_app_with_helpers)
|
||||
Given that tuist is available
|
||||
And I have a working directory
|
||||
Then I copy the fixture ios_app_with_helpers into the working directory
|
||||
|
@ -11,3 +11,4 @@ Feature: Edit an existing project using Tuist
|
|||
Then I should be able to build for macOS the scheme AppSupportManifests
|
||||
Then I should be able to build for macOS the scheme Setup
|
||||
Then I should be able to build for macOS the scheme Config
|
||||
Then I should be able to build for macOS the scheme Dependencies
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
Feature: List targets sorted by number of dependencies
|
||||
|
||||
Scenario: The project is the microfeature fixture
|
||||
Given that tuist is available
|
||||
And I have a working directory
|
||||
Then I copy the fixture ios_workspace_with_microfeature_architecture into the working directory
|
||||
Then run tuist migration list-targets for UIComponents in ios_workspace_with_microfeature_architecture matches list-targets-ui-components.json
|
||||
Then run tuist migration list-targets for Core in ios_workspace_with_microfeature_architecture matches list-targets-core.json
|
||||
Then run tuist migration list-targets for Data in ios_workspace_with_microfeature_architecture matches list-targets-data.json
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"dependenciesCount" : 0,
|
||||
"targetName" : "Core"
|
||||
},
|
||||
{
|
||||
"dependenciesCount" : 2,
|
||||
"targetName" : "CoreTests"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"dependenciesCount" : 1,
|
||||
"targetName" : "Data"
|
||||
},
|
||||
{
|
||||
"dependenciesCount" : 2,
|
||||
"targetName" : "DataTests"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"dependenciesCount" : 1,
|
||||
"targetName" : "UIComponents"
|
||||
},
|
||||
{
|
||||
"dependenciesCount" : 2,
|
||||
"targetName" : "UIComponentsTests"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,12 @@
|
|||
Then(/run tuist migration list-targets for (.+) in ios_workspace_with_microfeature_architecture matches (.+)$/) do |framework, json_file|
|
||||
fixtures_path = File.expand_path("../../fixtures", __dir__)
|
||||
fixture_path = File.join(fixtures_path, "ios_workspace_with_microfeature_architecture/Frameworks/#{framework}Framework/#{framework}.xcodeproj/")
|
||||
resources_path = File.expand_path("../resources", __dir__)
|
||||
expected_json = File.read("#{resources_path}/#{json_file}")
|
||||
|
||||
assert(false, "Project #{fixture_path} not found") unless File.exist?(fixture_path)
|
||||
|
||||
out, s = Open3.capture2("swift", "run", "tuist", "migration", "list-targets", "-p", fixture_path)
|
||||
|
||||
assert(out.include?(expected_json))
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
import ProjectDescription
|
||||
|
||||
let dependencies = Dependencies([
|
||||
.carthage(name: "Alamofire", requirement: .exact("5.3.0")),
|
||||
])
|
|
@ -79,3 +79,26 @@ tuist migration check-empty-settings -p Project.xcodeproj -t MyApp
|
|||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
## List targets sorted by dependencies
|
||||
|
||||
Migration of big Xcode projects to Tuist can happen iteratively, one target at a time. It makes sense to start from the target with the lowest number of dependencies.
|
||||
To help with that, Tuist includes a command that lists the targets of a project sorted by number dependencies ascending. The count only includes dependencies that are declared in build phases.
|
||||
|
||||
```bash
|
||||
tuist migration list-targets -p Project.xcodeproj
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
<ArgumentsTable
|
||||
args={[
|
||||
{
|
||||
long: '`--xcodeproj-path`',
|
||||
short: '`-p`',
|
||||
description:
|
||||
'Path to the Xcode project whose build settings will be checked.',
|
||||
required: true,
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -53,10 +53,15 @@ After extracting the build settings into `.xcconfig` files, we recommend adding
|
|||
tuist migration check-empty-settings -p Project.xcodeproj -t MyApp
|
||||
```
|
||||
|
||||
#### Migrate the most independent projects first
|
||||
#### Migrate the most independent targets first
|
||||
|
||||
Those are usually simpler and contain fewer dependencies than the rest.
|
||||
Those are usually simpler since they contain fewer dependencies than the rest.
|
||||
That makes them good candidates from which we can start the migration.
|
||||
Use this command to list the targets of a project, sorted by number of dependencies. We recommend starting from the top, with the target that has the lowest number of dependencies
|
||||
|
||||
```
|
||||
tuist migration list-targets -p Project.xcodeproj
|
||||
```
|
||||
|
||||
#### Remove broken references
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
"author": "Pedro Piñera <pedro@ppinera.es>",
|
||||
"dependencies": {
|
||||
"@brainhubeu/react-carousel": "^1.19.26",
|
||||
"@emotion/core": "^10.0.35",
|
||||
"@emotion/core": "^10.1.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.11",
|
||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
||||
"@mdx-js/mdx": "^1.6.19",
|
||||
"@mdx-js/react": "^1.6.19",
|
||||
"@mdx-js/tag": "^0.20.3",
|
||||
|
@ -26,26 +26,26 @@
|
|||
"gatsby": "^2.24.89",
|
||||
"gatsby-image": "^2.4.21",
|
||||
"gatsby-plugin-favicon": "3.1.6",
|
||||
"gatsby-plugin-feed": "^2.5.19",
|
||||
"gatsby-plugin-feed": "^2.5.20",
|
||||
"gatsby-plugin-google-analytics": "^2.3.19",
|
||||
"gatsby-plugin-manifest": "2.4.36",
|
||||
"gatsby-plugin-mdx": "^1.2.51",
|
||||
"gatsby-plugin-manifest": "2.4.37",
|
||||
"gatsby-plugin-mdx": "^1.2.53",
|
||||
"gatsby-plugin-meta-redirect": "^1.1.1",
|
||||
"gatsby-plugin-netlify": "^2.3.24",
|
||||
"gatsby-plugin-netlify": "^2.3.25",
|
||||
"gatsby-plugin-next-seo": "^1.6.1",
|
||||
"gatsby-plugin-offline": "^3.2.37",
|
||||
"gatsby-plugin-offline": "^3.2.38",
|
||||
"gatsby-plugin-purgecss": "^5.0.0",
|
||||
"gatsby-plugin-react-helmet": "^3.3.14",
|
||||
"gatsby-plugin-react-svg": "^3.0.0",
|
||||
"gatsby-plugin-robots-txt": "^1.5.3",
|
||||
"gatsby-plugin-sharp": "2.6.43",
|
||||
"gatsby-plugin-sharp": "2.6.44",
|
||||
"gatsby-plugin-sitemap": "^2.4.17",
|
||||
"gatsby-plugin-theme-ui": "^0.3.0",
|
||||
"gatsby-redirect-from": "^0.2.4",
|
||||
"gatsby-remark-check-links": "^2.1.0",
|
||||
"gatsby-remark-copy-linked-files": "^2.3.19",
|
||||
"gatsby-remark-embedder": "^4.0.0",
|
||||
"gatsby-remark-images": "^3.3.39",
|
||||
"gatsby-remark-embedder": "^4.1.0",
|
||||
"gatsby-remark-images": "^3.3.40",
|
||||
"gatsby-remark-smartypants": "^2.3.13",
|
||||
"gatsby-source-filesystem": "^2.3.37",
|
||||
"gatsby-theme-tailwindcss": "^1.1.0",
|
||||
|
@ -77,9 +77,9 @@
|
|||
"react-use-visibility": "^0.3.0",
|
||||
"remark-slug": "^6.0.0",
|
||||
"semantic-ui-react": "^2.0.1",
|
||||
"slug": "^4.0.1",
|
||||
"slug": "^4.0.2",
|
||||
"theme-ui": "^0.3.1",
|
||||
"twin.macro": "^1.10.0",
|
||||
"twin.macro": "^1.12.0",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
@ -1073,10 +1073,10 @@
|
|||
"@emotion/utils" "0.11.3"
|
||||
"@emotion/weak-memoize" "0.2.5"
|
||||
|
||||
"@emotion/core@^10.0.0", "@emotion/core@^10.0.14", "@emotion/core@^10.0.27", "@emotion/core@^10.0.35", "@emotion/core@^10.0.9":
|
||||
version "10.0.35"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.35.tgz#513fcf2e22cd4dfe9d3894ed138c9d7a859af9b3"
|
||||
integrity sha512-sH++vJCdk025fBlRZSAhkRlSUoqSqgCzYf5fMOmqqi3bM6how+sQpg3hkgJonj8GxXM4WbD7dRO+4tegDB9fUw==
|
||||
"@emotion/core@^10.0.0", "@emotion/core@^10.0.14", "@emotion/core@^10.0.27", "@emotion/core@^10.0.9", "@emotion/core@^10.1.1":
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
|
||||
integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
"@emotion/cache" "^10.0.27"
|
||||
|
@ -1225,10 +1225,10 @@
|
|||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.32"
|
||||
|
||||
"@fortawesome/react-fontawesome@^0.1.11":
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a"
|
||||
integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg==
|
||||
"@fortawesome/react-fontawesome@^0.1.12":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.12.tgz#fbdea86e8b73032895e6ded1ee1dbb1874902d1a"
|
||||
integrity sha512-kV6HtqotM3K4YIXlTVvomuIi6QgGCvYm++ImyEx2wwgmSppZ6kbbA29ASwjAUBD63j2OFU0yoxeXpZkjrrX0qQ==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
|
@ -7765,16 +7765,16 @@ gatsby-plugin-favicon@3.1.6:
|
|||
html-react-parser "^0.6.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
gatsby-plugin-feed@^2.5.19:
|
||||
version "2.5.19"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-feed/-/gatsby-plugin-feed-2.5.19.tgz#fab25c72f760457cc5e98f6a34309db8950f38fa"
|
||||
integrity sha512-W79GwBFNdt7U/FiKHKKKOFVlPLqGaI7VchE4yMt4NXj+p2mUNDY+JwRy98/wKaRfDCWyvL4ulmaVaEH7EVqaQw==
|
||||
gatsby-plugin-feed@^2.5.20:
|
||||
version "2.5.20"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-feed/-/gatsby-plugin-feed-2.5.20.tgz#a4dba4eddb4786028956933853382872a0adc362"
|
||||
integrity sha512-JNSPLddBuu12Le6/0Es8KcgZgqy40+BmgcFGVPDBdT3oRsp/AtWWNMDvOoXU+avdObzyB3l1IkMbSBSmUxTSMg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
"@hapi/joi" "^15.1.1"
|
||||
common-tags "^1.8.0"
|
||||
fs-extra "^8.1.0"
|
||||
gatsby-plugin-utils "^0.2.39"
|
||||
gatsby-plugin-utils "^0.2.40"
|
||||
lodash.merge "^4.6.2"
|
||||
rss "^1.2.2"
|
||||
|
||||
|
@ -7786,21 +7786,21 @@ gatsby-plugin-google-analytics@^2.3.19:
|
|||
"@babel/runtime" "^7.11.2"
|
||||
minimatch "3.0.4"
|
||||
|
||||
gatsby-plugin-manifest@2.4.36:
|
||||
version "2.4.36"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.4.36.tgz#d4923df481dd5cb527b51bf78c807b1784777445"
|
||||
integrity sha512-HedqeBVR9WcZeVMUb07GqRvO04RUbo1eWxY1URdfVaNrRHIz2KjAceVJVghSbHJRkbl4aSrkKc19aW5GlnBNqg==
|
||||
gatsby-plugin-manifest@2.4.37:
|
||||
version "2.4.37"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.4.37.tgz#36ec84cc09f9ee6f872b82e7397570da294878d5"
|
||||
integrity sha512-Gub8QanC6lwkF5PLDUhZm6AGYSjjriVFWIF97d9TS9c5ofS/wlwHdnc7i3VozyWwIDjfKSaUyOZyHKVyB9vVMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
gatsby-core-utils "^1.3.23"
|
||||
gatsby-plugin-utils "0.2.39"
|
||||
gatsby-plugin-utils "^0.2.40"
|
||||
semver "^7.3.2"
|
||||
sharp "^0.25.4"
|
||||
|
||||
gatsby-plugin-mdx@^1.2.51:
|
||||
version "1.2.51"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.2.51.tgz#d1285505026011bc3d75ae26d32cde9cab03d106"
|
||||
integrity sha512-wddtB88h4aHhS+tjNWYKT848S7LHuh2/svMg+7qdl0nL4D0tvNua76OUZxGUZDtkf0y8ajcAufps4qGeYA9mVQ==
|
||||
gatsby-plugin-mdx@^1.2.53:
|
||||
version "1.2.53"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.2.53.tgz#09e3c07f2169a7c01804ed6822e8ded3e2952270"
|
||||
integrity sha512-hh/+0R0nKjJwUkufRo2Hx4ijRwXzUdV2E0Wk6q7tHj7QUWrUOaHYLbH8x0V31Vi7xqG389tWuTUzPx6Z4gE54w==
|
||||
dependencies:
|
||||
"@babel/core" "^7.11.6"
|
||||
"@babel/generator" "^7.11.6"
|
||||
|
@ -7846,10 +7846,10 @@ gatsby-plugin-meta-redirect@^1.1.1:
|
|||
dependencies:
|
||||
fs-extra "^7.0.0"
|
||||
|
||||
gatsby-plugin-netlify@^2.3.24:
|
||||
version "2.3.24"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-netlify/-/gatsby-plugin-netlify-2.3.24.tgz#468bda55b28234b99dadd007e2b2b7285db90d44"
|
||||
integrity sha512-OJgV1XNbffLt7C9zzOviC9oS3Yvfv5pV908H54p0PB37yP4ohfKF66NEyO0svWBYBZdf2QpeUUXJdn0ji4/AAQ==
|
||||
gatsby-plugin-netlify@^2.3.25:
|
||||
version "2.3.25"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-netlify/-/gatsby-plugin-netlify-2.3.25.tgz#24b297c342922d5f4a247eac493417be9edddd85"
|
||||
integrity sha512-iPFGzobb8tn13i3nIdF1o79UWrgzz4xarI1DN4mpG87SJQ170dFMVM42MrpO4TXlYG4gFgsgcZHG6PRP6vzvcw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
fs-extra "^8.1.0"
|
||||
|
@ -7867,10 +7867,10 @@ gatsby-plugin-next-seo@^1.6.1:
|
|||
schema-dts "0.6.0"
|
||||
type-fest "^0.15.1"
|
||||
|
||||
gatsby-plugin-offline@^3.2.37:
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-offline/-/gatsby-plugin-offline-3.2.37.tgz#2c57f390425264cdc670c7b039cd1ca9857ea264"
|
||||
integrity sha512-GwutE/t5t1SlPv8tQPboxGdwk2ERMFoVLE/LAMgLKnt+SonDk3kLd2+j7qwyA60qdVjyET1gGa2296gyPnLPsw==
|
||||
gatsby-plugin-offline@^3.2.38:
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-offline/-/gatsby-plugin-offline-3.2.38.tgz#17d097f468a5fb3f080dfed5fbd52525ecfa10dd"
|
||||
integrity sha512-9HbElk+9oPzM1baSx8z0+9EhBt3L0uA7aL9u0VC+xLSBXxJ16mD6+wczFl6C007udCxpHSUGl/neK3tjLunnXA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
cheerio "^1.0.0-rc.3"
|
||||
|
@ -7933,10 +7933,10 @@ gatsby-plugin-robots-txt@^1.5.3:
|
|||
"@babel/runtime" "^7.11.2"
|
||||
generate-robotstxt "^8.0.3"
|
||||
|
||||
gatsby-plugin-sharp@2.6.43:
|
||||
version "2.6.43"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-2.6.43.tgz#dd7f4fcf848337e64a014f491318f61d530a38a8"
|
||||
integrity sha512-/V5T8SzS/GgpQEgp34blLwzwpc+dxJJVPRXGBOZtKdcTDRAfCYuY2IuRf5FJa65lSuPRQmHdG6AoQIka/0xnWA==
|
||||
gatsby-plugin-sharp@2.6.44:
|
||||
version "2.6.44"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-2.6.44.tgz#cb30c41b2488639e060e461f819f8f5026bfc28d"
|
||||
integrity sha512-GhTzNMmdcJtVQirba97P6r6IEQvTSTHnWzbskW8g0TsaZ+Wx20/uFnDZCquT4F8EzaqM626RqK53vUmiXFHMGw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
async "^3.2.0"
|
||||
|
@ -7986,10 +7986,10 @@ gatsby-plugin-typescript@^2.4.24:
|
|||
"@babel/runtime" "^7.11.2"
|
||||
babel-plugin-remove-graphql-queries "^2.9.20"
|
||||
|
||||
gatsby-plugin-utils@0.2.39, gatsby-plugin-utils@^0.2.39:
|
||||
version "0.2.39"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-utils/-/gatsby-plugin-utils-0.2.39.tgz#aa0c5e9469977b476884d53fe97b28fd771f2a78"
|
||||
integrity sha512-Ar6m9hjWodd4+AwHQZYBe08XGmYS1t5nghZdyKGaBcdMZRP3GGmoeyU7LH/bdkvJFf0dyoY1Me0oJV4ZNT6Abg==
|
||||
gatsby-plugin-utils@^0.2.39, gatsby-plugin-utils@^0.2.40:
|
||||
version "0.2.40"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-utils/-/gatsby-plugin-utils-0.2.40.tgz#20e997d10efb9a0368270f79ce2e6001346f6336"
|
||||
integrity sha512-RKjmpPhmi8TDR9hAKxmD4ZJMje3BLs6nt6mxMWT0F8gf5giCYEywplJikyCvaPfuyaFlq1hMmFaVvzmeZNussg==
|
||||
dependencies:
|
||||
joi "^17.2.1"
|
||||
|
||||
|
@ -8091,20 +8091,20 @@ gatsby-remark-copy-linked-files@^2.3.19:
|
|||
probe-image-size "^5.0.0"
|
||||
unist-util-visit "^1.4.1"
|
||||
|
||||
gatsby-remark-embedder@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-embedder/-/gatsby-remark-embedder-4.0.0.tgz#ed0e2ddc669d21b4ba8e2cdffbd3f79aa5ee1102"
|
||||
integrity sha512-nBVxhnuKcZZaibN6ipRKiAQibRcEJRBC6fuF2JW9op+W/uvgpqkaIzvH/JSKzESDQWX/eg+MnR8UAKsfR/6BKA==
|
||||
gatsby-remark-embedder@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-embedder/-/gatsby-remark-embedder-4.1.0.tgz#3c08c33e09625fde3b0fa4c6c1a55e36fd5c9b1c"
|
||||
integrity sha512-cp0Q1SDBniW57s6FB0prTM8n+yyT+e/zYKcDISHXvRWNGG/T9eRDDl99f4mQjKjWkA23QXihh/+4BfBrIwcwvQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
fetch-retry "^4.0.1"
|
||||
node-fetch "^2.6.1"
|
||||
unist-util-visit "^2.0.3"
|
||||
|
||||
gatsby-remark-images@^3.3.39:
|
||||
version "3.3.39"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-images/-/gatsby-remark-images-3.3.39.tgz#a356968fd62cdf2a511cf7624a79b60c70b7ed36"
|
||||
integrity sha512-nAP/xOo8MTwYmWrA2xesoLyBoYrdpqYuiYd/N5c+IWExD+u6ARG2WdUGG9hJAyT32GACUxgLDaKqSvMSD2u2cg==
|
||||
gatsby-remark-images@^3.3.40:
|
||||
version "3.3.40"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-remark-images/-/gatsby-remark-images-3.3.40.tgz#da605c42de282225a1a133bbfb0aadbecaf47071"
|
||||
integrity sha512-GVFvGdOOApG5SaNqPgbTqfiXoaf07e3bIa1pZgIyy8lC0EpL6Fq8RDtcVz+Xu+fzxEMbRJGdTF7bXxAkJ6Er0w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
chalk "^2.4.2"
|
||||
|
@ -15118,10 +15118,10 @@ slice-ansi@^2.1.0:
|
|||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
slug@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/slug/-/slug-4.0.1.tgz#24775a0fbb7fabae2f94e34bdfab0a32ed3bc1ba"
|
||||
integrity sha512-g3eofiaQdlNRrjGSW+gtSHmAdE8Oq/tFIrFlP6oQ4AW4M3l3FegO2C4TV6MM4eHdnpllJJdgG892Vt/KqpV9kQ==
|
||||
slug@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/slug/-/slug-4.0.2.tgz#35a62b4e71582778ac08bb30a1bf439fd0a43ea7"
|
||||
integrity sha512-c5XbWkwxHU13gAdSvBHQgnGy2sxv/REMz0ugcM0SOSBCO/N4wfU0TDBC3pgdOwVGjZwGnLBTRljXzdVYE+KYNw==
|
||||
|
||||
slugify@^1.4.4:
|
||||
version "1.4.5"
|
||||
|
@ -16010,10 +16010,10 @@ tailwind.macro@^1.0.0-alpha.10:
|
|||
dset "^2.0.1"
|
||||
tailwindcss "^1.0.0-beta.8"
|
||||
|
||||
tailwindcss@^1.0.0-beta.8, tailwindcss@^1.2.0, tailwindcss@^1.8.8:
|
||||
version "1.8.9"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.8.9.tgz#86aa05f019b4975675d1c67a2c9962d9c833ea13"
|
||||
integrity sha512-VgoBxIyF+Ipf+x3eDz3tPA8A5blGBPkswA2xM6fxCmAwAmhlIr9Zk3exft44BUf3q9mxorvjCRtxBrklfPDSoQ==
|
||||
tailwindcss@^1.0.0-beta.8, tailwindcss@^1.2.0, tailwindcss@^1.9.6:
|
||||
version "1.9.6"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.9.6.tgz#0c5089911d24e1e98e592a31bfdb3d8f34ecf1a0"
|
||||
integrity sha512-nY8WYM/RLPqGsPEGEV2z63riyQPcHYZUJpAwdyBzVpxQHOHqHE+F/fvbCeXhdF1+TA5l72vSkZrtYCB9hRcwkQ==
|
||||
dependencies:
|
||||
"@fullhuman/postcss-purgecss" "^2.1.2"
|
||||
autoprefixer "^9.4.5"
|
||||
|
@ -16449,10 +16449,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
|||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
twin.macro@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-1.10.0.tgz#b13d0934457d4cae54f1504a3a2ed744ed603960"
|
||||
integrity sha512-+K9xvBvlx7iQ+CRatqNO/3VVV2+D+rbxexViKFtkjlICd+7A9hp5/8IOQ3SUPTQp80Ouist3Zcs/89quSLaoZg==
|
||||
twin.macro@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/twin.macro/-/twin.macro-1.12.0.tgz#3917743cee5e0693df9679ab182aba4de962be45"
|
||||
integrity sha512-KGRed9d42MkZDIS4wnCvUp5PsPnRx3eaMpvTAOQTz7/K7ArELhg/+SrbJxniQWh6QtRm7/LotlzRfvVW3/qGHQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.10.2"
|
||||
babel-plugin-macros "^2.8.0"
|
||||
|
@ -16463,7 +16463,7 @@ twin.macro@^1.10.0:
|
|||
dset "^2.0.1"
|
||||
lodash.merge "^4.6.2"
|
||||
string-similarity "^4.0.1"
|
||||
tailwindcss "^1.8.8"
|
||||
tailwindcss "^1.9.6"
|
||||
timsort "^0.3.0"
|
||||
|
||||
type-check@~0.3.2:
|
||||
|
|
Loading…
Reference in New Issue