🐧 发行版检测与 PHP
识别 Linux 发行版、匹配包管理器、按需安装 PHP、按版本安装与卸载 Composer——installer 包里所有与「Linux 环境」和「PHP 运行时」相关的底层能力都在这里。
包路径:github.com/scagogogo/composer-skills/pkg/installer
DistroInfo
Linux 发行版信息。
type DistroInfo struct {
ID string // 发行版标识:ubuntu / centos / arch / alpine ...
Name string // 人类可读名:Ubuntu / CentOS ...
Version string // 版本号:22.04 / 9 ...
PackageManager string // 包管理器:apt / yum / dnf / pacman / apk / zypper / emerge
}DetectLinuxDistro
检测当前 Linux 发行版。
func DetectLinuxDistro() (*DistroInfo, error)非 Linux 系统返回 not running on Linux 错误。检测按以下优先级逐级回退:
| 顺序 | 检测方式 | 说明 |
|---|---|---|
| 1️⃣ | parseOSRelease() | 读 /etc/os-release(现代标准),解析 ID/NAME/VERSION_ID |
| 2️⃣ | detectViaLSBRelease() | 执行 lsb_release -a,解析 Distributor ID/Release |
| 3️⃣ | detectViaKnownFiles() | 检查发行版专属文件(/etc/redhat-release、/etc/debian_version、/etc/arch-release、/etc/alpine-release、/etc/SuSE-release、/etc/gentoo-release) |
| 4️⃣ | detectViaAvailablePkgManager() | 探测 apt-get/dnf/yum/pacman/apk/zypper/emerge 哪个可用,返回 *-like 标识 |
最后一级回退返回 &DistroInfo{ID: "unknown", Name: "Unknown Linux", PackageManager: "unknown"},不会返回 nil。
发行版 → 包管理器映射
determinePkgManager(distroID) 把发行版 ID 映射到包管理器:
| 发行版 ID | 包管理器 |
|---|---|
ubuntu/debian/linuxmint/pop/elementary/kubuntu/xubuntu/linuxlite | apt |
fedora | dnf |
centos/rhel/redhat/oracle/amzn/almalinux/rocky | dnf(若可用)否则 yum |
arch/manjaro/endeavouros/garuda | pacman |
alpine | apk |
opensuse/opensuse-leap/opensuse-tumbleweed/sles | zypper |
gentoo | emerge |
void | xbps |
solus | eopkg |
| 其他 | unknown |
InstallComposerViaPackageManager
用系统原生包管理器安装 Composer。
func InstallComposerViaPackageManager(distro *DistroInfo, useSudo bool) (bool, error)| 参数 | 类型 | 说明 |
|---|---|---|
distro | *DistroInfo | 发行版信息,nil 或 PackageManager=="unknown" 时返回 (false, nil) |
useSudo | bool | 是否在包管理器命令前加 sudo |
返回 (attempted bool, err error):
attempted=true表示找到了匹配的包管理器并执行了命令(无论成功失败)。attempted=false表示没有匹配的包管理器(不报错,调用方应回退其他方式)。
各包管理器执行的命令:
| 包管理器 | 命令 |
|---|---|
apt | apt-get install -y composer |
dnf | dnf install -y composer |
yum | yum install -y composer |
pacman | pacman -S --noconfirm composer |
apk | apk add composer |
zypper | zypper -n in composer |
HasPHP
检查系统是否可用 PHP。
func HasPHP() bool先 exec.LookPath("php"),失败再检查常见路径 /usr/bin/php、/usr/local/bin/php、/opt/homebrew/bin/php(macOS Homebrew),任一存在即返回 true。
GetPHPVersion
获取 PHP 版本字符串。
func GetPHPVersion() (string, error)执行 php -r "echo PHP_VERSION;",返回去除首尾空白后的版本号。失败返回 failed to get PHP version 包装错误。
InstallPHP
通过系统包管理器安装 PHP 及常用扩展。
func InstallPHP(distro *DistroInfo, useSudo bool) errordistro 为 nil 或 PackageManager=="unknown" 时返回 cannot install PHP: unknown package manager。各包管理器安装的包:
| 包管理器 | 安装的包 |
|---|---|
apt | php php-cli php-mbstring php-xml php-curl |
dnf | php php-cli php-mbstring php-xml php-curl |
yum | php php-cli php-mbstring php-xml php-curl |
pacman | php |
apk | php81 php81-cli php81-mbstring php81-xml php81-curl |
zypper | php7 php7-cli |
apk 装 php81
Alpine 的 PHP 包名带版本号(如 php81),InstallPHP 硬编码为 php81。若 Alpine 源里默认是其他版本,可能需要手动调整。
CheckComposerVersion
执行 composer --version 并返回输出。
func CheckComposerVersion(composerPath string) (string, error)返回的是 --version 的原始输出(如 Composer version 2.7.1 ...),不做解析。失败返回 failed to check composer version 包装错误。
InstallComposerVersion
安装指定版本的 Composer(直接 curl 拉 phar + 生成包装脚本)。
func InstallComposerVersion(version string, installPath string, useSudo bool) error| 参数 | 类型 | 说明 |
|---|---|---|
version | string | 版本别名或具体版本号 |
installPath | string | 安装目录,phar 落到 <installPath>/composer.phar |
useSudo | bool | 写文件与 chmod 是否用 sudo |
version 到下载 URL 的映射:
| version | 下载 URL |
|---|---|
1 / 1.x | https://getcomposer.org/composer-1.phar |
2 / 2.x / latest / "" | https://getcomposer.org/composer.phar |
preview | https://getcomposer.org/composer-preview.phar |
stable | https://getcomposer.org/composer.phar |
其他(如 2.5.1) | https://getcomposer.org/download/<version>/composer.phar |
流程:
curl -fsSL -o <installPath>/composer.phar <url>下载 phar。createComposerWrapper生成可执行包装:Windows 上写composer.bat(@php "<phar>" %*);其他系统写composer(#!/bin/sh\nphp "<phar>" "$@")并chmod +x(useSudo时用sudo chmod)。
UninstallComposer
删除 Composer 可执行文件及同目录下的 composer.phar。
func UninstallComposer(composerPath string, useSudo bool) error会删除两个文件:
composerPath本身。composerPath去掉末尾/composer后拼接的/composer.phar(即同目录下的 phar)。
useSudo=true 用 sudo rm -f,否则 os.Remove。文件不存在时跳过不报错。
快速示例
检测发行版并打印
package main
import (
"fmt"
"log"
"github.com/scagogogo/composer-skills/pkg/installer"
)
func main() {
info, err := installer.DetectLinuxDistro()
if err != nil {
log.Fatalf("非 Linux 或检测失败: %v", err)
}
fmt.Printf("发行版: %s %s (%s)\n包管理器: %s\n",
info.Name, info.Version, info.ID, info.PackageManager)
}缺 PHP 时自动装
if !installer.HasPHP() {
distro, _ := installer.DetectLinuxDistro()
if err := installer.InstallPHP(distro, true); err != nil {
log.Fatal(err)
}
}
fmt.Println("PHP 版本:", must(installer.GetPHPVersion()))安装指定版本 Composer
// 装 Composer 2.5.1 到 /usr/local/bin,使用 sudo
err := installer.InstallComposerVersion("2.5.1", "/usr/local/bin", true)
if err != nil {
log.Fatal(err)
}通过包管理器装 Composer
distro, _ := installer.DetectLinuxDistro()
attempted, err := installer.InstallComposerViaPackageManager(distro, true)
if !attempted {
log.Println("没有可用的包管理器,回退直接下载")
} else if err != nil {
log.Printf("包管理器安装失败: %v\n", err)
}卸载
if err := installer.UninstallComposer("/usr/local/bin/composer", true); err != nil {
log.Fatal(err)
}进阶
- 为什么 DetectLinuxDistro 不返回 nil:最坏情况下返回
unknown,让调用方可以用PackageManager == "unknown"判断「无法识别」,避免 nil 解引用。InstallComposerViaPackageManager与InstallPHP都兼容这种 unknown 情况。 - PHP 版本获取的可靠性:
GetPHPVersion依赖php在 PATH 中;HasPHP()返回 true 但GetPHPVersion失败的极端情况(如 php 损坏),调用方应处理错误。 - CheckComposerVersion 不解析:它返回原始
--version输出字符串;如需结构化版本号,应在调用方自行解析,或用pkg/composer的GetVersion/GetVersionInfo。 - InstallComposerVersion 的 curl 依赖:此函数用
curl下载,需要系统装有 curl。GetSystemInfo会检测curl_available用于诊断。 - 与 SmartInstaller 的关系:
SmartInstaller.doInstall在 Linux 上会先调InstallComposerViaPackageManager,失败再回退installer.Install()(内部含InstallComposerVersion路径),形成「包管理器 → Homebrew → 直接下载」三级回退。