Hide namespaces from PAUSE

The Perl Authors Upload Server (PAUSE) is responsible for analyzing distributions on their way to CPAN. PAUSE indexes the distributions to discover the package names that it contains so it can add them to the data files that many of the CPAN clients use to figure out what to download to install the module that you request. It also compares the package names that it finds to a list of permissions it maintains.

The mldistwatch program is reponsible for this bit. It tries two things to find the packages in the distribution. If it can read the META.yml (or META.json) to get the data. Otherwise, it examines files directory to look for package declarations. Sometimes these come up with the wrong answers.

First, there’s an easy fix to package statements in code. After ignoring text in Pod or after __END__ or __DATA__, PAUSE looks for package statements appearing on a single line:

# from PAUSE::pmfile::packages_per_pmfile()
           $pline =~ m{
                      (.*)
                      \bpackage\s+
                      ([\w\:\']+)
                      \s*
                      (?: $ | [\}\;] | ($version::STRICT) )
                    }x

If your complete package statement isn’t on a single line, then that won’t match it. Since Perl has insignificant whitespace, including vertical whitespace, you could to this:

package
    hide::this::package;

You might even leave yourself (or other developers) a note about the importance of that whitespace now:

package # hide from pause
    hide::this::package;

Indeed, if you grep CPAN, you’ll find hide from pause in many distributions.

That’s the easy way, although it’s kludgey and relies on a special case in the PAUSE code. Other indexers might not honor it. There’s a better way for you to explicitly tell an indexer what namespaces you want to advertise. You add them to the provides section of META.yml:

provides:
  Cats::Buster:
	file: lib/Cats/Buster.pm
	version: 0.01

The data in come from the META-spec. Module::Build will automatically create these entries in META.yml for you. The indexer can use these to know what’s in the distribution without directly examining module files.

If there are multiple packages declarations, all of them shown up in META.yml:

provides:
  Cats::Buster:
    file: lib/Test/Provides.pm
    version: 0.01
  Cats::Mimi:
    file: lib/Test/Provides.pm
    version: 0,02
  version:
    file: lib/Test/Provides.pm
    version: 0.01

Notice that version shows up in that list. You may have included it in your module to extend or override parts of that core module, but you don’t want people who want the real version to install your module to get it. You might only declare that package in your module as a temporary workaround and don’t intend it to be a permanent part of the work. They probably wouldn’t be able to do that anyway since PAUSE would recognize that you included a package for which you do not have permissions and would not index it. A site such as CPAN Search might mark your otherwise good distribution as “UNAUTHORIZED“. Module::Build doesn’t know to exclude version, at least not by default.

To hide that package from indexers, you can specify it in no_index. In Build.PL, you can use META_ADD to specify that parts of the META-spec not already supported by other arguments to new:

use Module::Build;

my $builder = Module::Build->new(
	...,
	meta_add => {
		no_index => {
			package   => [ qw( version Local ) ],
			directory => [ qw( t/inc inc ) ],
			file      => [ qw( t/lib/test.pm ) ],
			namespace => [ qw( Local ) ],
			},
		},
);

The directory and file keys tell the indexer to ignore those parts of the distribution. The package tells the indexer to ignore exactly those packages. The curious one is namespace, which tells the indexer to ignore namespaces under that namespace.

Likewise, you can do the same in Makefile.PL with a recent enough version:

use ExtUtils::Makemaker 6.48;

WriteMakefile(
	...,
	META_ADD => {
		no_index => {
			package   => [ qw( version Local ) ],
			directory => [ qw( t/inc inc ) ],
			file      => [ qw( t/lib/test.pm ) ],
			namespace => [ qw( Local ) ],
			},
		},
	);

Otherwise, it examines the module files to find package statements, but it does it without running the code.

But, what if provides and no_index have conflicting instructions? The META-spec doesn’t give any guidance for indexers in those cases. PAUSE filters on no_index last. This means that PAUSE and other indexers might leave out files you specify in provides but then exclude in no_index.

Things to remember

  • Spread the package statement over two or more lines to hide it from PAUSE
  • Use provides to advertise the namespaces a distribution comprises.
  • Use no_index to limit what an indexer sees or reports.