Skip to content

Commit

Permalink
👌 Major changes to PHP version detection
Browse files Browse the repository at this point in the history
* The information extracted from Homebrew's JSON command now also
  includes information about linked keg and installations.

* The mapped versions in the App class now contain information about
  the Homebrew installation as well.

* A HomebrewDiagnostics class has been added, which is currently able
  to detect conflicts between the `php` formulae of core and the
  `shivammathur/php` tap (which is currently an issue, see #54)

* Alerts are now displayed as critical if they are truly problematic.

* PhpInstallation was renamed to ActivePhpInstallation, to make room
  for a generic PhpInstallation object which contains cached info.

* Shell.pipe() now returns the contents of standardError if
  standardOutput was empty and there was some data in standardError.
  This makes it easier to debug the output of commands that output to
  standardError. (For example, failed brew commands might.)
  • Loading branch information
nicoverbruggen committed Nov 28, 2021
1 parent 52606aa commit 493b594
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 192 deletions.
24 changes: 18 additions & 6 deletions PHP Monitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; };
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
Expand Down Expand Up @@ -49,6 +49,10 @@
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */; };
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; };
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F7809B25D80344000DBC97 /* CommandTest.swift */; };
C4F7809F25D8037C000DBC97 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
Expand All @@ -70,7 +74,7 @@
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0CA225CC992000CC7490 /* StatsView.swift */; };
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; };
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
Expand Down Expand Up @@ -101,7 +105,7 @@
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -129,6 +133,8 @@
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewDiagnostics.swift; sourceTree = "<group>"; };
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.swift; sourceTree = "<group>"; };
C4F7807425D7F7E5000DBC97 /* RELEASE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "phpmon-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C4F7807D25D7F84B000DBC97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -173,7 +179,8 @@
54B20EDF263AA22C00D3250E /* PHP */ = {
isa = PBXGroup;
children = (
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */,
C4F2E4392752F7D00020E974 /* PhpInstallation.swift */,
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */,
C4ACA38E25C754C100060C66 /* PhpExtension.swift */,
);
path = PHP;
Expand Down Expand Up @@ -279,6 +286,7 @@
C4811D2322D70A4700B5F6B3 /* App.swift */,
C4D8016522B1584700C6DA1B /* Startup.swift */,
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */,
);
path = Core;
sourceTree = "<group>";
Expand Down Expand Up @@ -439,8 +447,10 @@
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
C42295DD2358D02000E263B2 /* Command.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
Expand All @@ -451,7 +461,7 @@
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
Expand All @@ -469,13 +479,14 @@
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
Expand All @@ -485,6 +496,7 @@
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
C4F780A225D804AA000DBC97 /* Paths.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
C4F7809625D7FBF8000DBC97 /* Shell.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions phpmon-tests/BrewJsonParserTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class BrewJsonParserTest: XCTestCase {
XCTAssertEqual(package.name, "php")
XCTAssertEqual(package.full_name, "php")
XCTAssertEqual(package.aliases.first!, "php@8.0")
XCTAssertEqual(package.installed.contains(where: { installed in
installed.version.starts(with: "8.0")
}), true)
}

}
14 changes: 3 additions & 11 deletions phpmon/Domain/Core/Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,12 @@ class Actions {
*/
public static func extractPhpLongVersions()
{
var mappedVersions: [String: String] = [:]
var mappedVersions: [String: PhpInstallation] = [:]
App.shared.availablePhpVersions.forEach { version in
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
var longVersion = version
if Shell.fileExists(phpConfigExecutablePath) {
longVersion = Command.execute(
path: phpConfigExecutablePath,
arguments: ["--version"]
)
}
mappedVersions[version] = longVersion
mappedVersions[version] = PhpInstallation(version)
}

App.shared.cachedPhpVersionNumbers = mappedVersions
App.shared.cachedPhpInstallations = mappedVersions
}

/**
Expand Down
8 changes: 4 additions & 4 deletions phpmon/Domain/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class App {
}

/** Information about the currently linked PHP installation. */
static var phpInstall: PhpInstallation? {
static var phpInstall: ActivePhpInstallation? {
return App.shared.currentInstall
}

Expand All @@ -42,7 +42,7 @@ class App {
/**
The currently active installation of PHP.
*/
var currentInstall: PhpInstallation? = nil
var currentInstall: ActivePhpInstallation? = nil

/**
All available versions of PHP.
Expand All @@ -52,7 +52,7 @@ class App {
/**
Cached information about the PHP installations; contains only the full version number at this point.
*/
var cachedPhpVersionNumbers : [String: String] = [:]
var cachedPhpInstallations : [String: PhpInstallation] = [:]

/**
The timer that will periodically fetch the PHP version that is currently active.
Expand All @@ -62,7 +62,7 @@ class App {
/**
Information we were able to discern from the Homebrew info command (as JSON).
*/
var brewPhpPackage: HomebrewPackage? = nil {
var brewPhpPackage: HomebrewPackage! = nil {
didSet {
brewPhpVersion = brewPhpPackage!.version
}
Expand Down
70 changes: 70 additions & 0 deletions phpmon/Domain/Core/HomebrewDiagnostics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// AliasConflict.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 28/11/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//

import Foundation

class HomebrewDiagnostics {

enum Errors: String {
case aliasConflict = "alias_conflict"
case installationMismatch = "installation_mismatch"
}

static let shared = HomebrewDiagnostics()
var errors: [HomebrewDiagnostics.Errors] = []

init() {
if self.determineAliasConflicts() {
self.errors.append(.aliasConflict)
}
}

/**
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
This will then result in two different aliases claiming to point to the same formula (`php`).
This will break all linking functionality in PHP Monitor, and the user needs to be informed of this.

This check only needs to be performed if the `shivammathur/php` tap is active.
*/
public func determineAliasConflicts() -> Bool
{
let tapAlias = Shell.pipe("\(Paths.brew) info shivammathur/php/php --json")

if tapAlias.contains("brew tap shivammathur/php") || tapAlias.contains("Error") {
print("The user does not appear to have tapped: shivammathur/php")
return false
} else {
print("The user DOES have the following tapped: shivammathur/php")
print("Checking for `php` formula conflicts...")

let tapPhp = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: tapAlias.data(using: .utf8)!
).first!

if tapPhp.version != App.shared.brewPhpVersion {
print("The `php` formula alias seems to be the different between the tap and core. This could be a problem!")
print("Determining whether both of these versions are installed...")

let bothInstalled = App.shared.availablePhpVersions.contains(tapPhp.version)
&& App.shared.availablePhpVersions.contains(App.shared.brewPhpVersion)

if bothInstalled {
print("Both conflicting aliases seem to be installed, warning the user!")
} else {
print("Conflicting aliases are not both installed, seems fine!")
}

return bothInstalled
}

print("All seems to be OK. No conflicts.")
return false
}
}
}
6 changes: 5 additions & 1 deletion phpmon/Domain/Core/Startup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ class Startup {

DispatchQueue.main.async { [self] in
// Present the information to the user
Alert.notify(message: messageText, info: informativeText)
Alert.notify(
message: messageText,
info: informativeText,
style: breaking ? .critical : .warning
)
// Only breaking issues will throw the extra retry modal
breaking ? failureCallback() : ()
}
Expand Down
8 changes: 5 additions & 3 deletions phpmon/Domain/Helpers/Alert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ class Alert {
messageText: String,
informativeText: String,
buttonTitle: String = "OK",
secondButtonTitle: String = ""
secondButtonTitle: String = "",
style: NSAlert.Style = .informational
) -> Bool {
let alert = NSAlert.init()
alert.alertStyle = style
alert.messageText = messageText
alert.informativeText = informativeText
alert.addButton(withTitle: buttonTitle)
Expand All @@ -25,8 +27,8 @@ class Alert {
return alert.runModal() == .alertFirstButtonReturn
}

public static func notify(message: String, info: String) {
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "")
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "", style: style)
}

}
11 changes: 10 additions & 1 deletion phpmon/Domain/Helpers/HomebrewPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@

import Foundation

struct HomebrewPackage : Decodable {
struct HomebrewPackage: Decodable {

let name: String
let full_name: String
let aliases: [String]
let installed: [HomebrewInstalled]
let linked_keg: String

public var version: String {
return aliases.first!.replacingOccurrences(of: "php@", with: "")
}

}

struct HomebrewInstalled: Decodable {
let version: String
let built_as_bottle: Bool
let installed_as_dependency: Bool
let installed_on_request: Bool
}
13 changes: 12 additions & 1 deletion phpmon/Domain/Menu/MainMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
*/
private func onEnvironmentPass() {
_ = Actions.detectPhpVersions()

if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) {
DispatchQueue.main.async {
Alert.notify(
message: "alert.php_alias_conflict.title".localized,
info: "alert.php_alias_conflict.info".localized,
style: .critical
)
}
}

updatePhpVersionInStatusBar()

let installation = App.phpInstall!
Expand Down Expand Up @@ -165,7 +176,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate {
// MARK: - User Interface

@objc func updatePhpVersionInStatusBar() {
App.shared.currentInstall = PhpInstallation()
App.shared.currentInstall = ActivePhpInstallation()
refreshIcon()
update()
}
Expand Down
2 changes: 1 addition & 1 deletion phpmon/Domain/Menu/StatusMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class StatusMenu : NSMenu {

// Get the short and long version
let shortVersion = App.shared.availablePhpVersions[index]
let longVersion = App.shared.cachedPhpVersionNumbers[shortVersion]!
let longVersion = App.shared.cachedPhpInstallations[shortVersion]!.longVersion

let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
let versionString = long ? longVersion : shortVersion
Expand Down
Loading

0 comments on commit 493b594

Please # to comment.