diff --git a/CHANGELOG.md b/CHANGELOG.md index e423d525a..c7fa5a996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Package.swift b/Package.swift index f46991df2..99ec80fa5 100644 --- a/Package.swift +++ b/Package.swift @@ -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", diff --git a/Sources/TuistEnvKit/Commands/UninstallCommand.swift b/Sources/TuistEnvKit/Commands/UninstallCommand.swift index 1a9149834..1bd6b2a29 100644 --- a/Sources/TuistEnvKit/Commands/UninstallCommand.swift +++ b/Sources/TuistEnvKit/Commands/UninstallCommand.swift @@ -1,5 +1,6 @@ import Foundation import TuistCore +import TuistShared import Utility final class UninstallCommand: Command { diff --git a/Sources/TuistEnvKit/Environment/EnvironmentController.swift b/Sources/TuistEnvKit/Environment/EnvironmentController.swift deleted file mode 100644 index b451f04cb..000000000 --- a/Sources/TuistEnvKit/Environment/EnvironmentController.swift +++ /dev/null @@ -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") - } -} diff --git a/Sources/TuistEnvKit/Settings/SettingsController.swift b/Sources/TuistEnvKit/Settings/SettingsController.swift index d26a7fdc1..be2a5c361 100644 --- a/Sources/TuistEnvKit/Settings/SettingsController.swift +++ b/Sources/TuistEnvKit/Settings/SettingsController.swift @@ -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 { diff --git a/Sources/TuistEnvKit/Versions/VersionsController.swift b/Sources/TuistEnvKit/Versions/VersionsController.swift index 00a5bd950..4057cb246 100644 --- a/Sources/TuistEnvKit/Versions/VersionsController.swift +++ b/Sources/TuistEnvKit/Versions/VersionsController.swift @@ -1,6 +1,7 @@ import Basic import Foundation import TuistCore +import TuistShared import Utility protocol VersionsControlling: AnyObject { diff --git a/Sources/TuistKit/Commands/FocusCommand.swift b/Sources/TuistKit/Commands/FocusCommand.swift index e64d003de..543547806 100644 --- a/Sources/TuistKit/Commands/FocusCommand.swift +++ b/Sources/TuistKit/Commands/FocusCommand.swift @@ -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) } diff --git a/Sources/TuistKit/Commands/GenerateCommand.swift b/Sources/TuistKit/Commands/GenerateCommand.swift index 3934b3018..e8ee46a6a 100644 --- a/Sources/TuistKit/Commands/GenerateCommand.swift +++ b/Sources/TuistKit/Commands/GenerateCommand.swift @@ -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.") } diff --git a/Sources/TuistKit/Generator/GenerationDirectory.swift b/Sources/TuistKit/Generator/GenerationDirectory.swift new file mode 100644 index 000000000..6c0bdfb73 --- /dev/null +++ b/Sources/TuistKit/Generator/GenerationDirectory.swift @@ -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 +} diff --git a/Sources/TuistKit/Generator/ProjectDirectoryHelper.swift b/Sources/TuistKit/Generator/ProjectDirectoryHelper.swift new file mode 100644 index 000000000..6b9fc7021 --- /dev/null +++ b/Sources/TuistKit/Generator/ProjectDirectoryHelper.swift @@ -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 + } + } +} diff --git a/Sources/TuistKit/Generator/WorkspaceGenerator.swift b/Sources/TuistKit/Generator/WorkspaceGenerator.swift index a06146de2..0c7ff708a 100644 --- a/Sources/TuistKit/Generator/WorkspaceGenerator.swift +++ b/Sources/TuistKit/Generator/WorkspaceGenerator.swift @@ -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) diff --git a/Sources/TuistShared/EnvironmentController.swift b/Sources/TuistShared/EnvironmentController.swift new file mode 100644 index 000000000..a78624319 --- /dev/null +++ b/Sources/TuistShared/EnvironmentController.swift @@ -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") + } +} diff --git a/Sources/TuistShared/String+MD5.swift b/Sources/TuistShared/String+MD5.swift new file mode 100644 index 000000000..767a4ad6b --- /dev/null +++ b/Sources/TuistShared/String+MD5.swift @@ -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 + 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(_ value: T, length: Int? = nil) -> [UInt8] { + let totalBytes = length ?? (MemoryLayout.size * 8) + + let valuePointer = UnsafeMutablePointer.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.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.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 { get } + + /** Common part for hash calculation. Prepare header data. */ + func prepare(_ len: Int) -> Array +} + +extension HashProtocol { + func prepare(_ len: Int) -> Array { + 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(repeating: 0, count: counter) + return tmpMessage + } +} + +func toUInt32Array(_ slice: ArraySlice) -> Array { + var result = Array() + result.reserveCapacity(16) + + for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.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? { + 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 diff --git a/Tests/TuistEnvKitTests/Environment/Mocks/MockEnvironmentController.swift b/Sources/TuistSharedTesting/MockEnvironmentController.swift similarity index 69% rename from Tests/TuistEnvKitTests/Environment/Mocks/MockEnvironmentController.swift rename to Sources/TuistSharedTesting/MockEnvironmentController.swift index e2773ba95..01febab57 100644 --- a/Tests/TuistEnvKitTests/Environment/Mocks/MockEnvironmentController.swift +++ b/Sources/TuistSharedTesting/MockEnvironmentController.swift @@ -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") } diff --git a/Tests/TuistEnvKitTests/Settings/SettingsControllerTests.swift b/Tests/TuistEnvKitTests/Settings/SettingsControllerTests.swift index 31088d487..70f35a17a 100644 --- a/Tests/TuistEnvKitTests/Settings/SettingsControllerTests.swift +++ b/Tests/TuistEnvKitTests/Settings/SettingsControllerTests.swift @@ -1,6 +1,7 @@ import Basic import Foundation @testable import TuistEnvKit +@testable import TuistSharedTesting import XCTest final class SettingsControllerTests: XCTestCase { diff --git a/Tests/TuistEnvKitTests/Versions/VersionsControllerTests.swift b/Tests/TuistEnvKitTests/Versions/VersionsControllerTests.swift index 171a960d4..faa94e092 100644 --- a/Tests/TuistEnvKitTests/Versions/VersionsControllerTests.swift +++ b/Tests/TuistEnvKitTests/Versions/VersionsControllerTests.swift @@ -2,6 +2,7 @@ import Basic import Foundation @testable import TuistCoreTesting @testable import TuistEnvKit +@testable import TuistSharedTesting import Utility import XCTest diff --git a/Tests/TuistKitTests/Commands/FocusCommandTests.swift b/Tests/TuistKitTests/Commands/FocusCommandTests.swift index 295b11c78..9d16b3b93 100644 --- a/Tests/TuistKitTests/Commands/FocusCommandTests.swift +++ b/Tests/TuistKitTests/Commands/FocusCommandTests.swift @@ -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) diff --git a/Tests/TuistKitTests/Commands/GenerateCommandTests.swift b/Tests/TuistKitTests/Commands/GenerateCommandTests.swift index a880e729f..c27b94c88 100644 --- a/Tests/TuistKitTests/Commands/GenerateCommandTests.swift +++ b/Tests/TuistKitTests/Commands/GenerateCommandTests.swift @@ -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 } diff --git a/Tests/TuistKitTests/Generator/Mocks/MockWorkspaceGenerator.swift b/Tests/TuistKitTests/Generator/Mocks/MockWorkspaceGenerator.swift index e3d76d052..c6049c96a 100644 --- a/Tests/TuistKitTests/Generator/Mocks/MockWorkspaceGenerator.swift +++ b/Tests/TuistKitTests/Generator/Mocks/MockWorkspaceGenerator.swift @@ -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") } } diff --git a/Tests/TuistKitTests/Generator/ProjectDirectoryHelperTests.swift b/Tests/TuistKitTests/Generator/ProjectDirectoryHelperTests.swift new file mode 100644 index 000000000..12445b991 --- /dev/null +++ b/Tests/TuistKitTests/Generator/ProjectDirectoryHelperTests.swift @@ -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) + } +}