# makedep **Repository Path**: jia2024/makedep ## Basic Information - **Project Name**: makedep - **Description**: Build files based on dependencies. - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-18 - **Last Updated**: 2024-08-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # makedep makedep 是一个类似 GNU/make 那样的,基于依赖和命令的构建程序。但它专注于任务并发,而将搭建构建关系的复杂性交给用户自己维护。 makedep 还在开发早期,第一个版本还没有释放。examples 文件夹下有些示例可供参考。 ## 安装 cargo install --path . dep --version ## 使用 `Makedep` 脚本 与使用配置文件进行构建的构建系统不同,dep 要求用户自定义的脚本完成配置,dep 要求当前工作目录中有 `Makedep` 脚本,dep 会执行这个脚本,获知如何构建。 `Makedep` 需要向标准输出写入一个 JSON 对象。包含要构建的文件,和对如何构建它们的描述。它看起来可能是这样: ```json { "node": { "build": { "cmd": ["mkdir", "-p", "build"] }, "build/hello": { "deps": ["build", "hello.c"], "cmd": ["cc", "-o", "build/hello", "hello.c"] }, ":default": { "deps": ["build/hello"] } } } ``` node 指向一个对象,它包含 3 个键值对,每一对称作是一个【节点】,节点包含三部份含义: - 相对于 Makedep 所在文件夹内的文件路径。 - 它依赖的节点。 - 如何构建一个节点。 因此上面的示例表示有 `build`、`build/hello` 和 `:default` 三个节点。而且 `:default` 依赖 `build/hello`,`build/hello` 依赖 `build`。 构建 `build` 的方法是调用 mkdir 命令,而构建 `build/hello` 的方法是调用 C 的编译器 cc。 `:default` 节点则比较特殊,任何以冒号开头的节点,都不是文件,而只是一个【虚拟节点】,用来构建更复杂的依赖关系。从模型上,它可以看作是一个永远不存在的文件。相对的,其他不已冒号开头的文件,称作普通节点。 dep 会构建每个【过期】的节点。如果满足下面任何一个条件,一个节点就是过期的: - 这个节点的名字以 `:` 开头(是虚拟节点)。 - 这个节点对应的文件不存在。 - 这个节点的任何一个依赖过期。 - 这个节点的任何一个依赖文件比当前文件新。注意,因为虚拟节点永远是过期的,所以这里不存在比较虚拟节点和普通节点的文件新旧的问题。 dep 会按照依赖顺序构建节点,一个节点的依赖总是先构建。dep 构建依赖的方法就是调用 Makedep 定义的命令。任何一个构建命令失败,dep 都会报告错误退出。 为了书写和阅读方便,在文档里我们会省略 JSON 中的括号和引号,在没有歧义的情况下写成下面的形式: node: build: cmd: mkdir -p build build/hello deps: build hello.c cmd: cc -o build/hello hello.c :default: deps: build/hello dep (可能)会并发地构建文件,并发程度可以用 `-j,--jobs N` 选项配置。默认值是当前的 CPU 核心数。 ## 多个命令 我们只给每个节点填入一个命令的位置,如果需要多个命令来完成一个节点的构建,首先可以将命令代理给一个脚本,让脚本封装多个命令为一个命令。但是更直接的方法是使用虚拟节点将每个命令展开。 例如: node: foo: deps: x y z cmd: cmd1 && cmd2 # invalid 可以做成: node: foo: deps: :foo[2] :foo[2]: deps: :foo[1] cmd: cmd2 :foo[1]: deps: :foo[0] cmd: cmd1 :foo[0] deps: x y z ## 条件构建 make 获知类似的程序会传入一个叫【目标】的参数。让 make 可以每次运行构建不同的内容。例如 `make` 本身基本上会完成大部分程序的构建,而 `make clean` 会删除这些内容。 dep 采取模拟特性方案来完成这种功能。dep 会传入一列字符串参数(可以空),表示当前构建要打开的开关。这些开关会原样发送给 Makedep 的命令行参数,而 Makedep 自己要根据不同的特性来产生不同的节点表。 例如 `dep clean` 实际上是打开了 clean 这个开关,Makedep 可以处理它: ```python import sys match sys.argv[1:]: case []: print_build_nodes() case ["clean"]: print_clean_nodes() _: report_error() ``` ## 示例 $ cd examples/hello $ dep makedep INFO $ mkdir build makedep INFO $ cc -c -Wall -Wextra -Werror -fPIC -o build/hello.o hello.c makedep INFO $ cc -c -Wall -Wextra -Werror -fPIC -o build/hello_test.o hello_test.c makedep INFO $ cc -shared -o build/libhello.so build/hello.o makedep INFO $ cc -o build/hello_test build/hello_test.o build/libhello.so makedep INFO All 5 jobs done $ ./build/hello_test $ dep clean makedep INFO $ rm -r build makedep INFO All 1 jobs done ## 多阶段构建 在 C 的构建模型里,我们不能直接得到一个 C 文件的依赖的头文件列表,而需要用 `cc -MM SOURCE -MF DEP.d` 来生成一个 `.d` 文件,然后解析它才能得到正确的依赖关系。 而如果要求 `Makedep` 脚本来完成 `.d` 文件的生成,那所有生成任务都存在排队问题和依赖问题。即 cc 程序们应该用一定的并发度并发,而不能串行,以提高构建速度。另外,C 文件不过期的时候也不必重新生成 .d 文件。这些任务 dep 都完成的很好,而要求 Makedep 再制作一次这些逻辑的结构是不好的。 因此我们引入了多阶段构建,让 Makedep 除了发出构建节点之外,还发出(可选的)列表,告诉 Makedep 下一步用哪些 feature 构建: next: [[build]] nodes: // Other nodes to build build/dep/*.d. 我们已经知道,Makedep 的参数只有 features,它是一个字符串数组。因此一个字符串数组可以用来描述一次 Makedep 的调用。 dep 会构建 nodes 这些节点,然后再做一次 `./Makedep build`,这样在第二次 Makedep 执行的时候,`.d` 文件已经生成好了,Makedep 就可以解析并且生成正确的依赖树了。 ## 生成代码 我们可以任意增加构建阶段的数目,因此生成代码也并不是复杂的构建任务,只需要再新增一个阶段完成这个工作就可以。 下面给出了一个示例:`dep` 命令返回了 3 个阶段。 # [] next: [[generate], [makedep], [build]] # 生成 version.h # [] -> [generate] nodes: version.h: cmd: redirect --stdout=version.h -- git describe --tags # 生成 hello.d。 # [] -> [makedep] nodes: hello.d: cmd: cc -MM -MF hello.d hello.c deps: hello.c # 生成 hello.o。 # [] -> [build] nodes: hello.o: cmd: cc -c -o hello.o hello.c deps: hello.d hello.c hello.h version.h # 依赖是解析 hello.d 得到的 hello.d: cmd: cc -MM -MF hello.d hello.c deps: hello.c 当然这个例子里我们可以吧 generate 和 makedep 合成一个阶段,但是代价就是 hello.d 要依赖 config.h。不然的话,hello.d 可能先生成,此时因为 version.h 不存在,hello.d 里包含的可能是某个其他路径里的 version.h。 ## TODO - [ ] 如何让一个项目跟其他项目交互。 - [ ] 缓存 Makedep 的输出。