编译器自举场景下,如何在Makefile中声明不同Git提交的依赖?
问题描述
我正在自动化编译器从版本n到n+1的自举流程。这个编译器部分用自身编译的语言编写,所以它既是自身构建的依赖,修改源代码还会同时改变语言语法(进而影响源代码自身语法)和生成代码的语义——这两者在版本n升级到n+1时都可能变化。
我采用的自举方案是:
- 基于已有的版本n编译器二进制文件(版本0为手动生成)
- 编写版本n.5编译器:用版本n的语法/语义,但能解析版本n+1的语法,并遵循n+1的语义
- 用版本n编译器编译n.5代码,得到n.5编译器
- 编写版本n+1编译器:采用自身定义的语法与语义
- 用n.5编译器编译n+1代码,得到最终的n+1编译器
为了避免n.5版本的隐性bug影响后续版本,我从版本n的Git提交创建两个独立分支:一个用于n.5,一个用于n+1,各自迭代后再合并得到n+1版本。
现在的问题是:如何让Make识别部分依赖来自当前提交之外的其他分支?具体来说,版本n+1的编译器依赖n.5的编译器,但n.5的源代码在另一个Git分支,这种跨分支依赖是否可行?
解决方案
完全可以实现跨Git分支的依赖管理,核心思路是把其他分支的源代码显式导出到本地独立目录,再让Make将其作为构建依赖。以下是具体实现方式:
1. 导出目标分支的源代码到独立目录
用git archive命令把n.5分支(或指定提交)的代码导出到专门的bootstrap目录,避免和当前分支代码冲突。比如:
git archive refs/heads/n.5 | tar -x -C ./bootstrap/n.5/
在Makefile中可以把这个导出动作定义为一个目标,确保只有当n.5分支的提交哈希变化时,才重新导出代码。
2. 在Makefile中声明跨分支依赖链
显式定义各个构建目标的依赖关系:n.5编译器依赖导出的n.5源代码和n版本编译器;n+1编译器依赖n.5编译器和当前分支的n+1源代码。
示例Makefile片段:
# 配置参数 N5_BRANCH := refs/heads/n.5 # 获取n.5分支的最新提交哈希 N5_COMMIT := $(shell git rev-parse $(N5_BRANCH)) # 存储n.5源代码的目录 N5_SOURCE_DIR := ./bootstrap/n.5 # 记录n.5提交哈希的文件,用于判断是否需要重新导出 N5_HASH_FILE := $(N5_SOURCE_DIR)/commit-hash # n.5编译器的输出路径 N5_COMPILER := $(N5_SOURCE_DIR)/bin/compiler-n.5 # n+1编译器的输出路径 N1_COMPILER := ./bin/compiler-n+1 # 预先存在的n版本编译器 N_COMPILER := ./bin/compiler-n # 导出n.5分支源代码的目标 $(N5_SOURCE_DIR): $(N5_HASH_FILE) git archive $(N5_COMMIT) | tar -x -C $(dir $@) echo $(N5_COMMIT) > $(N5_HASH_FILE) # 生成哈希记录文件 $(N5_HASH_FILE): @mkdir -p $(dir $@) @echo $(N5_COMMIT) > $@ # 构建n.5编译器 $(N5_COMPILER): $(N5_SOURCE_DIR) $(N_COMPILER) cd $(N5_SOURCE_DIR) && $(N_COMPILER) build --output $@ # 构建n+1编译器 $(N1_COMPILER): $(N5_COMPILER) $(wildcard src/**/*) $(N5_COMPILER) build src --output $@ # 清理bootstrap目录的目标 .PHONY: clean-bootstrap clean-bootstrap: rm -rf ./bootstrap
3. 用提交哈希确保依赖一致性
通过记录n.5分支的提交哈希,Make可以自动检测分支是否有更新:如果当前n.5分支的哈希和本地记录的不一致,就会重新导出源代码并重新构建n.5编译器,保证依赖的版本正确性。
4. 额外注意事项
- 确保n版本编译器已经预先构建完成,或者在Makefile中添加从n版本源代码构建的步骤(如果需要)
- 建议把bootstrap目录加入
.gitignore,避免把导出的代码提交到仓库 - 可以将n.5编译器的二进制文件缓存起来(比如放到项目的缓存目录或Git LFS),减少重复构建的时间
内容的提问来源于stack exchange,提问作者jthulhu
相关产品推荐
相关产品推荐

