使用 FPM 将 CPAN Perl 模块打包为 deb、rpm:完整指南与源码级解析
【免费下载链接】fpmEffing package management! Build packages for multiple platforms (deb, rpm, etc) with great ease and sanity.项目地址: https://gitcode.com/gh_mirrors/fp/fpm
导读
本文聚焦 FPM(Effing Package Management)项目中的cpan输入类型,讲解如何把 CPAN 上的 Perl 模块(或本地 Perl 模块目录)一键转换成 deb、rpm 等系统原生包。读完本文,你将掌握fpm -s cpan -t deb的完整实战用法、cpan 专属命令行参数的默认值与行为,以及 FPM 从 MetaCPAN 搜索、下载、构建到生成包的底层调用链,可直接用于 Perl 依赖的自动化打包与分发。
一、cpan 输入类型的定位:只进不出
在 FPM 中,每种包类型既可以作为输入(-s,source),也可以作为输出(-t,target)。但根据 docs/packages/cpan.rst 的明确说明,cpan类型只支持作为输入类型使用:
- 你不能用
-t cpan把其他格式转换为 CPAN 发行包; - 你只能用
fpm -s cpan -t <输出格式>把 Perl 模块转换为 deb、rpm、solaris、freebsd、osxpkg、puppet、sh、tar、zip 等输出格式(完整输出类型见 docs/getting-started.rst)。
这一设计从代码结构上也可以印证:在 lib/fpm/package/cpan.rb 中,类FPM::Package::CPAN只实现并公开了input(package)方法(见文件末尾public(:input)),而没有实现output相关的转换逻辑;其他包类型的输出转换由各自对应的 lib/fpm/package/ 文件完成。
二、输入参数:模块名或本地目录
作为输入类型时,fpm命令位置参数(即模块名)支持两种写法:
- MetaCPAN 上的模块名:例如
Regexp::Common、File::Temp、Digest::MD5。FPM 会先在 MetaCPAN 上搜索该模块,找到后自动下载对应发行包用于构建; - 本地目录路径:包含某个 Perl 模块源码的本地目录。FPM 会直接使用该目录进行构建,跳过 MetaCPAN 搜索与下载环节。
这一分支逻辑位于 lib/fpm/package/cpan.rb:
if File.exist?(package) moduledir = package # 本地目录:直接使用 result = {} else result = search_module(package, version) # 远程模块:先搜索 tarball = download(result, version) # 再下载 moduledir = unpack(tarball) # 解压到构建目录 end位置参数数量不限(任意多个都可以),每个参数按上述规则独立处理。
三、快速上手:把 Regexp::Common 打包成 deb
原文档给出了一个完整可复现的示例——将 CPAN 上的Regexp::Common模块打包为 Debian 包:
fpm -s cpan -t deb Regexp::CommonFPM 帮你完成了大量工作:先在 MetaCPAN 上搜索Regexp::Common,然后下载最新版本,最后构建出 deb 包。实际运行时你会看到如下输出:
Downcasing provides 'perl-Regexp-Common' because deb packages don't work so good with uppercase names {:level=>:warn} Downcasing provides 'perl-Regexp-Common-Entry' because deb packages don't work so good with uppercase names {:level=>:warn} Debian tools (dpkg/apt) don't do well with packages that use capital letters in the name. In some cases it will automatically downcase them, in others it will not. It is confusing. Best to not use any capital letters at all. I have downcased the package name for you just to be safe. {:oldname=>"perl-Regexp-Common", :fixedname=>"perl-regexp-common", :level=>:warn} Debian packaging tools generally labels all files in /etc as config files, as mandated by policy, so fpm defaults to this behavior for deb packages. You can disable this default behavior with --deb-no-default-config-files flag {:level=>:warn} Created package {:path=>"perl-regexp-common_2017060201_all.deb"}3.1 关于那些警告信息
示例中出现的几行警告并非错误,而是 FPM 为让生成包被 Debian 工具链接纳所做的主动适配:
- 包名小写化:Debian 包名约定全部使用小写。FPM 在检测到
perl-Regexp-Common这类含大写字母的名字时,会提示"已经帮你转为小写"(fixedname=>"perl-regexp-common"); ::替换为-:Debian 包名不允许包含::,FPM 自动将其替换为短横线。Regexp::Common最终得到包名perl-Regexp-Common,再经小写化变为perl-regexp-common;- /etc 配置文件策略:Debian 策略要求 /etc 下的文件一律标记为配置文件,FPM 默认遵循该行为,可通过
--deb-no-default-config-files关闭(该 flag 属于 deb 输出类型,详见 docs/packages/deb.rst)。
3.2 底层命名逻辑
上述转换由 lib/fpm/package/cpan.rb 中的两个私有方法实现:
def cap_name(name) return "perl(" + name.gsub("-", "::") + ")" end def fix_name(name) case name when "perl"; return "perl" else; return [attributes[:cpan_package_name_prefix], name].join("-").gsub("::", "-") end endfix_name负责包名:把前缀(默认perl)与发行包名用-连接,并把::替换成-;cap_name负责依赖与 provides 的命名:把模块名包装成perl(模块名)形式(如perl(Regexp::Common)),这是 Debian/CPAN 生态通用的虚拟包命名习惯;- 小写化则由 deb 输出端在构建阶段统一完成。
四、指定版本:使用 -v 参数
FPM 默认下载 MetaCPAN 上的最新版本。如果你需要固定某个版本,使用全局的-v参数即可,例如:
fpm -s cpan -t deb -v 2016060201 Regexp::Common在源码层面,版本号会同时作用于两处:
- MetaCPAN 搜索:
search_module(module_name, version)构建查询时,若指定版本则匹配module.version,否则匹配status: latest(见 lib/fpm/package/cpan.rb); - 下载地址定位:
download(metadata, version)会用发行包名-版本号精确查询 MetaCPAN 的 release 搜索接口,得到该版本的 download_url(见 lib/fpm/package/cpan.rb)。
五、安装与验证生成的包
原文档示范了完整的安装与使用闭环。先安装刚才生成的 deb 包:
sudo dpkg -i perl-regexp-common_2017060201_all.deb输出示例:
Selecting previously unselected package perl-regexp-common. (Reading database ... 81209 files and directories currently installed.) Preparing to unpack perl-regexp-common_2017060201_all.deb ... Unpacking perl-regexp-common (2017060201) ... Setting up perl-regexp-common (2017060201) ... Processing triggers for man-db (2.9.1-1) ...然后验证模块可用——让Regexp::Common输出一个匹配实数的正则表达式:
perl -MRegexp::Common -e 'print $RE{num}{real}'输出:
(?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[-+]?)(?:[0123456789]+))|))这说明模块已成功安装到 Perl 的库路径中,可以被use正常加载,打包验证通过。
六、cpan 专属命令行参数详解
除全局参数(如-s、-t、-v、-p、-n)外,cpan 输入类型还提供一组专属参数,定义在 lib/fpm/package/cpan.rb,并被 docs/packages/cli/cpan.rst 收录。下面结合源码中的默认值逐一说明:
| 命令行参数 | 参数值 | 作用 | 默认值 |
|---|---|---|---|
--cpan-perl-bin PERL_EXECUTABLE | 字符串 | 指定要调用的 perl 可执行文件路径 | perl |
--cpan-cpanm-bin CPANM_EXECUTABLE | 字符串 | 指定要运行的 cpanm 可执行文件路径 | cpanm |
--cpan-mirror CPAN_MIRROR | 字符串 | 使用指定的 CPAN 镜像替代默认源(源码默认回退http://www.cpan.org/) | 无 |
--[no-]cpan-mirror-only | 布尔 | 仅用指定镜像获取元数据(依赖--cpan-mirror) | false |
--cpan-package-name-prefix NAME_PREFIX | 字符串 | 包名前缀 | perl |
--cpan-perl-lib-path PERL_LIB_PATH | 字符串 | 目标 Perl 库安装路径 | 无(跟随本机路径) |
--[no-]cpan-sandbox-non-core | 布尔 | 对非核心模块做沙箱隔离(即使已安装也重新安装到临时目录) | true |
--[no-]cpan-test | 布尔 | 打包前是否运行模块测试 | true |
--[no-]cpan-verbose | 布尔 | 是否让 cpanm 输出详细日志 | false |
--cpan-cpanm-force | 布尔 | 向 cpanm 传递--force参数(跳过失败继续安装) | false |
--cpan-disable-dependency MODULE | 多值 | 从自动生成的依赖中过滤指定模块(按 Perl 模块名,如URI::Escape) | ['vars','warnings','strict','Config'] |
--cpan-metacpan-api-url URL | 字符串 | MetaCPAN API 地址 | https://fastapi.metacpan.org |
6.1 关于--[no-]布尔参数
布尔型参数支持--flag与--no-flag两种写法。这一机制来自 lib/fpm/package.rb 的option定义:当参数为:flag类型时,FPM 会自动为它生成--[no-]类型-名称形式。例如:
fpm -s cpan -t deb --no-cpan-test Regexp::Common会跳过测试(速度更快,适合 CI 流水线);fpm -s cpan -t deb --no-cpan-sandbox-non-core Regexp::Common会取消依赖沙箱。
6.2 与全局参数的关系
-v指定模块版本,而--cpan-package-name-prefix指定生成包的前缀;若某模块名本身就是perl(如对 Perl 核心打补丁的场景),fix_name会直接返回perl而不再拼接前缀,见上文fix_name代码。
七、与 Perl 模块打包相关的常见调优示例
结合 docs/getting-started.rst 中"Packaging a CPAN module"一节,FPM 对 CPAN 打包默认持有三条假设,均可通过参数调整:
- 本机 Perl 库路径即目标 Perl 库路径;
- 包名默认以
perl为前缀;- CPAN 依赖关系有效,且依赖的命名均以
perl为前缀。
若本机 Perl 安装在/opt/usr/share/perl5.10,但希望生成的包将模块安装到/usr/share/perl5(目标机器路径),可执行:
fpm -t deb -s cpan --cpan-perl-lib-path /usr/share/perl5 Fennec若希望包名前缀从perl-Fennec变为fubar-Fennec:
fpm -t deb -s cpan --cpan-package-name-prefix fubar Fennec最朴素的用法则是直接用模块名打包:
fpm -s cpan -t deb FennecFPM 会自动从 CPAN 下载 Fennec 并生成本地 Debian 包。
八、源码级解析:一次fpm -s cpan的完整调用链
理解内部流程有助于排查构建失败。FPM::Package::CPAN#input(lib/fpm/package/cpan.rb)的完整流程如下:
- 获取源码:本地目录直接使用;否则
search_module查询 MetaCPAN module 接口(要求maturity: released,未指定版本时取status: latest),download再通过 MetaCPAN release 接口拿到精确下载地址(默认回退到http://www.cpan.org/下的路径),最终unpack解压到构建目录(解压时会用--transform去掉顶层目录)。 - 读取元数据:按
META.json→META.yml→MYMETA.json→MYMETA.yml的优先级读取模块元数据(lib/fpm/package/cpan.rb),并与 MetaCPAN 查询结果合并,本地 META 文件优先。若全部缺失,抛出FPM::InvalidPackageConfiguration。 - 填充包元信息:从元数据中提取版本、描述(abstract)、许可证(license,数组取第一个,缺失为 unknown)、作者(vendor,缺失为 "No Vendor Or Author Provided")、主页 URL;
architecture默认all。 - 安装构建/配置依赖:调用
cpanm(默认参数-L沙箱目录或-l局部目录 +--installdeps,仅安装依赖不重复构建目标模块)。--no-cpan-test追加-n,--cpan-mirror追加--mirror,--cpan-mirror-only追加--mirror-only,--cpan-cpanm-force追加--force(lib/fpm/package/cpan.rb)。 - 自动解析依赖:合并 META 中的
requires与prereqs.runtime.requires,生成perl(模块名)形式的依赖(可用--cpan-disable-dependency过滤)。其中对perl核心依赖会做版本号格式转换(如5.010形式的转换),版本为0视为任意版本,复杂的版本字符串(如>= 0, != 1.0, != 1.2)会被拆分处理(lib/fpm/package/cpan.rb)。 - 构建与安装:在模块目录内,若存在
Build.PL则走 Module::Build 流程(Build.PL→./Build→./Build test→./Build install);否则若存在Makefile.PL则走 ExtUtils::MakeMaker 流程(Makefile.PL PREFIX=... INSTALL_BASE=→make→make test→make DESTDIR=... install);两者都没有则报错(lib/fpm/package/cpan.rb)。--cpan-perl-lib-path会作为LIB/--install_path lib参与安装路径控制。 - 清理无用文件:删除
perllocal.pod与.packlist(后者连同空父目录一并移除),避免多包冲突与冗余(lib/fpm/package/cpan.rb)。 - 架构修正:在暂存目录中查找
.so共享库文件,若发现则把架构从all改为native——这解决了含 XS 编译扩展的模块必须按本机架构打包的问题(lib/fpm/package/cpan.rb)。
九、测试与质量保障
仓库在 spec/fpm/package/cpan_spec.rb 中对 cpan 输入类型有完整覆盖,可帮助你理解各行为的预期:
- 包名前缀拼接:
fix_name("Foo::Bar") == "prefix-Foo-Bar"; - 模块名包装:
cap_name("Foo::Bar") == "perl(Foo::Bar)"; - 真实网络请求:对 MetaCPAN 的
httpfetch/httppost返回Net::HTTPOK; - 元数据搜索:
search_module("File::Temp")返回 distributionFile-Temp; - 精确版本下载:
download(metadata, "0.01")得到Set-Tiny-0.01.tar.gz; - provides 生成:
search_provided_modules("Test-DB", "0.10")得到perl(Test::DB) = 0.10等条目(含无版本模块); - 端到端打包:
Digest::MD5(验证依赖解析)与Regexp::Common均可生成名为perl-*的包。
这些测试需要系统 PATH 中存在cpanm,否则会被跳过(见测试文件顶部的检测逻辑),这提醒我们在使用 cpan 输入类型前,须先确保本机安装好 perl 与 cpanm。
十、使用前提与注意事项
- 环境依赖:cpan 输入类型依赖本机的
perl与cpanm可执行文件(默认路径为 PATH 中的perl/cpanm,可通过--cpan-perl-bin/--cpan-cpanm-bin指定); - 网络依赖:搜索与下载模块需要能访问 MetaCPAN API(默认
https://fastapi.metacpan.org,可配置);下载源默认回退http://www.cpan.org/,可通过--cpan-mirror换成镜像。源码中的 HTTP 请求支持http_proxy环境变量代理(见 lib/fpm/package/cpan.rb); - 命名适配:输出为 deb 时包名会被小写化并将
::替换为-,这是为了兼容 Debian 工具链的强制约束,属于预期行为; - 关闭自动依赖:若不想自动生成依赖,可使用全局参数
--no-auto-depends(见 lib/fpm/command.rb); - CI 提速:在自动化构建中可配合
--no-cpan-test跳过上游模块测试以缩短构建时间,但需评估风险。
综上,FPM 的 cpan 输入类型打通了"CPAN Perl 模块 → 系统原生包"的自动化通道:一条fpm -s cpan -t deb命令即可完成搜索、下载、依赖解析、构建、命名适配与打包,源码实现(lib/fpm/package/cpan.rb)与测试(spec/fpm/package/cpan_spec.rb)为每项行为提供了可验证的依据,适合在需要批量交付 Perl 依赖的运维与发布场景中直接落地。
【免费下载链接】fpmEffing package management! Build packages for multiple platforms (deb, rpm, etc) with great ease and sanity.项目地址: https://gitcode.com/gh_mirrors/fp/fpm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考