当前位置: 首页 > news >正文

python MANIFEST.in

## Python MANIFEST.in 其实是打包时的一个隐性门槛

很多人刚开始接触 Python 打包时,setup.py 写得挺顺,但忽略了 MANIFEST.in 这个文件。等到项目安装到别人机器上,突然发现少了个数据文件,或者某个配置压根没带进去,才意识到问题。这种事我见的不少,原因其实很直白——太多人以为有了 setuptools 的include_package_data=True就万事大吉了。

这个文件到底是个什么

MANIFEST.in 本质上是一个清单文件,放在项目根目录下。它不是一个 Python 脚本,就是一个纯文本文件,每行一条指令,指示 setuptools 或 distutils 在打源码分发包(sdist)时应该包含哪些文件。

注意“源码分发包”这个词,很容易被忽略。MANIFEST.in 控制的只是python setup.py sdist生成的 .tar.gz 或者 .zip 包里有什么。这跟python setup.py bdist_wheel生成的 wheel 包是两套规则,虽然 wheel 内部也有自己的包含机制。

它能做哪些事,不能做哪些事

MANIFEST.in 最大的价值在于管理那些“被自动排除”的文件。Python 打包工具默认只包含 .py 文件,外加 README、setup.py、setup.cfg 之类的少数文件。但实际项目中可能有:

  • 配置文件:YAML、TOML、JSON 格式的,比如config/default.yaml
  • 静态资源:Web 框架里的 HTML、CSS、JavaScript 文件
  • 文档里的图片:一张docs/diagram.png
  • C 扩展所需的头文件:src/ext/some_header.h
  • 许可证文件或者版权声明

这些文件不会被自动打包。如果没有 MANIFEST.in,这些文件就会在pip installpython setup.py install时消失。不是少了功能,就是安装了之后跑不起来。

值得一提的是,include_package_data=True只对 VCS(git、svn 等)跟踪的文件有效,而且只在 wheel 打包场景下生效。MANIFEST.in 则是直接指定,不管你用不用版本控制,也不管你打什么类型的包。

实际怎么写,以及常见坑点

文件格式很简单,每行一个指令,通常用到的就这几个:

include README.rst CHANGELOG.rst recursive-include config *.yaml *.yml recursive-include static * prune docs/_build

看起来挺直观,但有几件事特别容易出错。

第一个坑,也是最大的坑——graftrecursive-include的区别不搞清楚,就很容易混乱。recursive-include path pattern是在path目录下找匹配pattern的文件,而graft path是直接包含path目录下的所有文件。很多人觉得用graft省事,但graft会把你整个目录结构都带进去,包括.git__pycache__这些。所以建议用recursive-include更可控。

第二个坑,路径问题。MANIFEST.in 里的路径是相对于项目根目录的,不要写成绝对路径,也不要自以为聪明地写../试图跨越目录。基本不会工作的。

第三个坑,如果使用的是较新版本的 setuptools(比如 40.0 以上),有可能会遇到 MANIFEST.in 和include_package_data相互覆盖的问题。简单来说,如果在 MANIFEST.in 里明确 exclude 了某个文件,而include_package_data又想包含它,最终结果可能跟你预期不一样。我习惯的做法是:在 MANIFEST.in 中只定义需要包含的额外文件,把“排除”逻辑留给 setup.cfg 里的exclude配置。

举个比较完整的例子,假设有一个叫mytool的项目:

# 项目根目录下 . ├── mytool/ │ ├── __init__.py │ ├── core.py │ └── templates/ │ └── main.html ├── config/ │ └── prod.yaml ├── tests/ │ ├── test_core.py │ └── fixtures/ │ └── sample_data.csv ├── docs/ │ └── reference.md ├── setup.py ├── setup.cfg └── MANIFEST.in

对应的 MANIFEST.in 可以这样写:

include LICENSE include README.md recursive-include config *.yaml recursive-include mytool/templates * recursive-include tests/fixtures *.csv prune docs/_build

这样,源码包就会包含 LICENSE、README、所有 YAML 配置文件、模板文件,还有测试用的固定数据,同时排除掉自动生成的文档目录。

最佳实践的几个建议

第一点,把 MANIFEST.in 当成项目的一部分来维护。很多人在项目初期随手写一个,后面再也不看。但如果加了一个数据目录、变更了文件结构,MANIFEST.in 也需要同步更新。一个比较好的习惯是,每次修改 setup.cfg 或 setup.py 时,顺手看一眼 MANIFEST.in。

第二点,利用python setup.py sdist和检查生成的 tar 包来验证。运行python setup.py sdist后,去dist/目录解压生成的包,看看里面到底有什么文件。这是最直接的验证方式。也可以运行python -m tarfile -l dist/*.tar.gz快速列出内容。

第三点,尽量不要在 MANIFEST.in 中使用global-exclude。这个指令会排除所有目录下匹配的文件,容易误伤。如果想在特定目录排除某些文件,用prune或者recursive-exclude配合具体路径更靠谱。

第四点,如果项目使用了命名空间包(namespace package),或者包含 C 扩展,MANIFEST.in 里要特别注意包含 C 源文件和头文件。缺少头文件会导致在用户机器上编译失败。

同类工具或机制的对比

这里最容易被拿来比较的是几个东西:setup.cfg里的[options.package_data]setup.py里的include_package_data、以及 wheel 包专用的data_files

setup.cfg里的setup.cfg其实和 MANIFEST.in 有部分重叠功能,但它控制的是包安装后的文件,不是源码分发包的内容。一个比较尴尬的场景是:用package_data指定了要包含某个文件,但 MANIFEST.in 没写,那么打 sdist 时这个文件不会被包含,当用户通过源码安装时,package_data也找不到这个文件。所以两者往往要配合使用。

include_package_data=True是个看起来省心,实际需要小心的配置。它会自动包含版本控制(git 或 svn)中追踪的所有文件。但如果版本控制里有一堆测试数据、模板、或者自动生成的文档,它们就都会被包含。这时用 MANIFEST.in 提供更精细的控制反而更可靠。

wheel 包有自己的一套规则,wheel 格式本身是基于 zip 的,包含文件相对简单。比如.dist-info/METADATA里记录了文件列表。所以如果项目只提供 wheel 包,MANIFEST.in 的重要度会降低一些。但很多项目仍然需要同时提供 sdist,特别是那些需要编译扩展的现实场景。

还有一点,一些现代构建工具(如 flit、poetry)对 MANIFEST.in 的支持度不同。flit 基本不依赖 MANIFEST.in,而是通过 pyproject.toml 中的tool.flit.include来控制。Poetry 也类似,在tool.poetry.packages或者include字段中指定。但即便用这些新工具,如果项目里还保留了 setup.py(有些项目需要兼容老工具链),MANIFEST.in 仍然需要维护。

一句话总结:如果项目还在用 setuptools、distutils 那一套,MANIFEST.in 是必不可少的部分。它负责在打包阶段,把那些系统默认不会携带但实际必要的文件包含进去。不写的话,大概率会在某个不经意的瞬间,被用户报告“文件缺失”的 bug。

http://www.jsqmd.com/news/719063/

相关文章:

  • dstack:本地AI计算集群的高效管理工具
  • DLSS Swapper技术架构深度解析:多平台游戏DLSS文件管理系统的设计与实现
  • Tesseract-OCR不止于安装:在Windows上用Python调用它,实现批量图片转文本的自动化脚本
  • AI时代后端架构的“围栏”哲学:如何用约束驯服智能体的随机性
  • 代码审查文化:建设性反馈与知识传播的结合
  • VS Code Markdown Preview Enhanced 深度指南:从技术文档到交互式演示的完整解决方案
  • DV170E0M-N30京东方液晶屏代理17寸LCD显示屏LVDS接口参数
  • 2026年4月防爆电子秤哪家性价比高?国产防爆电子秤/防爆秤源头工厂/防爆电子秤厂家直销选择指南 - 品牌推荐大师1
  • 为智能体装上“实时百科全书”:RAG 如何打破 AI 的知识边界?
  • Docker 学习1 - 入门基础篇
  • 从“对话者”到“执行者”:AI Agent 产品设计与系统架构深度研究
  • 告别下载!给Ecology9流程表单附件加个“直接打印”按钮(附完整Ecode代码)
  • 铭饮食品:奶茶原料源头/茶饮供应链一站式服务/奶茶咖啡店免费培训/奶茶原料批发/奶茶咖啡原料出口公司,布局广东广州等地区,赋能茶饮行业升级 - 十大品牌榜
  • 智慧职教刷课脚本:3分钟解放你的在线学习时间
  • 解锁群晖NAS网络性能:Realtek USB网卡驱动的深度配置指南
  • 终极游戏模组加载器:3分钟学会安装任何游戏插件
  • CSS随笔记
  • 浏览器P2P文件传输终极指南:5分钟掌握FilePizza完整解决方案
  • Platinum-MD:终极解决方案!如何让古董MiniDisc设备重获新生?
  • OPRF技术如何增强FIDO2多设备认证安全性
  • 别再只用border-radius了!用CSS radial-gradient实现Chrome标签页同款反向圆角
  • 拉萨装配式建筑首选方案:西藏藏建科技vs中国建筑、万科、碧桂园、中铁建工深度对比 - 优质企业观察收录
  • 从理论到代码:拆解ORB-SLAM中‘关键帧’与‘地图点’管理的那些精妙设计
  • 3分钟掌握GPU内存检测:MemtestCL终极指南与实战技巧
  • macOS桌面歌词终极指南:LyricsX 2.0快速上手教程
  • 远程开发环境还在“全量启动”?揭秘VS Code容器生命周期管理:冷启动→热复用→自动休眠的3级智能调度机制
  • CAR-Flow:高效条件流匹配模型的技术解析与实践
  • 手把手教你用Python logging和Allure2生成可交互的测试日志报告
  • 书匠策AI:毕业论文写作的“智慧魔法棒”,开启学术新纪元!
  • 告别手动下载!Eclipse 2022-06 最新版一键安装中文语言包保姆级教程