From 141c06d14bd1e51162a4e4224751a384a37b918b Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 24 Nov 2023 18:35:36 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Update=20PHP=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift b/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift index c645ead..39d5b22 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift @@ -17,6 +17,7 @@ extension HandlesBrewFormulae { public func refreshPhpVersions(loadOutdated: Bool) async { let items = await loadPhpVersions(loadOutdated: loadOutdated) Task { @MainActor in + await PhpEnvironments.shared.determinePhpAlias() Brew.shared.formulae.phpVersions = items } } From 7159ca8612dbda50cd55cacc76e37e6207c3fa5a Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 24 Nov 2023 20:23:12 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20Correctly=20handle=20mismatc?= =?UTF-8?q?hes=20when=20upgrading=20PHP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PHP/PHP Version/PhpEnvironments.swift | 36 +++++++++++++++++-- phpmon/Common/PHP/PhpInstallation.swift | 6 ++++ .../Integrations/Homebrew/BrewFormula.swift | 10 ++++-- .../Homebrew/BrewFormulaeHandler.swift | 7 ++-- .../UI/PhpVersionManagerView.swift | 8 +++-- phpmon/de.lproj/Localizable.strings | 1 + phpmon/en.lproj/Localizable.strings | 1 + phpmon/fr.lproj/Localizable.strings | 1 + phpmon/nl.lproj/Localizable.strings | 1 + phpmon/pt-PT.lproj/Localizable.strings | 1 + phpmon/vi.lproj/Localizable.strings | 1 + 11 files changed, 64 insertions(+), 9 deletions(-) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift index f33562a..5ac9069 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift @@ -37,7 +37,27 @@ class PhpEnvironments { from: brewPhpAlias.data(using: .utf8)! ).first! - Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version)!") + Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version).") + + // Check if that version actually corresponds to an older version + let phpConfigExecutablePath = "\(Paths.optPath)/php/bin/php-config" + if FileSystem.fileExists(phpConfigExecutablePath) { + let longVersionString = Command.execute( + path: phpConfigExecutablePath, + arguments: ["--version"], + trimNewlines: false + ).trimmingCharacters(in: .whitespacesAndNewlines) + + if let version = try? VersionNumber.parse(longVersionString) { + PhpEnvironments.brewPhpAlias = version.short + if version.short != homebrewPackage.version { + Log.info("[BREW] An older version of `php` is actually installed (\(version.short)).") + } + } else { + Log.warn("Could not determine the actual version of the php binary; assuming Homebrew is correct.") + PhpEnvironments.brewPhpAlias = homebrewPackage.version + } + } } // MARK: - Properties @@ -77,7 +97,12 @@ class PhpEnvironments { As such, we take that information from Homebrew. */ - static var brewPhpAlias: String { + static var brewPhpAlias: String = "" + + /** + It's possible for the alias to be newer than the actual installed version of PHP. + */ + static var homebrewBrewPhpAlias: String { if PhpEnvironments.shared.homebrewPackage == nil { return "8.2" } return PhpEnvironments.shared.homebrewPackage.version @@ -144,7 +169,12 @@ class PhpEnvironments { // Avoid inserting a duplicate if !supportedVersions.contains(phpAlias) && FileSystem.fileExists("\(Paths.optPath)/php/bin/php") { - supportedVersions.insert(phpAlias) + let phpAliasInstall = PhpInstallation(phpAlias) + // Before inserting, ensure that the actual output matches the alias + // if that isn't the case, our formula remains out-of-date + if !phpAliasInstall.missingBinary { + supportedVersions.insert(phpAlias) + } } availablePhpVersions = Array(supportedVersions) diff --git a/phpmon/Common/PHP/PhpInstallation.swift b/phpmon/Common/PHP/PhpInstallation.swift index 5658747..4c9c5de 100644 --- a/phpmon/Common/PHP/PhpInstallation.swift +++ b/phpmon/Common/PHP/PhpInstallation.swift @@ -12,6 +12,8 @@ class PhpInstallation { var versionNumber: VersionNumber + var missingBinary: Bool = false + var isHealthy: Bool = true /** @@ -35,6 +37,10 @@ class PhpInstallation { // The parser should always work, or the string has to be very unusual. // If so, the app SHOULD crash, so that the users report what's up. self.versionNumber = try! VersionNumber.parse(longVersionString) + } else { + // Keep track that the `php-config` binary is missing; this often means there's a mismatch between + // the `php` version alias and the actual installed version (e.g. you haven't upgraded `php`) + missingBinary = true } if FileSystem.fileExists(phpExecutablePath) { diff --git a/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift b/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift index 3291212..c663717 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift @@ -8,7 +8,7 @@ import Foundation -struct BrewFormula { +struct BrewFormula: Equatable { /// Name of the formula. let name: String @@ -48,6 +48,12 @@ struct BrewFormula { return upgradeVersion != nil } + /// Whether this formula alias is different. + var hasUpgradedFormulaAlias: Bool { + return self.shortVersion == PhpEnvironments.homebrewBrewPhpAlias + && PhpEnvironments.homebrewBrewPhpAlias != PhpEnvironments.brewPhpAlias + } + /// The associated Homebrew folder with this PHP formula. var homebrewFolder: String { let resolved = name @@ -60,7 +66,7 @@ struct BrewFormula { /// The short version associated with this formula, if installed. var shortVersion: String? { guard let version = self.installedVersion else { - return nil + return self.displayName.replacingOccurrences(of: "PHP ", with: "") } return VersionNumber.make(from: version)?.short ?? nil diff --git a/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift b/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift index 39d5b22..8ded2e7 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewFormulaeHandler.swift @@ -44,7 +44,8 @@ class BrewFormulaeHandler: HandlesBrewFormulae { } return Brew.phpVersionFormulae.map { (version, formula) in - let fullVersion = PhpEnvironments.shared.cachedPhpInstallations[version]?.versionNumber.text + let fullVersion = PhpEnvironments.shared.cachedPhpInstallations[version]? + .versionNumber.text var upgradeVersion: String? @@ -54,13 +55,15 @@ class BrewFormulaeHandler: HandlesBrewFormulae { })?.current_version } - return BrewFormula( + let formula = BrewFormula( name: formula, displayName: "PHP \(version)", installedVersion: fullVersion, upgradeVersion: upgradeVersion, prerelease: Constants.ExperimentalPhpVersions.contains(version) ) + + return formula }.sorted { $0.displayName > $1.displayName } } } diff --git a/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView.swift b/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView.swift index ebe4a0a..00e9491 100644 --- a/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView.swift +++ b/phpmon/Modules/PHP Version Manager/UI/PhpVersionManagerView.swift @@ -157,7 +157,11 @@ struct PhpVersionManagerView: View { } } - if formula.isInstalled && formula.hasUpgrade { + if formula.hasUpgradedFormulaAlias { + Text("phpman.version.automatic_upgrade".localized(formula.shortVersion!)) + .font(.system(size: 11)) + .foregroundColor(.gray) + } else if formula.isInstalled && formula.hasUpgrade { Text("phpman.version.has_update".localized( formula.installedVersion!, formula.upgradeVersion! @@ -195,7 +199,7 @@ struct PhpVersionManagerView: View { } else { Button("phpman.buttons.install".localizedForSwiftUI) { Task { await self.install(formula) } - } + }.disabled(formula.hasUpgradedFormulaAlias) } } .listRowBackground(index % 2 == 0 ? Color.gray.opacity(0): Color.gray.opacity(0.08)) diff --git a/phpmon/de.lproj/Localizable.strings b/phpmon/de.lproj/Localizable.strings index de087a7..245815b 100644 --- a/phpmon/de.lproj/Localizable.strings +++ b/phpmon/de.lproj/Localizable.strings @@ -90,6 +90,7 @@ "phpman.version.has_update" = "Version %@ installiert, %@ verfügbar."; "phpman.version.installed" = "Version %@ ist derzeit installiert."; "phpman.version.available_for_installation" = "Diese Version kann installiert werden."; +"phpman.version.automatic_upgrade" = "Diese Version wird automatisch installiert, indem eine ältere Version aktualisiert wird."; "phpman.buttons.uninstall" = "Deinstallieren"; "phpman.buttons.install" = "Installieren"; "phpman.buttons.update" = "Aktualisieren"; diff --git a/phpmon/en.lproj/Localizable.strings b/phpmon/en.lproj/Localizable.strings index bd5c8dd..126dde4 100644 --- a/phpmon/en.lproj/Localizable.strings +++ b/phpmon/en.lproj/Localizable.strings @@ -107,6 +107,7 @@ "phpman.version.has_update" = "Version %@ installed, %@ available."; "phpman.version.installed" = "Version %@ is currently installed."; "phpman.version.available_for_installation" = "This version can be installed."; +"phpman.version.automatic_upgrade" = "This version will be automatically installed by upgrading an older version."; "phpman.buttons.uninstall" = "Uninstall"; "phpman.buttons.install" = "Install"; "phpman.buttons.update" = "Update"; diff --git a/phpmon/fr.lproj/Localizable.strings b/phpmon/fr.lproj/Localizable.strings index 5e8fb13..ca0f010 100644 --- a/phpmon/fr.lproj/Localizable.strings +++ b/phpmon/fr.lproj/Localizable.strings @@ -107,6 +107,7 @@ "phpman.version.has_update" = "Version %@ installée, %@ disponible."; "phpman.version.installed" = "La version %@ est actuellement installée."; "phpman.version.available_for_installation" = "Cette version peut être installée."; +"phpman.version.automatic_upgrade" = "Cette version sera installée automatiquement en mettant à jour une version plus ancienne."; "phpman.buttons.uninstall" = "Désinstaller"; "phpman.buttons.install" = "Installer"; "phpman.buttons.update" = "Mettre à jour"; diff --git a/phpmon/nl.lproj/Localizable.strings b/phpmon/nl.lproj/Localizable.strings index c118a6e..23f1db4 100644 --- a/phpmon/nl.lproj/Localizable.strings +++ b/phpmon/nl.lproj/Localizable.strings @@ -91,6 +91,7 @@ "phpman.version.has_update" = "Versie %@ geïnstalleerd, %@ beschikbaar."; "phpman.version.installed" = "Versie %@ is momenteel geïnstalleerd."; "phpman.version.available_for_installation" = "Deze versie kan worden geïnstalleerd."; +"phpman.version.automatic_upgrade" = "Deze versie zal automatisch geïnstalleerd worden door een upgrade."; "phpman.buttons.uninstall" = "Verwijderen"; "phpman.buttons.install" = "Installeren"; "phpman.buttons.update" = "Bijwerken"; diff --git a/phpmon/pt-PT.lproj/Localizable.strings b/phpmon/pt-PT.lproj/Localizable.strings index 71cccf5..c3a73ce 100644 --- a/phpmon/pt-PT.lproj/Localizable.strings +++ b/phpmon/pt-PT.lproj/Localizable.strings @@ -90,6 +90,7 @@ "phpman.version.has_update" = "Versão %@ instalada, %@ disponível."; "phpman.version.installed" = "Versão %@ atualmente instalada."; "phpman.version.available_for_installation" = "Esta versão pode ser instalada."; +"phpman.version.automatic_upgrade" = "Esta versão será instalada automaticamente ao atualizar uma versão mais antiga."; "phpman.buttons.uninstall" = "Desinstalar"; "phpman.buttons.install" = "Instalar"; "phpman.buttons.update" = "Atualizar"; diff --git a/phpmon/vi.lproj/Localizable.strings b/phpmon/vi.lproj/Localizable.strings index e97c28e..2f54cfe 100644 --- a/phpmon/vi.lproj/Localizable.strings +++ b/phpmon/vi.lproj/Localizable.strings @@ -90,6 +90,7 @@ "phpman.version.has_update" = "Phiên bản %@ được cài đặt, Phiên bản %@ có sẵn."; "phpman.version.installed" = "Phiên bản %@ hiện đã được cài đặt."; "phpman.version.available_for_installation" = "Phiên bản này có thể được cài đặt."; +"phpman.version.automatic_upgrade" = "Phiên bản này sẽ được cài đặt tự động bằng cách nâng cấp từ một phiên bản cũ hơn."; "phpman.buttons.uninstall" = "Gỡ cài đặt"; "phpman.buttons.install" = "Cài đặt"; "phpman.buttons.update" = "Cập nhật"; From d3053b8fe32ef5c66e9048f5aaa6ae324738791c Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 24 Nov 2023 22:26:33 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20Allow=20for=20seamless=20upgrad?= =?UTF-8?q?e=20to=20PHP=208.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The older version (8.2 in most cases) that becomes obsolete will also be reinstalled and the app will attempt to switch to the last active version as well. It is likely that PHP Monitor will have to repair your older PHP installations after upgrading the `php` formula, but this too should be a seamless process. --- .../PHP/PHP Version/PhpEnvironments.swift | 2 +- .../Integrations/Homebrew/BrewFormula.swift | 13 +++++++ .../Commands/InstallAndUpgradeCommand.swift | 37 +++++++++++++++++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift index 5ac9069..ff3a299 100644 --- a/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift +++ b/phpmon/Common/PHP/PHP Version/PhpEnvironments.swift @@ -29,7 +29,7 @@ class PhpEnvironments { /** Determine which PHP version the `php` formula is aliased to. */ - func determinePhpAlias() async { + @MainActor func determinePhpAlias() async { let brewPhpAlias = await Shell.pipe("\(Paths.brew) info php --json").out self.homebrewPackage = try! JSONDecoder().decode( diff --git a/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift b/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift index c663717..535f4e8 100644 --- a/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift +++ b/phpmon/Domain/Integrations/Homebrew/BrewFormula.swift @@ -54,6 +54,19 @@ struct BrewFormula: Equatable { && PhpEnvironments.homebrewBrewPhpAlias != PhpEnvironments.brewPhpAlias } + var unavailableAfterUpgrade: Bool { + if (installedVersion == nil || upgradeVersion == nil) { + return false + } + + if let installed = try? VersionNumber.parse(self.installedVersion!), + let upgrade = try? VersionNumber.parse(self.upgradeVersion!) { + return upgrade.short != installed.short + } + + return false + } + /// The associated Homebrew folder with this PHP formula. var homebrewFolder: String { let resolved = name diff --git a/phpmon/Domain/Integrations/Homebrew/Commands/InstallAndUpgradeCommand.swift b/phpmon/Domain/Integrations/Homebrew/Commands/InstallAndUpgradeCommand.swift index e9b3ad8..8e604b1 100644 --- a/phpmon/Domain/Integrations/Homebrew/Commands/InstallAndUpgradeCommand.swift +++ b/phpmon/Domain/Integrations/Homebrew/Commands/InstallAndUpgradeCommand.swift @@ -41,12 +41,22 @@ class InstallAndUpgradeCommand: BrewCommand { description: "PHP Monitor is preparing Homebrew..." )) + let unavailable = upgrading.first(where: { formula in + formula.unavailableAfterUpgrade + }) + // Make sure the tap is installed try await self.checkPhpTap(onProgress) - // Try to run all upgrade and installation operations - try await self.upgradePackages(onProgress) - try await self.installPackages(onProgress) + if unavailable == nil { + // Try to run all upgrade and installation operations + try await self.upgradePackages(onProgress) + try await self.installPackages(onProgress) + } else { + // Simply upgrade `php` to the latest version + try await self.upgradeMainPhpFormula(unavailable!, onProgress) + await PhpEnvironments.shared.determinePhpAlias() + } // Re-check the installed versions await PhpEnvironments.detectPhpVersions() @@ -58,6 +68,27 @@ class InstallAndUpgradeCommand: BrewCommand { await self.completedOperations(onProgress) } + private func upgradeMainPhpFormula( + _ unavailable: BrewFormula, + _ onProgress: @escaping (BrewCommandProgress) -> Void + ) async throws { + // Determine which version was previously available (that will become unavailable) + guard let short = try? VersionNumber + .parse(unavailable.installedVersion!).short else { + return + } + + // Upgrade the main formula + let command = """ + export HOMEBREW_NO_INSTALL_CLEANUP=true; \ + \(Paths.brew) upgrade php; + \(Paths.brew) install php@\(short); + """ + + // Run the upgrade command + try await run(command, onProgress) + } + private func checkPhpTap(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws { if !BrewDiagnostics.installedTaps.contains("shivammathur/php") { let command = "brew tap shivammathur/php"