Ansible hacking 开发工具指南:用 env-setup 搭建源码树开发环境,用 test-module.py 与 return_skeleton_generator.py 本地调试模块
本文以 hacking/README.md 为主体,讲解 Ansible 仓库中 hacking/ 目录下的三件开发者核心工具:用 env-setup 脚本在不安装的情况下直接从 git 检出运行源码树、用 test-module.py 在本地脱离完整执行器单独运行某个模块(含断点调试)、以及用 return_skeleton_generator.py 从模块 JSON 输出自动生成 RETURNS 文档骨架。读完本文,你可以独立搭建 Ansible 源码级开发环境,并对任意模块完成"本地单步调试 → 收集返回数据 → 生成文档"的完整开发闭环。
一、hacking/ 目录的定位
Ansible 的 hacking/ 目录是一组面向模块开发者与测试者的辅助脚本集合,核心目标是让你绕过完整的 Ansible 执行器(executor),直接在当前机器上运行、调试单个模块。hacking/README.md 官方说明了三个主要工具:
env-setup:修改环境变量,使你能用受支持的 Python 版本直接从 git 检出(checkout)运行 ansible;test-module.py:让模块开发者(或测试者)在本地、脱离 ansible 程序本身运行一个模块;return_skeleton_generator.py:根据模块的 JSON 输出(文件参数或 stdin 传入)生成模块 RETURNS 文档段落的骨架。
二、env-setup:从 git 检出直接运行 Ansible
hacking/env-setup 是一个 POSIX shell 脚本(#!/bin/sh),必须用 source 方式加载,这样它修改的环境变量才会作用于当前 shell。README 给出的标准用法是:
source ./hacking/env-setup
2.1 前提依赖
脚本本身不管理依赖,只假设你本机已具备可用 Python(hacking/env-setup 中通过 PYTHON=$(command -v python3 || command -v python) 探测 python3,找不到再退回 python)。如果你希望依赖来自 pip 而非系统包管理器,可按 hacking/README.md 的说明安装:
python -Im ensurepip # if pip is not already available
pip install -r requirements.txt
其中 requirements.txt 声明的运行期依赖为:jinja2 >= 3.1.0(3.1.0 起修复 Jinja2 native macro 支持)、PyYAML >= 5.1、cryptography、packaging、resolvelib >= 0.8.0, < 2.0.0(ansible-galaxy 的依赖解析器)。文件头部注释特别说明:这份 requirements 是"最宽松"的运行依赖集,仅列必需包而非经过测试的固定版本集。
2.2 脚本实际做了什么
从 hacking/env-setup 源码逐段看,它做了四件事:
- 定位仓库根目录并导出
ANSIBLE_DEV_HOME(L35-L48):脚本用$BASH_SOURCE/$0/$KSH_VERSION多路分支确定HACKING_DIR(注释说明source时$0会变成 bash,所以必须优先取BASH_SOURCE),再用 Python 的os.path.realpath解析符号链接后取父目录,export ANSIBLE_DEV_HOME。 - 前插路径变量(L50-L58):通过幂等的
prepend_path函数,把以下前缀加入对应环境变量(若已存在则跳过,因此可以安全地重复 source):PYTHONPATH←$ANSIBLE_DEV_HOME/lib(核心库lib/ansible)与$ANSIBLE_DEV_HOME/test/lib(ansible_test库);PATH←$ANSIBLE_DEV_HOME/bin(ansible-playbook、ansible-test等可执行入口);MANPATH←$ANSIBLE_DEV_HOME/docs/man。
- 清理
.pyc缓存(L65-L73):在ANSIBLE_DEV_HOME下执行find . -type f -name "*.pyc" -exec rm -f {} \;,避免源码修改后命中旧字节码导致"改动不生效"的困惑。 - 打印生效结果(L75-L88):输出最终的
PATH/PYTHONPATH/MANPATH,并提示可用-i指定 inventory 文件。
脚本支持 -q 参数静默运行(source ./hacking/env-setup -q),此时 verbosity=silent:跳过 .pyc 清理的输出与末尾的状态打印。
仓库还附带 Fish shell 版本 hacking/env-setup.fish,逻辑等价:探测 PYTHON_BIN、用 status -f 取脚本目录、前插 PYTHONPATH/PATH/MANPATH、清理 .pyc,支持 --quiet。Fish 用户使用 . ./hacking/env-setup 加载。
补充一点定位关系:context/dev-environment.md 指出,开发环境有三条路径可选——可编辑安装 pip install -e .、source hacking/env-setup,或完全不安装地直接 bin/ansible-test sanity -v --docker default 跑测试。env-setup 属于其中的"免安装"路线,适合不想污染虚拟环境、或只需要临时从源码树跑 CLI 的场景。
三、test-module.py:脱离 Ansible 执行器本地运行模块
hacking/test-module.py(约 312 行)是模块开发者的主力调试工具。README 给出的最小示例:
./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hi"
它的价值在于:模块被打包成 AnsiballZ 单文件、在远端以特定方式启动、参数以 JSON 参数文件传入——这些流程在开发阶段都会增加调试成本。test-module.py 在本机完整复刻了这套流程,因此你可以直接对模块插入断点(README 原话:"This is a good way to insert a breakpoint into a module, for instance"),也可以在脚本中 print 观察中间状态而不必走 ansible-playbook。
3.1 完整命令行选项
从 hacking/test-module.py 的 parse()(L56-L88)整理出全部选项:
| 选项 | 含义 | 默认值 |
|---|---|---|
-m, --module-path |
必填,待执行模块源码的完整路径 | 无(缺失时打印帮助并退出) |
-a, --args |
模块参数字符串(支持 kv、JSON/YAML 文档、@文件 三种形式) |
"" |
-D, --debugger |
Python 调试器路径(如 /usr/bin/pdb),指定后进入交互调试而非直接执行 |
不调试 |
-I, --interpreter |
指定解释器,格式 TYPE=PATH(如 ansible_python_interpreter=/usr/bin/python);TYPE 不以 ansible_ 开头或不以 _interpreter 结尾时会自动补全 |
ansible_python_interpreter=<当前 sys.executable> |
-c, --check |
以 check mode 运行模块(向参数注入 _ansible_check_mode=True) |
关闭 |
-n, --noexecute |
只生成模块产物、不执行 | 默认执行(store_false) |
-o, --output |
生成模块文件的落盘位置 | ~/.ansible_module_generated |
脚本头部注释(L25-L29)给出的官方示例也值得记住:
./hacking/test-module.py -m lib/ansible/modules/command.py -a "/bin/sleep 3"
./hacking/test-module.py -m lib/ansible/modules/command.py -a "/bin/sleep 3" --debugger /usr/bin/pdb
./hacking/test-module.py -m lib/ansible/modules/lineinfile.py -a "dest=/etc/exports line='/srv/home hostname1(rw,sync)'" --check
./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hello" -n -o "test_hello"
3.2 复合参数的两种高级写法
README 特别演示了复杂参数的用法。对于如下 YAML 参数:
parent:
child:
- item: first
val: foo
- item: second
val: boo
可以直接把等价的 JSON 作为 -a 参数传入:
./hacking/test-module.py -m module \
-a '{"parent": {"child": [{"item": "first", "val": "foo"}, {"item": "second", "val": "bar"}]}}'
结合源码(hacking/test-module.py boilerplate_module(),L132-L163)可以看到脚本实际支持三种参数形态,优先级依次判断:
-a以@开头:视为 YAML/JSON 文件路径(注释说明"JSON 是 YAML 的子集"),用DataLoader.load_from_file读入并与内建参数合并,字符串参数清空;-a以{开头:视为内联 YAML 文档(loader.load(args)解析);- 其余情况:按 kv 语法用 lib/ansible/parsing/splitter.py 的
parse_kv解析。
无论哪种形态,脚本都会先注入一组与真实执行一致的"内建复杂参数"(boilerplate_module,L144-L150):
_ansible_selinux_special_fs←C.DEFAULT_SELINUX_SPECIAL_FS_ansible_tmpdir←C.DEFAULT_LOCAL_TMP_ansible_keep_remote_files←C.DEFAULT_KEEP_REMOTE_FILES_ansible_version← 当前__version__- 若
-c:追加_ansible_check_mode=True
这意味着 test-module.py 下模块拿到的内建变量与生产执行路径保持一致,调试结论可以迁移到真实 playbook。
3.3 内部流程:复刻 AnsiballZ 打包 → 解压 → 执行
理解 test-module.py 的关键在于它忠实模拟了 lib/ansible/executor/module_common.py 中模块执行的全链路:
- 打包:
main()(L285-L290)先init_plugin_loader(),再调用module_common.modify_module(...)(定义见 lib/ansible/executor/module_common.py#L1545)把模块源码、所需module_utils、参数与Templar组合成最终的模块字节串,产物写入-o指定的文件(默认~/.ansible_module_generated),并判定module_style(new/ansiballz/old等);若检测到_ANSIBALLZ_WRAPPER = True则标记为ansiballz风格(L184-L185)。 - 解压(explode):对 ansiballz 风格,
ansiballz_setup()(L198-L234)以指定解释器执行<module> explode,从输出的第二行解析出临时目录,再在ansible/modules、ansible_collections/*/*/plugins/modules、ansible/legacy三类目录中定位被解压出的模块源码与args参数文件——这正是远端执行的原始形态,因此对 AnsiballZ 模块的调试所见即所得。 - 执行/调试:
runtest()(L237-L270)以子进程运行模块,捕获 stdout/stderr,先打印RAW OUTPUT(原始 JSON),再json.loads并打印PARSED OUTPUT(4 空格缩进、键排序的美化 JSON);rundebug()(L273-L282)则改为subprocess.call("<debugger> <modfile> <argsfile>")进入 pdb 等调试器。对old/non_native_want_json/binary风格,write_argsfile()(L107-L114)会把参数写入~/.ansible_test_module_arguments供旧式模块读取。 - 清理:
main的finally(L308-L312)会shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)删除本地临时目录,不留垃圾。
一个实用组合是:-n -o test_hello 只打包不执行,检查生成物;-D /usr/bin/pdb 直接进断点;-c 验证 check mode 分支。
四、return_skeleton_generator.py:从 JSON 输出生成 RETURNS 文档骨架
hacking/return_skeleton_generator.py(约 97 行)解决的是模块文档中 RETURNS 段落的编写成本问题:它的输入是模块的 JSON 输出(文件参数或 stdin),输出是一段可直接粘贴进模块文档字符串的 YAML。文件头部注释明确提示:获取 JSON 输出的最简单方式就是用 hacking/test-module.py;生成结果"很可能需要你去掉敏感数据、确认 returned 取值并补写有意义的 description"——每个 key 的 description 初始为 FIXME *** add description for <key>,returned 默认填 always。
4.1 用法
# 通过文件参数
./hacking/test-module.py -m lib/ansible/modules/command.py -a "echo hi" | \
./hacking/return_skeleton_generator.py
# 或先落盘再喂给生成器
./hacking/return_skeleton_generator.py module_output.json
4.2 类型推断规则
从源码看(get_return_data(),L51-L69),生成器的推断规则是:
- 值为
dict,或"元素全为 dict 的非空 list" →type: complex,递归展开为contains(对 list 只取首个元素value[0]做推断); - 其他标量 →
type取type(value).__name__,并附带sample样例值;Python 的unicode类型会被改写为文档约定的str; main()(L81-L93)会主动删除invocation键(它是每次调用都出现的元数据,不属于模块返回语义);输出用yaml.safe_dump(default_flow_style=False)展开成块状 YAML,并通过自定义represent_ordereddict保持字段顺序(summary 类字段在前,contains在后)。
这与第三节形成完整工作流:test-module.py 的 PARSED OUTPUT 就是标准的模块 JSON 返回,可以直接管道进生成器,得到带 description/returned/type/contains 结构的骨架,再人工补全描述即可。
五、hacking/ 目录的其余工具(辅助了解)
除 README 三件套外,hacking/ 目录还包含几个低频但实用的工具,与本文主题同属开发辅助生态:
- hacking/ansible-profile.py(38 行):用
cProfile给任意 Ansible CLI 做性能剖析,用法形如./hacking/ansible-profile.py playbook <args>——它通过把子程序名映射到ansible.cli.<target>模块并取<Target>CLI类动态实例化(L10-L12, L30-L38),因此对ansible-playbook、ansible-galaxy等均可用; - hacking/report.py:测试报告相关辅助脚本(约 224 行);
- hacking/backport/:上游补丁反向回合到维护分支的辅助工具(
backport_of_line_adder.py等); - hacking/tests/gen_distribution_version_testcase.py:生成分布板版本测试用例;
- hacking/ticket_stubs/:issue 回复模板集合,偏社区流程而非技术调试。
六、小结:一条模块开发调试主线
把三个工具串起来,就是 Ansible 模块开发的标准本地回路:
source ./hacking/env-setup让 shell 直接从源码树加载lib/、test/lib/与bin/;./hacking/test-module.py -m lib/ansible/modules/<module>.py -a "..." -D /usr/bin/pdb单步调试模块本体,参数内建变量与生产执行保持一致(见 hacking/test-module.pyboilerplate_module()的实现);- 用
test-module.py打印的 JSON 返回喂给./hacking/return_skeleton_generator.py,快速生成 RETURNS 骨架,人工补全描述后写入模块文档。
适用前提与限制:以上流程面向 POSIX 开发机(Windows 下建议 WSL,见 context/dev-environment.md);env-setup 依赖 checkout 自带的 lib/、test/lib/、bin/ 布局,不适用于已安装到 site-packages 的场景;test-module.py 走的是本机子进程,无法覆盖需要特定远端环境(如 Windows PowerShell)的模块行为,这类场景应回到 ansible-test 集成测试体系验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00