☰
Noms 构建与发布脚本辅助库:用 build.py / stage.py 打造可复现的构建与打包流程
2026/9/28 6:45:47 网站建设 项目流程
  • 数据库
  • 版本控制
  • 后端

【免费下载链接】noms

The versioned, forkable, syncable database

项目地址:https://gitcode.com/gh_mirrors/no/noms
点击查看免费下载

本文以仓库 tools/noms/README.md 为核心,系统讲解 Noms 项目约定的构建脚本(build.py)与发布暂存脚本(stage.py)的编写规范,并结合 tools/noms/ 目录下的 Python 辅助库源码(staging、copy、pushd、symlink)与对应单元测试,深度剖析其参数规则、目录校验与文件重命名机制。读完本文,你将能直接上手编写你自己的 Noms 应用构建脚本,并借助现成的辅助函数完成"构建产物 → 暂存目录 → 打包部署"的自动化流程。

一、这些辅助脚本解决什么问题

Noms 应用(例如 cmd/noms/splore 这样的浏览器端应用)在开发完成后,通常需要经过构建、整理产物、打包发布三个阶段。仓库在tools/noms目录下提供了一套 Python 辅助库,用来统一两个约定:

  1. 构建脚本(build script):负责把源码编译/打包成构建产物;
  2. 暂存脚本(staging script):负责把构建产物整理进一个"可被打包并部署"的目录(staging directory)。

这套约定的核心收益在于:所有项目都遵循同一个命名规范与调用方式,构建系统可以统一发现、执行和串联这些脚本,而开发者只需专注于自己项目的构建逻辑。

二、构建脚本:名为 build.py 的约定

根据 tools/noms/README.md 的约定,你的构建脚本必须命名为build.py:

  • 它会被系统自动发现,并在其所在目录中被执行;
  • 它不能要求任何命令行参数(must require no arguments);
  • 环境变量会原样传入脚本进程(environment variables will propagate in)。

也就是说,build.py应当把项目所需的编译、打包动作(例如运行 webpack、复制静态资源等)全部封装在自身逻辑中,成为一个"零参数、可重复执行"的构建入口。由于它不接收参数,任何需要动态注入的配置都应通过环境变量传递,这保证了构建过程在不同环境下的一致性。

三、暂存脚本:名为 stage.py 的约定

构建脚本执行完成后,系统会接着运行暂存脚本stage.py。它的职责是:把构建产物放入一个已经准备好被打包、部署的目录中。

stage.py的签名约定与build.py不同,它必须接收一个唯一的命令行参数——暂存目录的路径:

python stage.py path/to/staging/directory

也就是说,stage.py接收一个"顶层目录"路径,所有项目构建产物都应被暂存(stage)到该目录之下。

编写 stage.py 的推荐方式

README 建议直接使用提供的noms.staging库来编写暂存脚本,示例代码如下:

#!/usr/bin/python import noms.staging as staging if __name__ == '__main__': staging.Main('nerdosphere', staging.GlobCopier('index.html', 'styles.css', '*.js'))

这段代码做了三件事:

  1. 通过staging.Main(projectName, stagingFunction)创建该项目的暂存子目录;
  2. 使用staging.GlobCopier(...)生成一个"复制函数";
  3. 由Main把暂存目录路径传给这个复制函数,完成产物整理。

正如 README 所述:"Importing and usingnoms.staginghandles determining where you should stage your code and creating the necessary directories for you. You just pass it the name of your project and a function that knows how to stage your build artifacts, given a path under which to put everything."—— 即你只需要提供项目名和一个"知道如何把构建产物放入给定路径"的函数,目录的定位与创建都由库代劳。

四、noms.staging 库源码剖析

4.1 staging.Main:暂存目录的创建与安全校验

staging.py 中的Main(projectName, stagingFunction)是暂存脚本的入口。它的核心流程如下:

  1. 用argparse解析唯一的命令行参数staging_dir(元变量名为path/to/staging/directory,帮助文本为 "top-level dir into which project build products are staged"),并经由_dir_path归一化为绝对路径(realpath);
  2. 计算项目级暂存目录:project_staging_dir = os.path.join(args.staging_dir, projectName),即在顶层暂存目录下为每个项目创建一个以项目名命名的子目录;
  3. 安全校验:通过_is_sub_dir(project_staging_dir, args.staging_dir)确保项目暂存目录确实位于顶层暂存目录之下;若出现..跳转、符号链接指向目录外等"看似嵌套实则越界"的情况,会直接抛出Exception,防止暂存产物写到目录之外;
  4. 若目录不存在则自动创建(os.makedirs);
  5. 最后调用stagingFunction(normalized),把归一化后的暂存目录路径传给用户提供的暂存函数。

其中_is_sub_dir的实现值得注意(见 staging.py):它对两个路径做realpath归一化后,利用os.path.commonprefix判断公共前缀是否等于父目录;并且特意在父目录路径末尾追加路径分隔符(os.path.join(directory, '')),从而避免/a/b与/a/bc这类"前缀相同但并非子目录"的误判。对应的单元测试 staging_test.py 中test_Nested、test_NotNested、test_DotDotNotReallyNested、test_LinkNotReallyNested恰好覆盖了正常嵌套、不相关目录、..越界、符号链接越界四种情形。

4.2 GlobCopier:按 glob 模式复制产物

GlobCopier(*globs, **kwargs)返回一个"暂存函数",用于把符合 glob 模式的文件复制进暂存目录(staging.py)。它的行为细节如下:

  • 位置参数(globs):一个或多个 glob 模式,如'index.html'、'styles.css'、'*.js';模式是相对当前工作目录(即build.py/stage.py所在目录)解析的;
  • 关键字参数(kwargs):
    • rename (bool):若为True,则文件会被重命名为name.<sha256前20位>.ext的形式(详见下文);
    • index_file (str):若指定,该文件会被复制到暂存目录,并且其中引用其他产物文件的路径会被批量更新为重命名后的新文件名;
  • 默认排除项:exclude = ('webpack.config.js',)——webpack.config.js这类构建配置文件永远不会被复制进暂存目录;
  • 目录处理:run_globs只复制普通文件(os.path.isdir(f)时跳过),并会在目标暂存目录中按原相对路径重建子目录(os.makedirs(to_dir))。

复制过程中,run_globs对每个匹配的文件调用shutil.copy2保持内容与元数据,然后按需执行rename_with_hash。

4.3 rename_with_hash:基于内容指纹的文件重命名

rename_with_hash(f, to_dir, rename_dict)(staging.py)是实现"内容寻址"式缓存命中的关键:

  1. 读取文件内容,计算SHA-256摘要;
  2. 以摘要的前 20 个十六进制字符拼出新文件名:name.<digest[:20]>.<ext>;
  3. 把原始文件名 → 新文件名的映射记录进rename_dict;
  4. 用shutil.move在暂存目录内完成改名。

这样,只要文件内容不变,重命名后的文件名就不会变化——这对于浏览器静态资源的长期缓存非常友好(内容不变即可安全设置长缓存)。staging_test.py 的test_GlobCopierWithRename验证了完整行为:例如内容为'hi! name: a.js'的文件会被重命名为a.702f720d2b49bd41c30f.js,且嵌套子目录(x/、x/xx/、y/)中的文件同样被逐层重命名。

4.4 index_file:自动改写索引文件中的资源路径

当传入index_file且rename=True时,GlobCopier会在复制完所有资源后,读取该索引文件(例如index.html),用正则\b<old_name>\b把所有旧文件名替换为对应的新哈希文件名,再写回暂存目录(staging.py)。这意味着你无需手工维护 HTML 中引用的 JS/CSS 文件名,构建流水线会自动让它们与新文件名保持同步。

五、其他辅助模块:copy、pushd、symlink

除了staging,tools/noms目录还提供了三个小而实用的辅助模块:

5.1 copy.Peers:复制"同侪"文件

copy.py 中的Peers(me, dstDir)会扫描与me同目录下的所有文件、目录和符号链接,并把它们(保留原名)复制到dstDir,同时跳过me自身(通过os.path.samefile判断)。它对符号链接使用os.readlink+os.symlink保留链接语义,普通文件用shutil.copy2,目录用shutil.copytree,遇到未知文件类型则抛出异常。这在"复制某个脚本及其所有周边资源"的场景下非常实用。copy_test.py 的test_CopyPeers验证了文件、目录、符号链接都会被正确复制且自身被跳过。

5.2 pushd:上下文管理器式目录切换

pushd.py 提供了一个极简的上下文管理器,等价于 shell 的pushd/popd:进入目录执行代码块,退出后自动chdir回原目录,保证目录切换不会"泄漏"到调用方:

import os from contextlib import contextmanager @contextmanager def pushd(path): currentDir = os.getcwd() os.chdir(path) yield os.chdir(currentDir)

在编写依赖"当前目录"的构建步骤时,用它包裹可以安全地在多个目录间切换。

5.3 symlink.Force:强制创建符号链接

symlink.py 的Force(source, linkName)会强制让linkName成为指向source的符号链接,规则如下:

  • 若linkName不存在,直接创建符号链接;
  • 若linkName是符号链接或普通文件,先删除再重新创建(即"覆盖");
  • 若linkName是目录,则拒绝覆盖,抛出symlink.LinkError("Refusing to clobber ...")。

symlink_test.py 的test_ClobberFile、test_ClobberSymlink、test_NoClobberDir分别验证了这三种分支,尤其是"绝不删除目录"的安全底线。

六、运行单元测试

开发或修改这套辅助库后,可以用 Python 标准库的unittest发现并运行全部测试(README 给出的命令,路径按本仓库调整为tools目录):

python -m unittest discover -p "*_test.py" -s tools

该命令会递归发现tools目录下所有匹配*_test.py的文件并执行,覆盖的测试用例包括:

  • staging_test.py:目录嵌套校验、路径归一化、GlobCopier基础复制与哈希重命名、index_file路径改写;
  • copy_test.py:Peers复制文件/目录/符号链接且跳过自身;
  • symlink_test.py:Force覆盖文件与符号链接、拒绝覆盖目录。

七、一个完整的实战编排示例

综合上述模块,一个典型的 Noms 应用发布流程可以这样组织:

  1. build.py(零参数):在项目根目录执行编译/打包(如调用 webpack、npm run build等),并通过环境变量接收配置;
  2. stage.py <staging_dir>(单参数):调用staging.Main(projectName, stagingFunction),其中stagingFunction由GlobCopier('index.html', 'styles.css', '*.js', index_file='index.html', rename=True)生成——既完成资源复制与目录重建,又自动对资源做 SHA-256 哈希重命名并同步改写index.html中的引用;
  3. 若需要把某个脚本连同其周边资源整体搬运,可用copy.Peers一次性复制"同侪"文件与符号链接;
  4. 需要临时切换目录执行步骤时,用pushd包裹,确保不影响后续流程;
  5. 部署阶段如需建立资源软链接,可用symlink.Force安全地覆盖旧链接(但绝不触碰目录)。

八、结语与延伸阅读

tools/noms这套辅助库的价值在于:用极小的代码量把"构建 → 暂存 → 部署"的工程化约定固化下来——脚本命名统一、参数契约明确、目录安全校验完备、静态资源支持内容寻址式重命名。无论你的 Noms 应用是 Go 后端还是浏览器端 SPA,都可以直接复用这套模式。

相关源码与测试文件:

  • 官方约定说明:tools/noms/README.md
  • 暂存库实现(Main/GlobCopier/rename_with_hash/_is_sub_dir):tools/noms/staging.py
  • 暂存库测试:tools/noms/staging_test.py
  • 同侪复制工具(Peers):tools/noms/copy.py、tools/noms/copy_test.py
  • 目录切换上下文(pushd):tools/noms/pushd.py
  • 强制符号链接(Force):tools/noms/symlink.py、tools/noms/symlink_test.py
  • 包初始化文件:tools/noms/init.py
  • 数据库
  • 版本控制
  • 后端

【免费下载链接】noms

The versioned, forkable, syncable database

项目地址:https://gitcode.com/gh_mirrors/no/noms
点击查看免费下载

相关推荐

上一篇:智能重构:戴森球计划工厂蓝图优化完全指南
下一篇:cloudflare-docs Miniflare R2 指南:用 r2Buckets 与 getR2Bucket() 在本地仿真测试 Workers 存储

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询