Skip to content

Commit

Permalink
Tuist plugins 2.0 fetch (#3577)
Browse files Browse the repository at this point in the history
* Tuist executable (#3494)

* Search for executable in path with tuist prefix if not tuist command

* Add acceptance test for new task

* Update documentation

* Check if executable exists

* Update projects/docs/docs/commands/task.md

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>

* Update Sources/TuistKit/Commands/TuistCommand.swift

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>

* Fix finding command

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>

* WIP: Fetch release

* Install tasks to release dir

* Execute plugin task

* Add TuistServiceTests

* Add FetchServiceTests

* Remove DependenciesFetchCommand

* Add fetch plugins test

* Fix PluginServiceTests

* Add test for new logic in PluginServiceTests

* Minor changes

* Revert changes to Package.resolved

* Add missing throws

* Rename gitID to gitReference

* Move to Swift-based APIs

* Fix fetch command

* Uncomment analytics code

* Fix acceptance tests for tuist edit

* Restore Package.resolved

* Fix plugins.feature acceptance test

* Add fetch to tasks.feature

* Add fetch to tasks.feature

* Add fetch to scaffold.feature

* Fix scaffold.feature

* Fix tasks.feature

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>
  • Loading branch information
fortmarek and danieleformichelli committed Dec 2, 2021
1 parent 0fc7ea6 commit ef0bf91
Show file tree
Hide file tree
Showing 30 changed files with 786 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import TuistGraph
public final class MockDependenciesController: DependenciesControlling {
public init() {}

var invokedFetch = false
var fetchStub: ((AbsolutePath, TuistGraph.Dependencies, TSCUtility.Version?) throws -> TuistCore.DependenciesGraph)?
public var invokedFetch = false
public var fetchStub: ((AbsolutePath, TuistGraph.Dependencies, TSCUtility.Version?) throws -> TuistCore.DependenciesGraph)?

public func fetch(
at path: AbsolutePath,
Expand All @@ -21,8 +21,8 @@ public final class MockDependenciesController: DependenciesControlling {
return try fetchStub?(path, dependencies, swiftVersion) ?? .none
}

var invokedUpdate = false
var updateStub: ((AbsolutePath, TuistGraph.Dependencies, TSCUtility.Version?) throws -> TuistCore.DependenciesGraph)?
public var invokedUpdate = false
public var updateStub: ((AbsolutePath, TuistGraph.Dependencies, TSCUtility.Version?) throws -> TuistCore.DependenciesGraph)?

public func update(
at path: AbsolutePath,
Expand All @@ -33,8 +33,8 @@ public final class MockDependenciesController: DependenciesControlling {
return try updateStub?(path, dependencies, swiftVersion) ?? .none
}

var invokedSave = false
var saveStub: ((TuistGraph.DependenciesGraph, AbsolutePath) throws -> Void)?
public var invokedSave = false
public var saveStub: ((TuistGraph.DependenciesGraph, AbsolutePath) throws -> Void)?

public func save(dependenciesGraph: TuistGraph.DependenciesGraph, to path: AbsolutePath) throws {
invokedSave = true
Expand Down
26 changes: 12 additions & 14 deletions Sources/TuistGraph/Models/PluginLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import Foundation

/// The location to a directory containing a `Plugin` manifest.
public enum PluginLocation: Hashable, Equatable {
public enum GitReference: Hashable, Equatable {
case sha(String)
case tag(String)
}

/// An absolute path `String` to a directory a `Plugin` manifest.
///
/// Example:
Expand All @@ -10,21 +15,14 @@ public enum PluginLocation: Hashable, Equatable {
/// ```
case local(path: String)

/// A `URL` to a `git` repository pointing at a `tag`.
/// A `URL` to a `git` repository pointing at a `GitReference` - either sha or tag.
///
/// Example:
/// ```
/// .gitWithTag(url: "https://git/helpers.git", tag: "1.0.0")
/// ```
case gitWithTag(url: String, tag: String)

/// A `URL` to a `git` repository pointing at a commit `sha`.
///
/// Example:
/// Examples:
/// ```
/// .gitWithHash(url: "https://git/helpers.git", sha: "d06b4b3d")
/// .git(url: "https://git/helpers.git", gitReference: .tag("1.0.0"))
/// .git(url: "https://git/helpers.git", gitReference: .sha("1.0.0"))
/// ```
case gitWithSha(url: String, sha: String)
case git(url: String, gitReference: GitReference)
}

// MARK: - description
Expand All @@ -34,9 +32,9 @@ extension PluginLocation: CustomStringConvertible {
switch self {
case let .local(path):
return "local path: \(path)"
case let .gitWithTag(url, tag):
case let .git(url, .tag(tag)):
return "git url: \(url), tag: \(tag)"
case let .gitWithSha(url, sha):
case let .git(url, .sha(sha)):
return "git url: \(url), sha: \(sha)"
}
}
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion Sources/TuistKit/Commands/DependenciesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ struct DependenciesCommand: ParsableCommand {
commandName: "dependencies",
abstract: "A set of commands for dependencies' management.",
subcommands: [
DependenciesFetchCommand.self,
DependenciesUpdateCommand.self,
DependenciesCleanCommand.self,
]
Expand Down
35 changes: 35 additions & 0 deletions Sources/TuistKit/Commands/FetchCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import ArgumentParser
import Foundation
import TSCBasic

enum FetchCategory: String, CaseIterable, RawRepresentable, ExpressibleByArgument {
case dependencies
case plugins
}

/// A command to fetch any remote content necessary to interact with the project.
struct FetchCommand: ParsableCommand {
static var configuration: CommandConfiguration {
CommandConfiguration(
commandName: "fetch",
abstract: "Fetches any remote content necessary to interact with the project."
)
}

@Option(
name: .shortAndLong,
help: "The path to the directory that contains the definition of the project.",
completion: .directory
)
var path: String?

@Argument(help: "Categories to be fetched.")
var fetchCategories: [FetchCategory] = FetchCategory.allCases

func run() throws {
try FetchService().run(
path: path,
fetchCategories: fetchCategories
)
}
}
38 changes: 27 additions & 11 deletions Sources/TuistKit/Commands/TuistCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public struct TuistCommand: ParsableCommand {
DumpCommand.self,
EditCommand.self,
ExecCommand.self,
FetchCommand.self,
FocusCommand.self,
GenerateCommand.self,
GraphCommand.self,
Expand Down Expand Up @@ -46,11 +47,10 @@ public struct TuistCommand: ParsableCommand {
let executeCommand: () throws -> Void
do {
let processedArguments = Array(processArguments(arguments)?.dropFirst() ?? [])
let commandName = processedArguments.first ?? ""
let isTuistCommand = Self.configuration.subcommands
.map { $0._commandName }
.map({ $0._commandName })
.contains(processedArguments.first ?? "")
if isTuistCommand || !System.shared.commandExists("tuist-" + commandName) {
if isTuistCommand {
if processedArguments.first == ScaffoldCommand.configuration.commandName {
try ScaffoldCommand.preprocess(processedArguments)
}
Expand All @@ -64,17 +64,11 @@ public struct TuistCommand: ParsableCommand {
executeCommand = { try execute(command) }
} else {
executeCommand = {
try TuistService().run(processedArguments)
try executeTask(with: processedArguments)
}
}
} catch {
let exitCode = self.exitCode(for: error).rawValue
if exitCode == 0 {
logger.info("\(fullMessage(for: error))")
} else {
logger.error("\(fullMessage(for: error))")
}
_exit(exitCode)
handleParseError(error)
}
do {
try executeCommand()
Expand All @@ -92,6 +86,28 @@ public struct TuistCommand: ParsableCommand {
}
}
}

private static func executeTask(with processedArguments: [String]) throws {
do {
try TuistService().run(processedArguments)
} catch TuistServiceError.taskUnavailable {
do {
_ = try parseAsRoot(processedArguments)
} catch {
handleParseError(error)
}
}
}

private static func handleParseError(_ error: Error) -> Never {
let exitCode = self.exitCode(for: error).rawValue
if exitCode == 0 {
logger.info("\(fullMessage(for: error))")
} else {
logger.error("\(fullMessage(for: error))")
}
_exit(exitCode)
}

private static func execute(_ command: ParsableCommand) throws {
var command = command
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,78 @@
import Foundation
import TSCBasic
import TuistCore
import TuistDependencies
import TuistLoader
import TuistPlugin
import TuistSupport
import TuistLoader
import TuistDependencies

final class DependenciesFetchService {
final class FetchService {
private let pluginService: PluginServicing
private let configLoader: ConfigLoading
private let dependenciesController: DependenciesControlling
private let dependenciesModelLoader: DependenciesModelLoading
private let configLoading: ConfigLoading
private let converter: ManifestModelConverting

init(
pluginService: PluginServicing = PluginService(),
configLoader: ConfigLoading = ConfigLoader(manifestLoader: CachedManifestLoader()),
dependenciesController: DependenciesControlling = DependenciesController(),
dependenciesModelLoader: DependenciesModelLoading = DependenciesModelLoader(),
configLoading: ConfigLoading = ConfigLoader(manifestLoader: ManifestLoader()),
converter: ManifestModelConverting = ManifestModelConverter()
) {
self.pluginService = pluginService
self.configLoader = configLoader
self.dependenciesController = dependenciesController
self.dependenciesModelLoader = dependenciesModelLoader
self.configLoading = configLoading
self.converter = converter
}

func run(path: String?) throws {
func run(path: String?, fetchCategories: [FetchCategory]) throws {
let path = self.path(path)
try fetchCategories.forEach {
switch $0 {
case .plugins:
try fetchPlugins(path: path)
case .dependencies:
try fetchDependencies(path: path)
}
}
}

// MARK: - Helpers

private func path(_ path: String?) -> AbsolutePath {
if let path = path {
return AbsolutePath(path, relativeTo: currentPath)
} else {
return currentPath
}
}

private var currentPath: AbsolutePath {
FileHandler.shared.currentPath
}

private func fetchPlugins(path: AbsolutePath) throws {
logger.info("Resolving and fetching plugins.", metadata: .section)

let config = try configLoader.loadConfig(path: path)
try pluginService.fetchRemotePlugins(using: config)

logger.info("Plugins resolved and fetched successfully.", metadata: .success)
}

private func fetchDependencies(path: AbsolutePath) throws {
guard FileHandler.shared.exists(
path.appending(components: Constants.tuistDirectoryName, Manifest.dependencies.fileName(path))
) else {
return
}

logger.info("Resolving and fetching dependencies.", metadata: .section)

let path = self.path(path)
let dependencies = try dependenciesModelLoader.loadDependencies(at: path)

let config = try configLoading.loadConfig(path: path)
let config = try configLoader.loadConfig(path: path)
let swiftVersion = config.swiftVersion

let dependenciesManifest = try dependenciesController.fetch(
Expand All @@ -47,14 +90,5 @@ final class DependenciesFetchService {

logger.info("Dependencies resolved and fetched successfully.", metadata: .success)
}

// MARK: - Helpers

private func path(_ path: String?) -> AbsolutePath {
if let path = path {
return AbsolutePath(path, relativeTo: FileHandler.shared.currentPath)
} else {
return FileHandler.shared.currentPath
}
}
}

1 change: 1 addition & 0 deletions Sources/TuistKit/Services/PluginArchiveService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ final class PluginArchiveService {
from: zipPath,
to: path.appending(component: zipName)
)
try archiver.delete()

logger.notice(
"Plugin was successfully archived. Create a new Github release and attach the file \(zipPath.pathString) as an artifact.",
Expand Down
45 changes: 44 additions & 1 deletion Sources/TuistKit/Services/TuistService.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
import Foundation
import TuistSupport
import TuistPlugin
import TuistLoader

enum TuistServiceError: FatalError {
case taskUnavailable

var type: ErrorType {
switch self {
case .taskUnavailable:
return .abortSilent
}
}

var description: String {
switch self {
case .taskUnavailable:
return "Task was not found in the environment"
}
}
}

final class TuistService: NSObject {
private let pluginService: PluginServicing
private let configLoader: ConfigLoading

init(
pluginService: PluginServicing = PluginService(),
configLoader: ConfigLoading = ConfigLoader(manifestLoader: CachedManifestLoader())
) {
self.pluginService = pluginService
self.configLoader = configLoader
}

func run(_ arguments: [String]) throws {
var arguments = arguments

let commandName = "tuist-\(arguments[0])"
arguments[0] = commandName

let config = try configLoader.loadConfig(path: FileHandler.shared.currentPath)
let pluginExecutables = try pluginService.remotePluginPaths(using: config)
.compactMap(\.releasePath)
.flatMap(FileHandler.shared.contentsOfDirectory)
.filter { $0.basename.hasPrefix("tuist-") }
if let pluginCommand = pluginExecutables.first(where: { $0.basename == commandName }) {
arguments[0] = pluginCommand.pathString
} else if System.shared.commandExists(commandName) {
arguments[0] = commandName
} else {
throw TuistServiceError.taskUnavailable
}

try System.shared.runAndPrint(arguments)
}
Expand Down
Loading

0 comments on commit ef0bf91

Please sign in to comment.