mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-03-27 22:40:08 +01:00
- Removed ShellProtocol.quiet(), now that pipe() is discardable - Detecting PHP versions is also discardable - The system command is also discardable This is a nice quality of life change overall, and gets rid of a couple of silly `_ =` assignments.
210 lines
7.4 KiB
Swift
210 lines
7.4 KiB
Swift
//
|
|
// HomebrewOperationManager.swift
|
|
// PHP Monitor
|
|
//
|
|
// Created by Nico Verbruggen on 28/04/2023.
|
|
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
class ModifyPhpVersionCommand: BrewCommand {
|
|
|
|
// MARK: - Container
|
|
|
|
var container: Container
|
|
|
|
// MARK: - Variables
|
|
|
|
let title: String
|
|
let installing: [BrewPhpFormula]
|
|
let upgrading: [BrewPhpFormula]
|
|
let phpGuard: PhpGuard
|
|
|
|
// MARK: - Methods
|
|
|
|
func getCommandTitle() -> String {
|
|
return title
|
|
}
|
|
|
|
/**
|
|
You can pass in which PHP versions need to be upgraded and which ones need to be installed.
|
|
The process will be executed in two steps: first upgrades, then installations.
|
|
|
|
Upgrades come first because... well, otherwise installations may very well break.
|
|
Each version that is installed will need to be checked afterwards. Installing a
|
|
newer formula may break other PHP installations, which in turn need to be fixed.
|
|
|
|
- Important: If any PHP formula is a major upgrade that causes a PHP "version" to be
|
|
uninstalled, this is remedied by running `upgradeMainPhpFormula()`. This process
|
|
will ensure that the upgrade is applied, but the also that old version is
|
|
re-installed and linked again.
|
|
*/
|
|
public init(
|
|
_ container: Container,
|
|
title: String,
|
|
upgrading: [BrewPhpFormula],
|
|
installing: [BrewPhpFormula]
|
|
) {
|
|
self.container = container
|
|
self.title = title
|
|
self.installing = installing
|
|
self.upgrading = upgrading
|
|
self.phpGuard = PhpGuard()
|
|
}
|
|
|
|
func execute(shell: ShellProtocol, onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
|
|
let progressTitle = "phpman.steps.wait".localized
|
|
|
|
onProgress(.create(
|
|
value: 0.2,
|
|
title: progressTitle,
|
|
description: "phpman.steps.preparing".localized
|
|
))
|
|
|
|
// Determine if a formula will become unavailable
|
|
// This is the case when `php` will be bumped to a new version
|
|
let unavailable = upgrading.first(where: { formula in
|
|
formula.unavailableAfterUpgrade
|
|
})
|
|
|
|
// Make sure the tap is installed
|
|
try await self.checkPhpTap(shell: shell, 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 container.phpEnvs.determinePhpAlias()
|
|
}
|
|
|
|
// Re-check the installed versions
|
|
await container.phpEnvs.detectPhpVersions()
|
|
|
|
// After performing operations, attempt to run repairs if needed
|
|
try await self.repairBrokenPackages(onProgress)
|
|
|
|
// Finally, complete all operations
|
|
await self.completedOperations(onProgress)
|
|
}
|
|
|
|
private func upgradeMainPhpFormula(
|
|
_ unavailable: BrewPhpFormula,
|
|
_ 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_DOWNLOAD_CONCURRENCY=auto; \
|
|
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
|
|
\(container.paths.brew) upgrade php;
|
|
\(container.paths.brew) install php@\(short);
|
|
"""
|
|
|
|
// Run the upgrade command
|
|
try await run(shell: container.shell, command, onProgress)
|
|
}
|
|
|
|
private func upgradePackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
|
|
// If no upgrades are needed, early exit
|
|
if self.upgrading.isEmpty {
|
|
return
|
|
}
|
|
|
|
let command = """
|
|
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
|
|
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
|
|
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
|
|
\(container.paths.brew) upgrade \(self.upgrading.map { $0.name }.joined(separator: " "))
|
|
"""
|
|
|
|
try await run(shell: container.shell, command, onProgress)
|
|
}
|
|
|
|
private func installPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
|
|
// If no installations are needed, early exit
|
|
if self.installing.isEmpty {
|
|
return
|
|
}
|
|
|
|
let command = """
|
|
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
|
|
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
|
|
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
|
|
\(container.paths.brew) install \(self.installing.map { $0.name }.joined(separator: " ")) --force
|
|
"""
|
|
|
|
try await run(shell: container.shell, command, onProgress)
|
|
}
|
|
|
|
private func repairBrokenPackages(_ onProgress: @escaping (BrewCommandProgress) -> Void) async throws {
|
|
// Determine which PHP installations are considered unhealthy
|
|
// Build a list of formulae to reinstall
|
|
let requiringRepair = container.phpEnvs
|
|
.cachedPhpInstallations.values
|
|
.filter({ !$0.isHealthy })
|
|
.map { installation in
|
|
let formula = "php@\(installation.versionNumber.short)"
|
|
|
|
if installation.versionNumber.short == PhpEnvironments.brewPhpAlias {
|
|
return "php"
|
|
}
|
|
|
|
return formula
|
|
}
|
|
|
|
// If no repairs are needed, early exit
|
|
if requiringRepair.isEmpty {
|
|
return
|
|
}
|
|
|
|
// If the health comes back as negative, attempt to reinstall
|
|
let command = """
|
|
export HOMEBREW_DOWNLOAD_CONCURRENCY=auto; \
|
|
export HOMEBREW_NO_INSTALL_UPGRADE=true; \
|
|
export HOMEBREW_NO_INSTALL_CLEANUP=true; \
|
|
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true; \
|
|
\(container.paths.brew) reinstall \(requiringRepair.joined(separator: " ")) --force
|
|
"""
|
|
|
|
try await run(shell: container.shell, command, onProgress)
|
|
}
|
|
|
|
private func completedOperations(_ onProgress: @escaping (BrewCommandProgress) -> Void) async {
|
|
// Reload and restart PHP versions
|
|
onProgress(.create(value: 0.95, title: self.title, description: "phpman.steps.reloading".localized))
|
|
|
|
// Ensure all symlinks are correctly linked
|
|
await BrewDiagnostics.shared.checkForOutdatedPhpInstallationSymlinks()
|
|
|
|
// Check which version of PHP are now installed
|
|
await container.phpEnvs.detectPhpVersions()
|
|
|
|
// Keep track of the currently installed version
|
|
await MainMenu.shared.refreshActiveInstallation()
|
|
|
|
// If a PHP version was active prior to running the operations, attempt to restore it
|
|
if let version = phpGuard.currentVersion {
|
|
await MainMenu.shared.switchToPhpVersionAndWait(version, silently: true)
|
|
}
|
|
|
|
// Also rebuild the content of the main menu
|
|
await MainMenu.shared.rebuild()
|
|
|
|
// Let the UI know that the installation has been completed
|
|
onProgress(.create(
|
|
value: 1,
|
|
title: "phpman.steps.completed".localized,
|
|
description: "phpman.steps.success".localized
|
|
))
|
|
}
|
|
}
|