composer如何在Jenkins Pipeline中缓存vendor目录?(workspace复用策略)

根本原因是jenkins默认不保留vendor目录且不共享composer缓存;应采用分层缓存:全局~/.composer/cache复用+按composer.lock哈希隔离vendor目录,并将php版本和扩展列表纳入缓存key以确保环境一致性。

composer如何在Jenkins Pipeline中缓存vendor目录?(workspace复用策略)

为什么 vendor 目录在 Jenkins Pipeline 中反复重装?

因为默认 workspace 每次构建都是干净的(除非显式复用),composer install 会从头拉包、解压、生成 autoloader——这既慢又浪费带宽,还可能触发 Packagist 限流。

根本原因不是 Composer 本身,而是 Jenkins 默认不保留 vendor/,也不共享 ~/.composer/cache。缓存得拆成两层:全局 Composer 缓存(下载层) + 项目 vendor 目录(构建层)。

archiveArtifacts + unstash 复用 vendor 目录行不行?

不推荐。虽然语法上能 stash vendor/,但实际极易出错:

  • vendor/ 包含大量软链接和平台相关扩展(如 ext-redis 编译产物),换 agent 或 PHP 版本后直接报 Class not found
  • stashing 多达数万小文件,I/O 开销大,超时风险高
  • 不同分支、不同 composer.lock 版本混用 vendor/,会导致 autoload 错乱或 class 冲突

结论:只缓存 vendor/ 是伪优化,必须配合 lock 文件哈希校验或隔离存储。

推荐方案:分层缓存 + 路径隔离

核心是两件事:1)复用 Composer 全局 cache;2)按 composer.lock 的 SHA256 哈希值隔离 vendor 目录。这样既安全又快。

Jenkinsfile 中这么做:

 steps {   script {     // 计算 lock 文件指纹,作为缓存 key     def lockHash = sh(script: 'sha256sum composer.lock | cut -d" " -f1', returnStdout: true).trim()     def cacheKey = "composer-vendor-${lockHash}" <pre class='brush:php;toolbar:false;'>// 复用全局 Composer cache(挂载到 ~/.composer/cache) sh 'mkdir -p ~/.composer/cache'  // 尝试恢复 vendor 目录(key 命中才解压) sh "rm -rf vendor && mkdir -p .cache && cd .cache && if [ -f ../.cache/${cacheKey}.tar.gz ]; then tar -xzf ../.cache/${cacheKey}.tar.gz; fi"  // 执行安装(有 vendor 就跳过下载,没命中就重装) sh 'composer install --no-interaction --no-progress --optimize-autoloader'  // 缓存 vendor(仅当新生成时才保存) sh "if [ -d vendor ]; then cd .cache && tar -czf ../.cache/${cacheKey}.tar.gz ../vendor; fi"

} }

关键点:

  • 必须用 composer.lock 哈希作 key,不能用分支名或时间戳
  • ~/.composer/cache 需在 Jenkins agent 上持久化(比如挂载宿主机目录),否则每次重装包
  • --optimize-autoloader 必须加,否则即使 vendor 存在,autoload 仍可能慢
  • 别把 .cache/ 放进 archiveArtifacts,它只是中间态,Jenkins workspace 清理时自动丢弃

PHP 版本或扩展变更时,缓存怎么自动失效?

Composer 本身不感知 PHP 环境,所以得靠外部控制。最简单可靠的做法:

  • Jenkinsfile 中显式读取 php -vphp -m 输出,拼进 cache key,例如:composer-vendor-${lockHash}-${phpVersion}-${extListHash}
  • 或者更彻底:把 PHP 版本写进 composer.jsonconfig.platform.php,让 composer install 自动校验并拒绝不匹配环境
  • CI agent 上禁用 ext-* 动态加载(比如用 docker-php-ext-install 静态编译),避免运行时才暴露扩展缺失

漏掉这点,缓存看起来快了,但测试通过、线上却报 Call to undefined function redis_connect()——这种坑没法靠日志提前发现。