Skip to content

💻 平台安装器

按操作系统把「装 Composer」这件事分发到不同的平台实现——Linux 优先包管理器、macOS 优先 Homebrew、Windows 直接下 phar、其他类 Unix 走通用脚本。

包路径:github.com/scagogogo/composer-skills/pkg/installer

PlatformInstaller 接口

所有平台安装器实现同一接口,只有一个 Install() 方法。

go
type PlatformInstaller interface {
    Install() error
}

GetPlatformInstaller

根据 runtime.GOOS 返回对应的平台安装器。

go
func GetPlatformInstaller(config Config) (PlatformInstaller, error)
GOOS返回类型说明
windows*WindowsInstaller直接下载 phar + 生成 .bat
darwin*MacOSInstaller优先 Homebrew,失败回退直接下载
linux*LinuxInstaller优先包管理器,失败回退直接下载
freebsd/openbsd/netbsd/dragonfly*UnixInstaller通用 Unix 脚本安装
其他nil返回 ErrUnsupportedPlatform 包装错误

Installer.Install() 内部正是先做 PHP 检查与 Linux 包管理器尝试,再调 GetPlatformInstaller 拿到平台实现执行。

各平台安装器

🐧 LinuxInstaller

go
type LinuxInstaller struct {
    config Config
}

func NewLinuxInstaller(config Config) *LinuxInstaller
func (i *LinuxInstaller) Install() error

Install 策略

  1. PreferPackageManager=trueDetectLinuxDistro()InstallComposerViaPackageManager(apt/dnf/yum/pacman/apk/zypper),成功并通过 exec.LookPath("composer") 验证即返回。
  2. 否则或包管理器失败:进入 installDirect()
    • 检查 InstallPath 可写性,不可写且 UseSudo=false 返回 ErrInsufficientRights
    • TargetVersion 非空且非 latest:调 InstallComposerVersion 拉指定版本 phar。
    • 否则:下载 composer-setup.php 到临时目录 → php composer-setup.php --install-dir=<InstallPath> --filename=composer.phar → 生成 composer shell 包装脚本并 chmod 755

Linux 的两层包管理器尝试

Installer.Install() 在调 GetPlatformInstaller 之前已经试过一次包管理器,LinuxInstaller.Install() 内部又会试一次。两次尝试都失败才会走直接下载,保证「能用包管理器就用包管理器」的优先级最大化。

🍎 MacOSInstaller

go
type MacOSInstaller struct {
    config Config
}

func NewMacOSInstaller(config Config) *MacOSInstaller
func (i *MacOSInstaller) Install() error

Install 策略

  1. PreferBrewOnMac=true:检查 brew 是否在 PATH,是则执行 brew install composer,成功即返回。
  2. 否则或 brew 失败:检查 InstallPath 可写 → 下载 composer-setup.phpphp composer-setup.php 生成 composer.phar → 生成 composer shell 包装脚本。

macOS 路径下不会用 UseSudo 写包装脚本(直接 CreateFileWithContent),因此 InstallPath 必须当前用户可写——Homebrew 安装时通常落到 /opt/homebrew/bin/usr/local/bin(brew 用户组可写)。

🪟 WindowsInstaller

go
type WindowsInstaller struct {
    config Config
}

func NewWindowsInstaller(config Config) *WindowsInstaller
func (i *WindowsInstaller) Install() error

Install 策略

  1. EnsureDirectoryExists(InstallPath) 确保目录存在。
  2. 下载 composer-setup.php 到临时目录。
  3. php composer-setup.php --install-dir=<InstallPath> --filename=composer.phar 生成 phar。
  4. 生成 composer.bat,内容 @php "<phar>" %*
  5. 打印提示:需手动把 InstallPath 加到 PATH 环境变量。

Windows 不自动改 PATH

出于权限与安全考虑,Windows 安装器只创建文件并提示用户手动加 PATH,不直接修改系统环境变量。安装后在新终端里 composer 才能被找到。

🐧 UnixInstaller(通用)

go
type UnixInstaller struct {
    config Config
}

func NewUnixInstaller(config Config) *UnixInstaller
func (i *UnixInstaller) Install() error

Install 策略(适用于 FreeBSD/OpenBSD/NetBSD/DragonFly 等):

  1. 检查 InstallPath 可写性,不可写且 UseSudo=false 返回 ErrInsufficientRights
  2. 下载 composer-setup.php 到临时目录。
  3. php composer-setup.php --install-dir=<InstallPath> --filename=composer.pharUseSudo=true 时前缀 sudo)。
  4. 生成 composer shell 包装脚本:UseSudo=trueecho ... | sudo tee 写入并 sudo chmod 755;否则直接 CreateFileWithContent

通用 Unix 安装器不尝试任何包管理器,因为这些 BSD 系发行版的包管理器各异且 Composer 通常不在官方源里。

各平台策略对比

维度🐧 Linux🍎 macOS🪟 Windows🐧 通用 Unix
第一选择包管理器(apt/dnf/...)Homebrew (brew install composer)直接下载 phar直接下载 phar
回退直接下载 setup.php直接下载 setup.php
产物composer.phar + composer(sh)composer.phar + composer(sh)composer.phar + composer.batcomposer.phar + composer(sh)
sudo 支持UseSudo❌ 不读 UseSudo❌ 无 sudo 概念UseSudo
自动加 PATH写入 /usr/local/bin 等已在 PATH 的目录同 Linux❌ 需手动加同 Linux
版本指定TargetVersionInstallComposerVersion走 setup.php(不支持指定版本)走 setup.php走 setup.php

快速示例

直接拿平台安装器

go
package main

import (
    "log"

    "github.com/scagogogo/composer-skills/pkg/installer"
)

func main() {
    pi, err := installer.GetPlatformInstaller(installer.SmartConfig())
    if err != nil {
        log.Fatalf("不支持的平台: %v", err)
    }
    if err := pi.Install(); err != nil {
        log.Fatalf("安装失败: %v", err)
    }
}

显式用 LinuxInstaller 并禁用包管理器

go
package main

import (
    "log"

    "github.com/scagogogo/composer-skills/pkg/installer"
)

func main() {
    cfg := installer.DefaultConfig()
    cfg.PreferPackageManager = false // 强制走直接下载
    cfg.UseSudo = true

    li := installer.NewLinuxInstaller(cfg)
    if err := li.Install(); err != nil {
        log.Fatalf("安装失败: %v", err)
    }
}

macOS 强制不用 Homebrew

go
cfg := installer.DefaultConfig()
cfg.PreferBrewOnMac = false
mi := installer.NewMacOSInstaller(cfg)
err := mi.Install()

进阶

  • 为什么用接口PlatformInstaller 接口让 Installer 不关心具体平台,便于测试时注入 mock 实现。
  • 自定义平台:实现 PlatformInstaller 接口后无法直接注入 Installer(它内部硬编码调 GetPlatformInstaller),但可以直接用自己的实现替代 Installer.Install() 的最后一步。
  • 直接下载的两条路径composer-setup.php(官方安装器,需 PHP 执行)与 InstallComposerVersion(直接 curl 拉 phar + 生成包装脚本,用于指定版本)。Linux 在 TargetVersion 非空时走后者,其他平台固定走前者。
  • 临时文件清理:所有平台都会 defer os.Remove(scriptPath) 清理下载的 composer-setup.php

基于 MIT 许可证发布