Introduction
To build a Perl module on openSUSE or other distributions, you need a file
with build instructions. For rpm that’s usually a file called
package-name.spec
.
See also General packaging guidelines and the Perl packaging guidelines.
In Open Build Service devel:languages:perl we currently have over 3100 perl packages. That would not have been possible without some automation.
In this post I will describe how cpanspec can create spec files automatically, and how it can even update spec files for a new release without overriding manual changes from before.
I will also mention some differences to other languages and specifically py2pack.
Another similar script is gem2rpm for ruby.
Anatomy of a Perl Module 🦴
A perl module on CPAN is actually called a distribution. It can contain more than one module, in this case referring to perl package names and files.
I will describe the common structure here, but there can be exceptions to basically everything. As an example I will show YAML::PP on CPAN / perl-YAML-PP in OBS.
The CPAN server does not enforce any rules for the uploaded tarballs. But the build tools which are creating those distributions ensure some minimum common structure for the large majority of perl distributions out there.
That is not the case for some other languages, which makes automating packaging harder for them.
Source 📝
The source is uploaded as a tarball (tgz
, tar.gz
, but can also be bz2
or
zip
).
The tarball for YAML::PP
is called YAML-PP-v0.39.0.tar.gz
.
Every distribution consists of one or more modules, and there is usually one main module the distribution gets its name and version from.
The tarball standard name is Main-Module-version.tar.gz
, and that’s the case
for over 98% of the distributions, I’d say.
In python, the tarballs sometimes use underscores, sometimes hyphens, which already requires manual adjustments.
Files 📁
The most common layout is something like this:
lib/Main/Module.pm
lib/Main/Module/Other.pm
t/some-test.t
META.yml
META.json
Makefile.PL
Changes
LICENSE
MANIFEST
Documentation 📖
Perl modules are documented with Pod - Plain Old Documentation.
The most common and recommended layout of the documentation for modules, especially for the main module, is:
NAME
: Main::Module - AbstractSYNOPSIS
: Short code examplesDESCRIPTION
: Short description- Other sections going into more details
LICENSE
There seems to be no such standard for python modules, for example, so
py2pack
just takes the whole README file, which always has to be shortened
for the spec file manually.
Meta Data
Most modules except ancient ones have a META.yml
and/or META.json
file with
useful meta data, like the license, abstract, distribution name, author,
dependencies, provided package names, homepage, bugtracker.
License ⚖️
The License can be in several places. There’s the LICENSE file, there is the LICENSE section in the Pod, and it should also be in the META files.
If it’s in the META files, it’s usually a short identifier we can use (more or less) directly. Otherwise cpanspec uses heuristics to read the License text and generate a short identifier from it automatically.
See also SPDX Licence List.
For example py2pack
can not do this at this time, so it needs to be adjusted
manually.
Running cpanspec on a new module
If you don’t use cpan
or cpanm
on your machine, you have to download the
package index first:
% curl -O http://www.cpan.org/modules/02packages.details.txt.gz
Then download the tarball. It is also possible to specify the module name, but for demonstration purposes we use the tarball directly.
% curl -O https://cpan.metacpan.org/authors/id/T/TI/TINITA/YAML-PP-v0.38.0.tar.gz
Then generate the spec file:
% cpanspec --pkgdetails 02packages.details.txt.gz YAML-PP-v0.38.0.tar.gz
...
cat perl-YAML-PP.spec
#
# spec file for package perl-YAML-PP
#
# Copyright (c) 2025 SUSE LLC
#
...
For the first generation, it will also create a perl-YAML-PP.changes
for you.
You can skip that with --skip-changes
.
To get more information about the metadata that cpanspec
found, use the -v
switch.
The generated spec file
Let’s go through the file step by step:
%define cpan_name YAML-PP
Name: perl-YAML-PP
Version: 0.38.0
Release: 0
The license in the META file says perl_5
, so cpanspec
uses this SPDX
identifier automatically:
License: Artistic-1.0 OR GPL-1.0-or-later
Also from META:
Summary: YAML 1.2 Processor
Generic url and source for all modules:
URL: https://metacpan.org/release/%{cpan_name}
Source0: https://cpan.metacpan.org/authors/id/T/TI/TINITA/%{cpan_name}-%{version}.tar.gz
cpanspec
uses some heuristics to decide if it’s a noarch build (no .c files
etc.):
BuildArch: noarch
Common requirements:
BuildRequires: perl
BuildRequires: perl-macros
Requirements from module meta data:
BuildRequires: perl(Module::Load)
BuildRequires: perl(Test::More) >= 0.98
BuildRequires: perl(Test::Warn)
Requires: perl(Module::Load)
Common macro:
%{perl_requires}
From module Pod:
%description
YAML::PP is a modular YAML processor.
It aims to support 'YAML 1.2' and 'YAML 1.1'. See https://yaml.org/. Some
(rare) syntax elements are not yet supported and documented below.
...
Common preparation:
%prep
%autosetup -n %{cpan_name}-%{version} -p1
The build section can vary depending on the used build script, detected
automatically by cpanspec
:
%build
perl Makefile.PL INSTALLDIRS=vendor
%make_build
%check
make test
Common:
%install
%perl_make_install
%perl_process_packlist
%perl_gen_filelist
cpanspec
can detect documentation based on some heuristics:
%files -f %{name}.files
%doc Changes CONTRIBUTING.md examples Makefile.dev README.md
%license LICENSE
%changelog
Everything is correct! We’re done 😃
Conclusion
In most cases it is as easy as that. But thanks to the large number of modules, there are still many cases where we have to adjust the abstract, license, requirements, description etc.
Updating a spec file for a new version
If we didn’t have to make manual changes in the original generation, it is as easy as this:
% curl -O https://cpan.metacpan.org/authors/id/T/TI/TINITA/YAML-PP-v0.39.0.tar.gz
% cpanspec --force --pkgdetails 02packages.details.txt.gz YAML-PP-v0.39.0.tar.gz
The --force
switch is needed to automatically overwrite the existing spec
file.
Let’s look at the diff:
diff --git a/perl-YAML-PP.changes b/perl-YAML-PP.changes
index 4fa718b..cc8cbac 100644
--- a/perl-YAML-PP.changes
+++ b/perl-YAML-PP.changes
@@ -1,3 +1,9 @@
+-------------------------------------------------------------------
+Thu Mar 20 14:28:45 UTC 2025 - Tina Müller <tina.mueller@suse.com>
+
+- updated to 0.39.0
+ see /usr/share/doc/packages/perl-YAML-PP/Changes
+
-------------------------------------------------------------------
Wed Mar 19 14:46:52 UTC 2025 - Tina Müller <tina.mueller@suse.com>
diff --git a/perl-YAML-PP.spec b/perl-YAML-PP.spec
index a5a54ed..acced96 100644
--- a/perl-YAML-PP.spec
+++ b/perl-YAML-PP.spec
@@ -18,7 +18,7 @@
%define cpan_name YAML-PP
Name: perl-YAML-PP
-Version: 0.38.0
+Version: 0.39.0
Release: 0
License: Artistic-1.0 OR GPL-1.0-or-later
Summary: YAML 1.2 Processor
That’s easy, and if there were any changes like requiring a new module or a newer version of it, it would automatically be generated.
Doing manual changes
Let’s assume we have to adjust some fields for YAML::PP
, because they are
wrong.
We could manually adjust the spec file. But then our changes would be overwritten with the next version.
Instead we use a configuration file named cpanspec.yml
.
Editing cpanspec.yml
---
patches:
some-bug-in-0.38.patch: -p1
summary: Improved abstract here
# Leftover file in tarball
skip_doc: Makefile.old
license: Actual-license
# A module might have a non-perl dependency
preamble:
BuildRequires: libfoo
We can now regenerate the spec, and it will automatically read cpanspec.yml
,
overwrite the fields and add it to the sources:
% cpanspec --force --skip-changes --pkgdetails 02packages.details.txt.gz YAML-PP-v0.38.0.tar.gz
diff --git a/perl-YAML-PP.spec b/perl-YAML-PP.spec
index a5a54ed..398ff1a 100644
--- a/perl-YAML-PP.spec
+++ b/perl-YAML-PP.spec
@@ -20,10 +20,13 @@
Name: perl-YAML-PP
Version: 0.38.0
Release: 0
-License: Artistic-1.0 OR GPL-1.0-or-later
-Summary: YAML 1.2 Processor
+#Upstream: Artistic-1.0 or GPL-1.0-or-later
+License: Actual-license
+Summary: Improved abstract here
URL: https://metacpan.org/release/%{cpan_name}
Source0: https://cpan.metacpan.org/authors/id/T/TI/TINITA/%{cpan_name}-v%{version}.tar.gz
+Source1: cpanspec.yml
+Patch0: some-bug-in-0.38.patch
BuildArch: noarch
BuildRequires: perl
BuildRequires: perl-macros
@@ -32,6 +35,9 @@ BuildRequires: perl(Test::More) >= 0.98
BuildRequires: perl(Test::Warn)
Requires: perl(Module::Load)
%{perl_requires}
+# MANUAL BEGIN
+BuildRequires: libfoo
+# MANUAL END
%description
YAML::PP is a modular YAML processor.
There are many more fields you can adjust with this. See this example file with all supported fields.
This way we will never have to touch the spec file itself, and it will survive any version update.
(There can be exceptions, when you have to make changes that cpanspec.yml
does not support, but it’s rare.)
Perl Module Versions
Perl module versions are a bit special. The traditional versions are simply
decimals, unlike semantic versions we know from elsewhere. In the example
above the module used the new version format with two dots and a v
in front,
which behave like we expect from semantic versions.
In the article about updating CPAN modules I explained it more detailed.
cpanspec
is currently getting a feature to “normalize” the old decimal
versions, but it’s not yet released at the time of this writing.