Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brew cask #1601

Merged
merged 10 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
### Added

- Add an acceptance test suite to cover a `test cache warm` command on a micro-feature architecture kind of application that is fully statically linked [#1594](https://github.com/tuist/tuist/pull/1594) by [@RomainBoulay](https://github.com/RomainBoulay)
- Add Homebrew cask up [#1601](https://github.com/tuist/tuist/pull/1601) by [@leszko11](https://github.com/leszko11)

## 11.14.0 - Spezi

Expand Down
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"repositoryURL": "https://github.com/rnine/Checksum.git",
"state": {
"branch": null,
"revision": "9dde3d1d898a5074608a1420791ef0a80c2399f2",
"revision": "cd1ae53384dd578a84a0afef492a4f5d6202b068",
"version": "1.0.2"
}
},
Expand Down
8 changes: 8 additions & 0 deletions Sources/ProjectDescription/Up/Up.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public class Up: Codable, Equatable {
UpHomebrewTap(repositories: repositories)
}

/// Returns an up that configures Homebrew cask projects.
///
/// - Parameter projects: Projects to be installed with cask.
/// - Returns: Up to cask projects in Homebrew.
public static func homebrewCask(projects: [String]) -> Up {
UpHomebrewCask(projects: projects)
}

/// Returns a user-defined up.
///
/// - Parameters:
Expand Down
37 changes: 37 additions & 0 deletions Sources/ProjectDescription/Up/UpHomebrewCask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation

/// Up that configures a Homebrew tap in the system.
class UpHomebrewCask: Up {
/// Projects to be installed with cask.
let projects: [String]

/// Initializes the up Homebrew Cask with its attributes.
///
/// - Parameter projects: Project to be installed with Homebrew Cask.
init(projects: [String]) {
self.projects = projects
super.init()
}

public enum CodingKeys: String, CodingKey {
case projects
case type
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projects = try container.decode([String].self, forKey: .projects)
try super.init(from: decoder)
}

override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(projects, forKey: .projects)
try container.encode("homebrew-cask", forKey: .type)
}

override func equals(_ other: Up) -> Bool {
guard let otherUpHomebrewTap = other as? UpHomebrewCask else { return false }
return projects == otherUpHomebrewTap.projects
}
}
2 changes: 2 additions & 0 deletions Sources/TuistLoader/Up/Up.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class Up: Upping {
return try UpHomebrew(dictionary: dictionary, projectPath: projectPath)
} else if type == "homebrew-tap" {
return try UpHomebrewTap(dictionary: dictionary, projectPath: projectPath)
} else if type == "homebrew-cask" {
return try UpHomebrewCask(dictionary: dictionary, projectPath: projectPath)
} else if type == "carthage" {
return try UpCarthage(dictionary: dictionary, projectPath: projectPath)
} else if type == "mint" {
Expand Down
89 changes: 89 additions & 0 deletions Sources/TuistLoader/Up/UpHomebrewCask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Foundation
import TSCBasic
import TuistSupport

/// Command that installs Homebrew and packages.
class UpHomebrewCask: Up, GraphInitiatable {
/// Up name.
static let name: String = "Homebrew cask"

/// Cask projects
let projects: [String]

/// Up homebrew for installing Carthge.
let upHomebrew: Upping

/// Initializes the Homebrew cask up.
///
/// - Parameters:
/// - projects: List of projects to be installed with cask.
/// - upHomebrew: Up homebrew for installing Homebrew.
init(projects: [String],
upHomebrew: Upping = UpHomebrew(packages: [])) {
self.projects = projects
self.upHomebrew = upHomebrew
super.init(name: UpHomebrewCask.name)
}

/// Default constructor of entities that are part of the manifest.
///
/// - Parameters:
/// - dictionary: Dictionary with the object representation.
/// - projectPath: Absolute path to the folder that contains the manifest.
/// This is useful to obtain absolute paths from the relative paths provided in the manifest by the user.
/// - Throws: A decoding error if an expected property is missing or has an invalid value.
required convenience init(dictionary: JSON, projectPath _: AbsolutePath) throws {
self.init(projects: try dictionary.get("projects"))
}

/// Returns true when the command doesn't need to be run.
///
/// - Parameters
/// - projectPath: Path to the directory that contains the project manifest.
/// - Returns: True if the command doesn't need to be run.
/// - Throws: An error if the check fails.
override func isMet(projectPath: AbsolutePath) throws -> Bool {
guard try upHomebrew.isMet(projectPath: projectPath) else { return false }
let casks = try self.casks()
guard projects.first(where: { !isCaskConfigured($0, casks: casks) }) == nil else { return false }
return true
}

/// When the command is not met, this method runs it.
///
/// - Parameters:
/// - projectPath: Path to the directory that contains the project manifest.
/// - Throws: An error if any error is thrown while running it.
override func meet(projectPath: AbsolutePath) throws {
// Homebrew
try upHomebrew.meet(projectPath: projectPath)

// Casks
let casks = try self.casks()
let notConfigured = projects.filter { !isCaskConfigured($0, casks: casks) }
for project in notConfigured {
logger.notice("Adding project cask: \(project)")
try System.shared.run(["brew", "cask", "install", project])
}
}

// MARK: - Fileprivate

/// Returns true if a cask repository is configured in the system.
///
/// - Parameters:
/// - project: cask project.
/// - casks: The list of system casks.
/// - Returns: True if the cask repository is configured in the system.
private func isCaskConfigured(_ project: String, casks: [String]) -> Bool {
casks.first(where: { $0.contains(project) }) != nil
}

/// Returns the list of casks that are available in the system.
///
/// - Returns: The list of casks available in the system.
/// - Throws: An error if the 'brew cask list' command errors.
private func casks() throws -> [String] {
try System.shared.capture(["brew", "cask", "list"]).spm_chomp().split(separator: "\n").map(String.init)
}
}
4 changes: 2 additions & 2 deletions Sources/TuistLoader/Up/UpHomebrewTap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class UpHomebrewTap: Up, GraphInitiatable {

/// Initializes the Homebrew tap up.
///
/// - Parameter
/// - repositories: List of repositories to be tapped.
/// - Parameters:
/// - repositories: List of repositories to be tapped.
/// - upHomebrew: Up homebrew for installing Homebrew.
init(repositories: [String],
upHomebrew: Upping = UpHomebrew(packages: [])) {
Expand Down
81 changes: 81 additions & 0 deletions Tests/TuistLoaderTests/Up/UpHomebrewCaskTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Foundation
import TSCBasic
import TuistSupport
import XCTest

@testable import TuistLoader
@testable import TuistSupportTesting

final class UpHomebrewCaskTests: TuistUnitTestCase {
var upHomebrew: MockUp!

override func setUp() {
super.setUp()
upHomebrew = MockUp()
}

override func tearDown() {
upHomebrew = nil
super.tearDown()
}

func test_isMet_when_homebrewIsNotMet() throws {
// Given
let temporaryPath = try self.temporaryPath()
let subject = UpHomebrewCask(projects: [], upHomebrew: upHomebrew)
upHomebrew.isMetStub = { _ in false }

// When
let got = try subject.isMet(projectPath: temporaryPath)

// Then
XCTAssertFalse(got)
}

func test_isMet_when_casksAreMissing() throws {
// Given
let temporaryPath = try self.temporaryPath()
let subject = UpHomebrewCask(projects: ["app"], upHomebrew: upHomebrew)
upHomebrew.isMetStub = { _ in true }
system.succeedCommand(["brew", "cask", "list"], output: "")

// When
let got = try subject.isMet(projectPath: temporaryPath)

// Then
XCTAssertFalse(got)
}

func test_isMet_when_allCasksAreConfigured() throws {
// Given
let temporaryPath = try self.temporaryPath()
let subject = UpHomebrewCask(projects: ["app"], upHomebrew: upHomebrew)
upHomebrew.isMetStub = { _ in true }
system.succeedCommand(["brew", "cask", "list"], output: "app\nother\n")

// When
let got = try subject.isMet(projectPath: temporaryPath)

// Then
XCTAssertTrue(got)
}

func test_meet() throws {
// When
let temporaryPath = try self.temporaryPath()
let subject = UpHomebrewCask(projects: ["app"], upHomebrew: upHomebrew)
system.succeedCommand(["brew", "cask", "list"], output: "")
system.succeedCommand(["brew", "cask", "install", "app"])
var homebrewUpped = false
upHomebrew.meetStub = { _ in
homebrewUpped = true
}

// When
try subject.meet(projectPath: temporaryPath)

// Then
XCTAssertTrue(homebrewUpped)
XCTAssertPrinterOutputContains("Adding project cask: app")
}
}
11 changes: 11 additions & 0 deletions Tests/TuistLoaderTests/Up/UpTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ final class UpTests: TuistUnitTestCase {
XCTAssertEqual(got?.repositories, ["repository"])
}

func test_with_when_homebrewCask() throws {
let temporaryPath = try self.temporaryPath()
let dictionary = JSON([
"type": "homebrew-cask",
"projects": JSON.array([JSON.string("project")]),
])
let got = try Up.with(dictionary: dictionary, projectPath: temporaryPath) as? UpHomebrewCask
XCTAssertEqual(got?.name, "Homebrew cask")
XCTAssertEqual(got?.projects, ["project"])
}

func test_with_when_carthage() throws {
let temporaryPath = try self.temporaryPath()
let dictionary = JSON([
Expand Down
8 changes: 8 additions & 0 deletions website/markdown/docs/commands/up.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ It installs the given [Homebrew](https://brew.sh) packages if they don’t exist

Configures Homebrew tap repositories. It also installs Homebrew if it's not available in the system.

### Homebrew cask

```swift
.homebrewCask(projects: ["periphery"])
```

Installs Homebrew cask projects. It also installs Homebrew if it's not available in the system.

### Carthage dependencies

```swift
Expand Down