Convert a Perl 4 Library with pl2pm
Somewhere in the codebase is a .pl file nobody has touched since Perl 4, and now it needs to become a proper .pm module. pl2pm does the mechanical first pass, and this shows you how to check the result before trusting it. Allow 15 to 30 minutes for a small library, plus whatever review time the code actually needs. The installed command here comes from the Perl package at version 5.38.2-3.2ubuntu0.6, and the local manual is honest about it: a rough conversion aid, not a complete translator.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is an ordinary, unprivileged workflow. Work in a copy or a version-controlled checkout. Do not run the conversion against a system library, or a directory where you cannot easily recover the original.
1. Check the command and make a working copy
Confirm which executable will run, then copy the source library into a scratch directory. Replace the example paths with your own. The source filename must end in lowercase .pl; that suffix is how pl2pm decides which input files it is willing to translate.
$ command -v pl2pm
/usr/bin/pl2pm
$ mkdir -p ~/pl2pm-work
$ cp --preserve=all /path/to/legacy/widget.pl ~/pl2pm-work/
Checkpoint: the input should still be present and unchanged.
$ ls -l ~/pl2pm-work/widget.pl
$ sha256sum /path/to/legacy/widget.pl ~/pl2pm-work/widget.pl
Do not use sudo for any of this. If the source is readable only by root, fix the working-copy arrangement through your normal access process rather than running the whole conversion as root.
2. Run pl2pm on the copied library
Pass one or more input filenames. There are no documented options in the local manual, and the command writes its result beside each input rather than to standard output.
$ cd ~/pl2pm-work
$ pl2pm widget.pl
$ ls -l widget.pl Widget.pm
The output name comes from changing .pl to .pm and capitalising the first word-like part of the basename, so widget.pl becomes Widget.pm here. The generated module opens with a package declaration, a Perl version requirement, Exporter setup and an export list, then the transformed source follows.
A successful run prints nothing at all. Verify the result by looking at its start:
$ sed -n '1,24p' Widget.pm
package Widget;
use 5.006;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(...);
The exact export list and body depend on your input. Treat that structure as a shape to check, not a promise that every legacy construct survived intact.
3. Understand what the first pass changes
The installed script makes mechanical substitutions aimed at Perl 4 libraries. It can translate old package-qualified variable and subroutine references, strip some old $[ idioms, turn simple open NAME calls into open(NAME), and swap die for croak while adding use Carp;. It also recognises a matching require 'widget.pl'; line and rewrites it as a module-style use Widget.
That list is deliberately short. It proves nothing about whether the code now follows modern Perl conventions, handles warnings properly, or preserves the original behaviour. The manual calls this a first step, and means it: inspect regular expressions, indirect filehandles, global variables, package boundaries, export choices, and anything that leans on Perl 4 semantics.
Checkpoint: compare the generated file with the original instead of trusting it blind.
$ diff -u widget.pl Widget.pm
$ git diff --no-index -- widget.pl Widget.pm
The second command returns a non-zero status when differences exist, which here is completely normal. Do not read that status as a failed conversion.
4. Compile the generated module
Use Perl's syntax-only check before loading the module into anything real:
$ perl -c Widget.pm
Widget.pm syntax OK
This checks syntax and compile-time activity only. It does not test exported functions, file handling, runtime data, or compatibility with whatever program used the old library. If it reports an error, edit a review copy or go back to the source, fix the problem deliberately, and rerun the check.
For a module that is safe to load in your test environment, add its directory to Perl's library path and ask Perl to load it:
$ perl -I. -MWidget -e 'print "loaded\\n"'
loaded
Loading a module can run compile-time code, so use a disposable test environment when the legacy library has side effects or reads configuration. This is not a substitute for the module's actual test suite.
5. Avoid accidental overwrites and misleading inputs
pl2pm will not overwrite an existing output file. If Widget.pm already exists, it warns and skips that input:
$ pl2pm widget.pl
Won't overwrite existing Widget.pm
Useful protection, but it does not back anything up or merge changes for you. Decide whether the existing module is the one you meant to keep before moving it aside. If you genuinely want a fresh result, rename the old output in your version-controlled working copy, run the command, and keep the old file until review is finished.
Do not pass --help or --version expecting normal option handling. The installed program has no such interface and treats every command-line argument as an input filename; a missing file produces a Perl file-open error. A file whose name does not end in .pl gets skipped, not converted, so check the spelling of every input.
6. Finish the migration manually
Keep the original .pl file until tests show the module behaves correctly. Update callers to load the module by its package name, review @EXPORT and prefer explicit imports where it makes sense, then run the application's tests. Add real module documentation and a deliberate interface rather than treating the generated export list as an API design.
If the migration is abandoned, recovery is simple: discard only the generated .pm from the scratch copy. If it has already been committed, restore it through version control after checking the path and commit. Never delete the only copy of the original library as part of this.
Done means
- The original
.plfile remains available and unchanged. - pl2pm created a new, correctly named
.pmfile beside the copy. - The generated module passed
perl -c. - You reviewed the diff and the generated exports instead of trusting the mechanical translation.
- Application tests cover the behaviour the old library actually provided.