Generate projects in a DerivedProjects directory (#146)

* Add option to generate the projects and workspaces in the ~/.tuist/DerivedProjects directory

* Remove unnecessary import

* Test ProjectDirectoryHelper

* Update CHANGELOG

* Remove unnecessary line
This commit is contained in:
Pedro Piñera Buendía 2018-10-09 10:04:51 +03:00 committed by GitHub
parent 41dff04ae9
commit 8fcbe8bc3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 547 additions and 93 deletions

View File

@ -6,6 +6,12 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
### Added
- Geration of projects and workspaces in the `~/.tuist/DerivedProjects` directory https://github.com/tuist/tuist/pull/146 by pepibumur.
## 0.7.0
### Added
- Support for actions https://github.com/tuist/tuist/pull/136 by @pepibumur.
## 0.6.0

View File

@ -21,7 +21,11 @@ let package = Package(
targets: [
.target(
name: "TuistShared",
dependencies: []
dependencies: ["Utility", "TuistCore"]
),
.target(
name: "TuistSharedTesting",
dependencies: ["TuistShared"]
),
.target(
name: "TuistKit",
@ -29,7 +33,7 @@ let package = Package(
),
.testTarget(
name: "TuistKitTests",
dependencies: ["TuistKit", "TuistCoreTesting"]
dependencies: ["TuistKit", "TuistCoreTesting", "TuistSharedTesting"]
),
.target(
name: "tuist",
@ -37,7 +41,7 @@ let package = Package(
),
.target(
name: "TuistEnvKit",
dependencies: ["Utility", "TuistCore", "TuistShared"]
dependencies: ["Utility", "TuistCore", "TuistShared", "TuistSharedTesting"]
),
.testTarget(
name: "TuistEnvKitTests",

View File

@ -1,5 +1,6 @@
import Foundation
import TuistCore
import TuistShared
import Utility
final class UninstallCommand: Command {

View File

@ -1,57 +0,0 @@
import Basic
import Foundation
/// Protocol that defines the interface of a local environment controller.
/// It manages the local directory where tuistenv stores the tuist versions and user settings.
protocol EnvironmentControlling: AnyObject {
/// Returns the versions directory.
var versionsDirectory: AbsolutePath { get }
/// Returns the path to the settings.
var settingsPath: AbsolutePath { get }
}
/// Local environment controller.
class EnvironmentController: EnvironmentControlling {
/// Returns the default local directory.
static let defaultDirectory: AbsolutePath = AbsolutePath(URL(fileURLWithPath: NSHomeDirectory()).path).appending(component: ".tuist")
// MARK: - Attributes
/// Directory.
private let directory: AbsolutePath
/// Filemanager.
private let fileManager: FileManager = .default
init(directory: AbsolutePath = EnvironmentController.defaultDirectory) {
self.directory = directory
setup()
}
// MARK: - EnvironmentControlling
/// Sets up the local environment.
///
/// - Throws: an error if something the directories creation fails.
private func setup() {
// Note: It should be safe to use try! here
if !fileManager.fileExists(atPath: directory.asString) {
try! fileManager.createDirectory(atPath: directory.asString, withIntermediateDirectories: true, attributes: nil)
}
if !fileManager.fileExists(atPath: versionsDirectory.asString) {
try! fileManager.createDirectory(atPath: versionsDirectory.asString, withIntermediateDirectories: true, attributes: nil)
}
}
/// Returns the directory where all the versions are.
var versionsDirectory: AbsolutePath {
return directory.appending(component: "Versions")
}
/// Settings path.
var settingsPath: AbsolutePath {
return directory.appending(component: "settings.json")
}
}

View File

@ -1,5 +1,6 @@
import Foundation
import TuistCore
import TuistShared
/// The class that conforms this protocol exposes an interface for interacting with the user settings.
protocol SettingsControlling: AnyObject {

View File

@ -1,6 +1,7 @@
import Basic
import Foundation
import TuistCore
import TuistShared
import Utility
protocol VersionsControlling: AnyObject {

View File

@ -60,9 +60,7 @@ class FocusCommand: NSObject, Command {
let workspacePath = try workspaceGenerator.generate(path: path,
graph: graph,
options: GenerationOptions(buildConfiguration: config),
system: system,
printer: printer,
resourceLocator: resourceLocator)
directory: .manifest)
try opener.open(path: workspacePath)
}

View File

@ -60,9 +60,7 @@ class GenerateCommand: NSObject, Command {
try workspaceGenerator.generate(path: path,
graph: graph,
options: GenerationOptions(buildConfiguration: config),
system: system,
printer: printer,
resourceLocator: resourceLocator)
directory: .manifest)
printer.print(success: "Project generated.")
}

View File

@ -0,0 +1,10 @@
import Foundation
/// Enum whose cases represent different destinations where projects can be generated.
///
/// - manifest: Generates the workspace and the project in the same folder where the project manifest is.
/// - derivedProjects: Generates the workspace and the project in the ~/.tuist/DerivedProjects directory.
enum GenerationDirectory {
case manifest
case derivedProjects
}

View File

@ -0,0 +1,86 @@
import Basic
import Foundation
import TuistCore
import TuistShared
/// Protocol that defines the interface of a class that can be used to set up the directories where projects are generated.
protocol ProjectDirectoryHelping: AnyObject {
/// Sets up the directory where the project will be generated. For commands like build or test where
/// the Xcode project is necessary to build the project, the project is generated in the ~/.tuist/DerivedProjects directory.
/// Optionally, it can be deleted afterwards.
///
/// - Parameters:
/// - project: Project that will be generated and whose directory need to be set up.
/// - directory: Directory where the
/// - Returns: The path to the directory where the project should be created.
/// - Throws: An error if the project directory cannot be set up.
func setupProjectDirectory(project: Project, directory: GenerationDirectory) throws -> AbsolutePath
/// Sets up the directory where the element with the given name and path will be generated.
///
/// - Parameters:
/// - name: Name of the element whose directory will be set up (workspace or project name).
/// - path: Path where the project or workspace is defined. The directory that contains the manifest.
/// - directory: Directory where the project will be generated.
/// - Returns: The path to the directory where the element (workspace or project) should be created.
/// - Throws: An error if the directory cannot be set up.
func setupDirectory(name: String, path: AbsolutePath, directory: GenerationDirectory) throws -> AbsolutePath
}
/// Default implementation of the ProjectDirectoryHelping protocol.
class ProjectDirectoryHelper: ProjectDirectoryHelping {
/// Environment controller instance. It's necessary to obtain the directory where the derived projects are generated into.
let environmentController: EnvironmentControlling
/// File handler instance for file operations.
let fileHandler: FileHandling
// MARK: - Init
/// Deafult constructor.
///
/// - Parameter environmentController: Environment controller instance. It's necessary to obtain the directory where the derived projects are generated into.
/// - Parameter fileHandler: File handler instance for file operations.
init(environmentController: EnvironmentControlling = EnvironmentController(),
fileHandler: FileHandling = FileHandler()) {
self.environmentController = environmentController
self.fileHandler = fileHandler
}
/// Sets up the directory where the project will be generated. For commands like build or test where
/// the Xcode project is necessary to build the project, the project is generated in the ~/.tuist/DerivedProjects directory.
/// Optionally, it can be deleted afterwards.
///
/// - Parameters:
/// - project: Project that will be generated and whose directory need to be set up.
/// - directory: Directory where the project will be generated.
/// - Returns: The path to the directory where the project should be created.
/// - Throws: An error if the directory cannot be set up.
func setupProjectDirectory(project: Project, directory: GenerationDirectory) throws -> AbsolutePath {
return try setupDirectory(name: project.name,
path: project.path,
directory: directory)
}
/// Sets up the directory where the element with the given name and path will be generated.
///
/// - Parameters:
/// - name: Name of the element whose directory will be set up (workspace or project name).
/// - path: Path where the project or workspace is defined. The directory that contains the manifest.
/// - directory: Directory where the project will be generated.
/// - Returns: The path to the directory where the element (workspace or project) should be created.
/// - Throws: An error if the directory cannot be set up.
func setupDirectory(name: String, path: AbsolutePath, directory: GenerationDirectory) throws -> AbsolutePath {
switch directory {
case .derivedProjects:
let md5 = path.asString.md5
let path = environmentController.derivedProjectsDirectory.appending(component: "\(name)-\(md5)")
if !fileHandler.exists(path) {
try fileHandler.createFolder(path)
}
return path
case .manifest:
return path
}
}
}

View File

@ -5,23 +5,30 @@ import xcodeproj
protocol WorkspaceGenerating: AnyObject {
@discardableResult
func generate(path: AbsolutePath,
graph: Graphing,
options: GenerationOptions,
system: Systeming,
printer: Printing,
resourceLocator: ResourceLocating) throws -> AbsolutePath
func generate(path: AbsolutePath, graph: Graphing, options: GenerationOptions, directory: GenerationDirectory) throws -> AbsolutePath
}
final class WorkspaceGenerator: WorkspaceGenerating {
// MARK: - Attributes
let projectGenerator: ProjectGenerating
let system: Systeming
let printer: Printing
let resourceLocator: ResourceLocating
let projectDirectoryHelper: ProjectDirectoryHelping
// MARK: - Init
init(projectGenerator: ProjectGenerating = ProjectGenerator()) {
init(projectGenerator: ProjectGenerating = ProjectGenerator(),
system: Systeming = System(),
printer: Printing = Printer(),
resourceLocator: ResourceLocating = ResourceLocator(),
projectDirectoryHelper: ProjectDirectoryHelping = ProjectDirectoryHelper()) {
self.projectGenerator = projectGenerator
self.system = system
self.printer = printer
self.resourceLocator = resourceLocator
self.projectDirectoryHelper = projectDirectoryHelper
}
// MARK: - WorkspaceGenerating
@ -30,19 +37,23 @@ final class WorkspaceGenerator: WorkspaceGenerating {
func generate(path: AbsolutePath,
graph: Graphing,
options: GenerationOptions,
system: Systeming = System(),
printer: Printing = Printer(),
resourceLocator: ResourceLocating = ResourceLocator()) throws -> AbsolutePath {
directory: GenerationDirectory = .manifest) throws -> AbsolutePath {
let workspaceRootPath = try projectDirectoryHelper.setupDirectory(name: graph.name,
path: graph.entryPath,
directory: directory)
let workspaceName = "\(graph.name).xcworkspace"
printer.print(section: "Generating workspace \(workspaceName)")
let workspacePath = path.appending(component: workspaceName)
let workspacePath = workspaceRootPath.appending(component: workspaceName)
let workspaceData = XCWorkspaceData(children: [])
let workspace = XCWorkspace(data: workspaceData)
try graph.projects.forEach { project in
let sourceRootPath = try projectDirectoryHelper.setupProjectDirectory(project: project,
directory: directory)
let xcodeprojPath = try projectGenerator.generate(project: project,
options: options,
graph: graph,
sourceRootPath: nil,
sourceRootPath: sourceRootPath,
system: system,
printer: printer,
resourceLocator: resourceLocator)

View File

@ -0,0 +1,74 @@
import Basic
import Foundation
import TuistCore
/// Protocol that defines the interface of a local environment controller.
/// It manages the local directory where tuistenv stores the tuist versions and user settings.
public protocol EnvironmentControlling: AnyObject {
/// Returns the versions directory.
var versionsDirectory: AbsolutePath { get }
/// Returns the path to the settings.
var settingsPath: AbsolutePath { get }
/// Returns the directory where all the derived projects are generated.
var derivedProjectsDirectory: AbsolutePath { get }
}
/// Local environment controller.
public class EnvironmentController: EnvironmentControlling {
/// Returns the default local directory.
static let defaultDirectory: AbsolutePath = AbsolutePath(URL(fileURLWithPath: NSHomeDirectory()).path).appending(component: ".tuist")
// MARK: - Attributes
/// Directory.
private let directory: AbsolutePath
/// File handler instance.
private let fileHandler: FileHandling
/// Default public constructor.
public convenience init() {
self.init(directory: EnvironmentController.defaultDirectory,
fileHandler: FileHandler())
}
/// Default environment constroller constructor.
///
/// - Parameters:
/// - directory: Directory where the Tuist environment files will be stored.
/// - fileHandler: File handler instance to perform file operations.
init(directory: AbsolutePath, fileHandler: FileHandling) {
self.directory = directory
self.fileHandler = fileHandler
setup()
}
// MARK: - EnvironmentControlling
/// Sets up the local environment.
private func setup() {
[directory, versionsDirectory, derivedProjectsDirectory].forEach {
if !fileHandler.exists($0) {
// Note: It should be safe to use try! here
try! fileHandler.createFolder($0)
}
}
}
/// Returns the directory where all the versions are.
public var versionsDirectory: AbsolutePath {
return directory.appending(component: "Versions")
}
/// Returns the directory where all the derived projects are generated.
public var derivedProjectsDirectory: AbsolutePath {
return directory.appending(component: "DerivedProjects")
}
/// Settings path.
public var settingsPath: AbsolutePath {
return directory.appending(component: "settings.json")
}
}

View File

@ -0,0 +1,282 @@
// swiftlint:disable all
//
// String+MD5.swift
// Kingfisher
//
// To date, adding CommonCrypto to a Swift framework is problematic. See:
// http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework
// We're using a subset and modified version of CryptoSwift as an alternative.
// The following is an altered source version that only includes MD5. The original software can be found at:
// https://github.com/krzyzanowskim/CryptoSwift
// This is the original copyright notice:
/*
Copyright (C) 2014 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
- This notice may not be removed or altered from any source or binary distribution.
*/
import Foundation
public extension String {
public var md5: String {
if let data = data(using: .utf8, allowLossyConversion: true) {
let message = data.withUnsafeBytes { bytes -> [UInt8] in
return Array(UnsafeBufferPointer(start: bytes, count: data.count))
}
let MD5Calculator = MD5(message)
let MD5Data = MD5Calculator.calculate()
var MD5String = String()
for c in MD5Data {
MD5String += String(format: "%02x", c)
}
return MD5String
} else {
return self
}
}
}
/** array of bytes, little-endian representation */
func arrayOfBytes<T>(_ value: T, length: Int? = nil) -> [UInt8] {
let totalBytes = length ?? (MemoryLayout<T>.size * 8)
let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
valuePointer.pointee = value
let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in
var bytes = [UInt8](repeating: 0, count: totalBytes)
for j in 0 ..< min(MemoryLayout<T>.size, totalBytes) {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
}
return bytes
}
#if swift(>=4.1)
valuePointer.deinitialize(count: 1)
valuePointer.deallocate()
#else
valuePointer.deinitialize()
valuePointer.deallocate(capacity: 1)
#endif
return bytes
}
extension Int {
/** Array of bytes with optional padding (little-endian) */
func bytes(_ totalBytes: Int = MemoryLayout<Int>.size) -> [UInt8] {
return arrayOfBytes(self, length: totalBytes)
}
}
extension NSMutableData {
/** Convenient way to append bytes */
func appendBytes(_ arrayOfBytes: [UInt8]) {
append(arrayOfBytes, length: arrayOfBytes.count)
}
}
protocol HashProtocol {
var message: Array<UInt8> { get }
/** Common part for hash calculation. Prepare header data. */
func prepare(_ len: Int) -> Array<UInt8>
}
extension HashProtocol {
func prepare(_ len: Int) -> Array<UInt8> {
var tmpMessage = message
// Step 1. Append Padding Bits
tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
// append "0" bit until message length in bits 448 (mod 512)
var msgLength = tmpMessage.count
var counter = 0
while msgLength % len != (len - 8) {
counter += 1
msgLength += 1
}
tmpMessage += Array<UInt8>(repeating: 0, count: counter)
return tmpMessage
}
}
func toUInt32Array(_ slice: ArraySlice<UInt8>) -> Array<UInt32> {
var result = Array<UInt32>()
result.reserveCapacity(16)
for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt32>.size) {
let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
let d3 = UInt32(slice[idx])
let val: UInt32 = d0 | d1 | d2 | d3
result.append(val)
}
return result
}
struct BytesIterator: IteratorProtocol {
let chunkSize: Int
let data: [UInt8]
init(chunkSize: Int, data: [UInt8]) {
self.chunkSize = chunkSize
self.data = data
}
var offset = 0
mutating func next() -> ArraySlice<UInt8>? {
let end = min(chunkSize, data.count - offset)
let result = data[offset ..< offset + end]
offset += result.count
return result.count > 0 ? result : nil
}
}
struct BytesSequence: Sequence {
let chunkSize: Int
let data: [UInt8]
func makeIterator() -> BytesIterator {
return BytesIterator(chunkSize: chunkSize, data: data)
}
}
func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
return ((value << bits) & 0xFFFF_FFFF) | (value >> (32 - bits))
}
class MD5: HashProtocol {
static let size = 16 // 128 / 8
let message: [UInt8]
init(_ message: [UInt8]) {
self.message = message
}
/** specifies the per-round shift amounts */
private let shifts: [UInt32] = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
]
/** binary integer part of the sines of integers (Radians) */
private let sines: [UInt32] = [
0xD76A_A478, 0xE8C7_B756, 0x2420_70DB, 0xC1BD_CEEE,
0xF57C_0FAF, 0x4787_C62A, 0xA830_4613, 0xFD46_9501,
0x6980_98D8, 0x8B44_F7AF, 0xFFFF_5BB1, 0x895C_D7BE,
0x6B90_1122, 0xFD98_7193, 0xA679_438E, 0x49B4_0821,
0xF61E_2562, 0xC040_B340, 0x265E_5A51, 0xE9B6_C7AA,
0xD62F_105D, 0x0244_1453, 0xD8A1_E681, 0xE7D3_FBC8,
0x21E1_CDE6, 0xC337_07D6, 0xF4D5_0D87, 0x455A_14ED,
0xA9E3_E905, 0xFCEF_A3F8, 0x676F_02D9, 0x8D2A_4C8A,
0xFFFA_3942, 0x8771_F681, 0x6D9D_6122, 0xFDE5_380C,
0xA4BE_EA44, 0x4BDE_CFA9, 0xF6BB_4B60, 0xBEBF_BC70,
0x289B_7EC6, 0xEAA1_27FA, 0xD4EF_3085, 0x4881D05,
0xD9D4_D039, 0xE6DB_99E5, 0x1FA2_7CF8, 0xC4AC_5665,
0xF429_2244, 0x432A_FF97, 0xAB94_23A7, 0xFC93_A039,
0x655B_59C3, 0x8F0C_CC92, 0xFFEF_F47D, 0x8584_5DD1,
0x6FA8_7E4F, 0xFE2C_E6E0, 0xA301_4314, 0x4E08_11A1,
0xF753_7E82, 0xBD3A_F235, 0x2AD7_D2BB, 0xEB86_D391,
]
private let hashes: [UInt32] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476]
func calculate() -> [UInt8] {
var tmpMessage = prepare(64)
tmpMessage.reserveCapacity(tmpMessage.count + 4)
// hash values
var hh = hashes
// Step 2. Append Length a 64-bit representation of lengthInBits
let lengthInBits = (message.count * 8)
let lengthBytes = lengthInBits.bytes(64 / 8)
tmpMessage += lengthBytes.reversed()
// Process the message in successive 512-bit chunks:
let chunkSizeBytes = 512 / 8 // 64
for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
// break chunk into sixteen 32-bit words M[j], 0 j 15
var M = toUInt32Array(chunk)
assert(M.count == 16, "Invalid array")
// Initialize hash value for this chunk:
var A: UInt32 = hh[0]
var B: UInt32 = hh[1]
var C: UInt32 = hh[2]
var D: UInt32 = hh[3]
var dTemp: UInt32 = 0
// Main loop
for j in 0 ..< sines.count {
var g = 0
var F: UInt32 = 0
switch j {
case 0 ... 15:
F = (B & C) | ((~B) & D)
g = j
break
case 16 ... 31:
F = (D & B) | (~D & C)
g = (5 * j + 1) % 16
break
case 32 ... 47:
F = B ^ C ^ D
g = (3 * j + 5) % 16
break
case 48 ... 63:
F = C ^ (B | (~D))
g = (7 * j) % 16
break
default:
break
}
dTemp = D
D = C
C = B
B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j])
A = dTemp
}
hh[0] = hh[0] &+ A
hh[1] = hh[1] &+ B
hh[2] = hh[2] &+ C
hh[3] = hh[3] &+ D
}
var result = [UInt8]()
result.reserveCapacity(hh.count / 4)
hh.forEach {
let itemLE = $0.littleEndian
let r1 = UInt8(itemLE & 0xFF)
let r2 = UInt8((itemLE >> 8) & 0xFF)
let r3 = UInt8((itemLE >> 16) & 0xFF)
let r4 = UInt8((itemLE >> 24) & 0xFF)
result += [r1, r2, r3, r4]
}
return result
}
}
// swiftlint:enable all

View File

@ -1,8 +1,8 @@
import Basic
import Foundation
@testable import TuistEnvKit
import TuistShared
class MockEnvironmentController: EnvironmentControlling {
public class MockEnvironmentController: EnvironmentControlling {
let directory: TemporaryDirectory
var setupCallCount: UInt = 0
var setupErrorStub: Error?
@ -14,11 +14,15 @@ class MockEnvironmentController: EnvironmentControlling {
attributes: nil)
}
var versionsDirectory: AbsolutePath {
public var versionsDirectory: AbsolutePath {
return directory.path.appending(component: "Versions")
}
var settingsPath: AbsolutePath {
public var derivedProjectsDirectory: AbsolutePath {
return directory.path.appending(component: "DerivedProjects")
}
public var settingsPath: AbsolutePath {
return directory.path.appending(component: "settings.json")
}

View File

@ -1,6 +1,7 @@
import Basic
import Foundation
@testable import TuistEnvKit
@testable import TuistSharedTesting
import XCTest
final class SettingsControllerTests: XCTestCase {

View File

@ -2,6 +2,7 @@ import Basic
import Foundation
@testable import TuistCoreTesting
@testable import TuistEnvKit
@testable import TuistSharedTesting
import Utility
import XCTest

View File

@ -50,7 +50,7 @@ final class FocusCommandTests: XCTestCase {
let result = try parser.parse([FocusCommand.command, "-c", "Debug"])
var configuration: BuildConfiguration?
let error = NSError.test()
workspaceGenerator.generateStub = { _, _, options, _, _, _ in
workspaceGenerator.generateStub = { _, _, options, _ in
configuration = options.buildConfiguration
throw error
}
@ -63,7 +63,7 @@ final class FocusCommandTests: XCTestCase {
func test_run() throws {
let result = try parser.parse([FocusCommand.command, "-c", "Debug"])
let workspacePath = AbsolutePath("/test.xcworkspace")
workspaceGenerator.generateStub = { _, _, _, _, _, _ in
workspaceGenerator.generateStub = { _, _, _, _ in
workspacePath
}
try subject.run(with: result)

View File

@ -44,7 +44,7 @@ final class GenerateCommandTests: XCTestCase {
let result = try parser.parse([GenerateCommand.command, "-c", "Debug"])
var configuration: BuildConfiguration?
let error = NSError.test()
workspaceGenerator.generateStub = { _, _, options, _, _, _ in
workspaceGenerator.generateStub = { _, _, options, _ in
configuration = options.buildConfiguration
throw error
}

View File

@ -4,14 +4,9 @@ import TuistCore
@testable import TuistKit
final class MockWorkspaceGenerator: WorkspaceGenerating {
var generateStub: ((AbsolutePath, Graphing, GenerationOptions, Systeming, Printing, ResourceLocating) throws -> AbsolutePath)?
var generateStub: ((AbsolutePath, Graphing, GenerationOptions, GenerationDirectory) throws -> AbsolutePath)?
func generate(path: AbsolutePath,
graph: Graphing,
options: GenerationOptions,
system: Systeming,
printer: Printing,
resourceLocator: ResourceLocating) throws -> AbsolutePath {
return (try generateStub?(path, graph, options, system, printer, resourceLocator)) ?? AbsolutePath("/test")
func generate(path: AbsolutePath, graph: Graphing, options: GenerationOptions, directory: GenerationDirectory) throws -> AbsolutePath {
return (try generateStub?(path, graph, options, directory)) ?? AbsolutePath("/test")
}
}

View File

@ -0,0 +1,38 @@
import Basic
@testable import TuistCoreTesting
@testable import TuistKit
@testable import TuistSharedTesting
import XCTest
final class ProjectDirectoryHelperTests: XCTestCase {
var subject: ProjectDirectoryHelper!
var fileHandler: MockFileHandler!
var environmentController: MockEnvironmentController!
override func setUp() {
super.setUp()
fileHandler = try! MockFileHandler()
environmentController = try! MockEnvironmentController()
subject = ProjectDirectoryHelper(environmentController: environmentController,
fileHandler: fileHandler)
}
func test_setupDirectory_when_derivedProjects() throws {
let path = fileHandler.currentPath
let got = try subject.setupDirectory(name: "MyApp",
path: path,
directory: .derivedProjects)
let expected = environmentController.derivedProjectsDirectory.appending(component: "MyApp-\(path.asString.md5)")
XCTAssertEqual(got, expected)
XCTAssertTrue(fileHandler.exists(expected))
}
func test_setupDirectory_when_manifest() throws {
let path = fileHandler.currentPath
let got = try subject.setupDirectory(name: "name",
path: path,
directory: .manifest)
XCTAssertEqual(got, path)
}
}