PGP-Sign

 view release on metacpan or  search on metacpan

.gitignore  view on Meta::CPAN

/MANIFEST.bak
/MYMETA.json
/MYMETA.json.lock
/MYMETA.yml
/PGP-Sign-*/
/PGP-Sign-*.tar.gz
/_build/
/blib/
/cover_db/
/pm_to_blib
/t/data/gnupg1/pubring.gpg~
/t/data/gnupg1/random_seed
/t/data/gnupg1/trustdb.gpg
/t/data/gnupg2/trustdb.gpg

Build.PL  view on Meta::CPAN


use Module::Build;

# Basic package configuration.
my $build = Module::Build->new(
    module_name          => 'PGP::Sign',
    dist_author          => 'Russ Allbery <rra@cpan.org>',
    license              => 'perl',
    recursive_test_files => 1,
    add_to_cleanup       =>
      [qw(MANIFEST.bak cover_db t/data/random_seed t/data/trustdb.gpg)],

    # Add additional package metadata.
    meta_merge => {
        'meta-spec' => { version => '2' },
        resources   => {
            bugtracker => {
                mailto => 'bug-PGP-Sign@rt.cpan.org',
                web => 'https://rt.cpan.org/Dist/Display.html?Name=PGP-Sign',
            },
            homepage   => 'https://www.eyrie.org/~eagle/software/pgp-sign',

Changes  view on Meta::CPAN


    Document that GnuPG 2.1.23 or GnuPG 1.4.20 or later is required and
    skip tests on platforms that do not meet those version requirements.
    The alternative would be auto-discovery of which command-line flags to
    use and these version requirements are met by Debian stable (and
    Debian oldstable with backports), so hopefully this restriction will
    not cause too much hardship.

PGP::Sign 1.02 (2020-08-29)

    On systems where gpg is GnuPG v1, override the path to the gpg binary
    in the test suite.  Some tests were still incorrectly looking for a
    gpg1 binary.

    On systems where gpg as found on the PATH is GnuPG v2 but is older
    than 2.1.12 and therefore doesn't support the command-line arguments
    PGP::Sign uses, skip the relevant tests.  Tests are skipped rather
    than failed because this doesn't represent a problem with the module
    and the module can still be used with explicit configuration pointing
    to a different version of GnuPG.

PGP::Sign 1.01 (2020-07-18)

    Fix test suite to pass on systems where gpg is GnuPG v1.  This is
    apparently still common among many CPAN tester machines, and thus
    probably other systems in the wild.  This does not change the module's
    default behavior; systems using GnuPG v1 still need to pass an
    explicit style => 'GPG1' argument to the PGP::Sign constructor.

    Update to rra-c-util 8.3:

    * Fix style issues caught by Perl::Critic::Freenode.
    * Ignore debian/changelog when checking for obsolete strings.

Changes  view on Meta::CPAN

    chooses by default.  It can stll be overridden by setting the tmpdir
    constructor parameter or $PGP::Sign::TMPDIR.

    Rewrite the build system to use Module::Build.  This eliminates the
    spurious VERSION.pm "module" at the top level, which was a hack for
    setting the distribution version in old versions of
    ExtUtils::MakeMaker and should improve the indexing of the module.
    Move the module into a lib structure and the test suite data into
    t/data.  Eliminate all of the prompting and command-line parameters to
    set the PGP style and path to programs; instead, PGP::Sign will
    default to using gpg1 from the user's PATH.

    Rewrite ChangeLog into a more conventional Changes file.

PGP::Sign 0.20 (2007-04-27)

    Unbuffer output when building the module since there is an interactive
    prompt.

PGP::Sign 0.19 (2004-08-08)

    Replace verification code for GnuPG with code that uses --status-fd,
    so that it will work independent of locale.

    Document limitations in the error reporting and recommended setting
    TMPDIR.

PGP::Sign 0.18 (2004-08-04)

    Remove trustdb.gpg from the distribution and add it to the files
    cleaned by make clean.

PGP::Sign 0.17 (2002-06-28)

    Skip the test for verification of data with trailing whitespace when
    run under GnuPG, since the whitespace behavior changes fromr elease to
    release.  GnuPG 1.0.2 is back to the previous behavior of releases
    before GnuPG 1.0.1.

    Update CAVEATS to be slightly less optimistic about the chances of a

MANIFEST  view on Meta::CPAN

docs/metadata/requirements
docs/metadata/test/suffix
lib/PGP/Sign.pm
LICENSE
MANIFEST			This list of files
MANIFEST.SKIP
README
README.md
t/api/basic.t
t/api/errors.t
t/api/gpg1.t
t/api/large-data.t
t/api/whitespace.t
t/data/gnupg1/pubring.gpg
t/data/gnupg1/secring.gpg
t/data/gnupg2/private-keys-v1.d/142395EC50F842F0638500914F0B8C925B12200C.key
t/data/gnupg2/private-keys-v1.d/B02AE5E841A818DC0BBC38F0773CB504F5BF4B00.key
t/data/gnupg2/pubring.kbx
t/data/message
t/data/message.dsa-v3.asc
t/data/message.dsa-v4.asc
t/data/message.rsa-pgp.sig
t/data/message.rsa-v3.asc
t/data/message.rsa-v4.asc
t/data/perl.conf

MANIFEST.SKIP  view on Meta::CPAN

\bcovered\b

# Avoid MYMETA files
^MYMETA\.

# Avoid archives of this distribution
\bPGP-Sign-[\d\.\_]+

# Ignore data generated by the test suite.
^t/data/gnupg1/random_seed$
^t/data/gnupg1/trustdb\.gpg$
^t/data/gnupg2/trustdb\.gpg$

README  view on Meta::CPAN


  PGP::Sign comes with a test suite, which you can run after building
  with:

      ./Build test

  If a test fails, you can run a single test with verbose output via:

      ./Build test --test_files <path-to-test>

  If the gpg binary found first on the PATH is too old, the tests will be
  skipped rather than fail.  This may not always be desirable, since the
  module is not usable on such a system without configuration, but the
  module can still be configured to use a GnuPG binary found elsewhere and
  therefore this doesn't represent an error in the module itself.

  The following additional Perl modules will be used by the test suite if
  present:

  * Devel::Cover
  * Perl::Critic::Freenode

README.md  view on Meta::CPAN

```
    ./Build test
```

If a test fails, you can run a single test with verbose output via:

```
    ./Build test --test_files <path-to-test>
```

If the gpg binary found first on the PATH is too old, the tests will be
skipped rather than fail.  This may not always be desirable, since the
module is not usable on such a system without configuration, but the
module can still be configured to use a GnuPG binary found elsewhere and
therefore this doesn't represent an error in the module itself.

The following additional Perl modules will be used by the test suite if
present:

* Devel::Cover
* Perl::Critic::Freenode

docs/metadata/metadata.json  view on Meta::CPAN

        "broken": true,
    },
    "distribution": {
        "section": "perl",
        "tarname": "PGP-Sign",
        "version": "pgp-sign",
        "cpan": "PGP-Sign",
        "ignore": [
            "^\\.github/",
            "^t/data/gnupg1/random_seed$",
            "^t/data/gnupg./trustdb\\.gpg$",
        ],
    },
    "packaging": {
        "debian": "libpgp-sign-perl",
    },
    "docs": {
        "user": [
            {
                "name": "docs",
                "title": "Module documentation",

docs/metadata/test/suffix  view on Meta::CPAN

If the gpg binary found first on the PATH is too old, the tests will be
skipped rather than fail.  This may not always be desirable, since the
module is not usable on such a system without configuration, but the
module can still be configured to use a GnuPG binary found elsewhere and
therefore this doesn't represent an error in the module itself.

The following additional Perl modules will be used by the test suite if
present:

* Devel::Cover
* Perl::Critic::Freenode

lib/PGP/Sign.pm  view on Meta::CPAN


# Make sure the module returns true.
1;

__DATA__

=for stopwords
Allbery DSS GNUPGHOME GPG GPG1 Gierth Mitzelfelt OpenPGP PGPMoose PGPPATH
TMPDIR canonicalized d'Itri egd keyrings pgpverify ps signcontrol
KEYID --force-v3-sigs --allow-weak-digest-algos --homedir --textmode cleartext
cryptographic gpg gpg1 gpgv homedir interoperable tmpdir

=head1 NAME

PGP::Sign - Create detached PGP signatures for data, securely

=head1 SYNOPSIS

    use PGP::Sign;
    my $keyid = '<some-key-id>';
    my $passphrase = '<passphrase-for-key>';

lib/PGP/Sign.pm  view on Meta::CPAN


If set to a true value, PGP::Sign will strip trailing spaces (only spaces, not
arbitrary whitespace) when signing or verifying signatures.  This will make
the resulting signatures and verification compatible with programs that
generate or verify cleartext signatures, since OpenPGP implementations ignore
trailing spaces when generating or checking cleartext signatures.

=item path

The path to the GnuPG binary to use.  If not set, PGP::Sign defaults to
running B<gpg> (as found on the user's PATH) for a C<style> setting of "GPG"
and B<gpg1> (as found on the user's PATH) for a C<style> setting of "GPG1".

PGP::Sign does not support B<gpgv> (it passes options that it does not
understand).  This parameter should point to a full GnuPG implementation.

=item style

The style of OpenPGP backend to use, chosen from "GPG" for GnuPG v2 (the
default) and "GPG1" for GnuPG v1.

If set to "GPG1", PGP::Sign will pass the command-line flags for maximum
backwards compatibility, including forcing v3 signatures instead of the
current version.  This is interoperable with PGP 2.6.2, at the cost of using

lib/PGP/Sign.pm  view on Meta::CPAN

be either "GPG" for GnuPG v2 or "GPG1" for GnuPG v1.  The default is "GPG".

If set to "GPG1", PGP::Sign will pass the command-line flags for maximum
backwards compatibility, including forcing v3 signatures instead of the
current version.  This is interoperable with PGP 2.6.2, at the cost of using
deprecated protocols and cryptographic algorithms with known weaknesses.

=item $PGP::Sign::PGPS

The path to the program used by pgp_sign().  If not set, PGP::Sign defaults to
running B<gpg> (as found on the user's PATH) if $PGP::Sign::PGPSTYLE is set to
"GPG" and B<gpg1> (as found on the user's PATH) if $PGP::Sign::PGPSTYLE is set
to "GPG1".

=item $PGP::Sign::PGPV

The path to the program used by pgp_verify().  If not set, PGP::Sign defaults
to running B<gpg> (as found on the user's PATH) if $PGP::Sign::PGPSTYLE is set
to "GPG" and B<gpg1> (as found on the user's PATH) if $PGP::Sign::PGPSTYLE is
set to "GPG1".

PGP::Sign does not support B<gpgv> (it passes options that it does not
understand).  This variable should point to a full GnuPG implementation.

=item $PGP::Sign::TMPDIR

The directory in which temporary files are created.  Defaults to whatever
directory File::Temp chooses to use by default.

=back

=head1 ENVIRONMENT

lib/PGP/Sign.pm  view on Meta::CPAN


=head1 COPYRIGHT AND LICENSE

Copyright 1997-2000, 2002, 2004, 2018, 2020 Russ Allbery <rra@cpan.org>

This program is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.

=head1 SEE ALSO

gpg(1), gpg1(1), L<File::Temp>

L<RFC 4880|https://tools.ietf.org/html/rfc4880>, which is the current
specification for the OpenPGP message format.

The current version of PGP::Sign is available from CPAN, or directly from its
web site at L<https://www.eyrie.org/~eagle/software/pgp-sign/>.

=cut

# Local Variables:

t/api/basic.t  view on Meta::CPAN

use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IO::File;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (gpg_is_gpg1()) {
        plan skip_all => 'gpg binary is GnuPG v1';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 2.1.23';
    } else {
        plan tests => 7;
        use_ok('PGP::Sign');
    }
}

# Locate our test data directory for later use.
my $data = 't/data';

# Open and load our data file.  This is the sample data that we'll be signing

t/api/errors.t  view on Meta::CPAN

# SPDX-License-Identifier: GPL-1.0-or-later OR Artistic-1.0-Perl

use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    } else {
        plan tests => 4;
        use_ok('PGP::Sign');
    }
}

# Locate our test data directory for later use.
my $data = 't/data';

# Open and load our data file.  This is the sample data that we'll be signing

t/api/errors.t  view on Meta::CPAN

# A path to a nonexistent binary.
$signer = PGP::Sign->new({ path => '/nonexistent/binary' });
undef $@;
my $signature = eval { $signer->sign($keyid, $passphrase, @data) };
ok($@, 'Bad path to GnuPG binary');

# Verification of a completely invalid signature.
$signer = PGP::Sign->new();
undef $@;
eval { $signer->verify('adfasdfasdf', @data) };
like($@, qr{Execution [ ] of [ ] gpg [ ] failed}xms, 'Invalid signature');

t/api/gpg1.t  view on Meta::CPAN

use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IO::File;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Path to GnuPG v1.
my $PATH;

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    $PATH = 'gpg1';
    if (!can_run('gpg1')) {
        if (gpg_is_gpg1()) {
            $PATH = 'gpg';
        } else {
            plan skip_all => 'gpg1 binary not available';
        }
    }
    if (!gpg_is_new_enough($PATH)) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    }
    plan tests => 7;
    use_ok('PGP::Sign');
}

# Locate our test data directory for later use.
my $data = 't/data';

# Open and load our data file.  This is the sample data that we'll be signing
# and checking signatures against.

t/api/large-data.t  view on Meta::CPAN


use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    } else {
        plan tests => 3;
        use_ok('PGP::Sign');
    }
}

# The key ID and pass phrase to use for testing.
my $keyid      = 'testing';
my $passphrase = 'testing';

# Create the object to use for testing.
my $signer;
if (gpg_is_gpg1()) {
    my $home = File::Spec->catdir('t', 'data', 'gnupg1');
    $signer = PGP::Sign->new(
        {
            home  => $home,
            path  => 'gpg',
            style => 'GPG1',
        },
    );
} else {
    my $home = File::Spec->catdir('t', 'data', 'gnupg2');
    $signer = PGP::Sign->new({ home => $home });
}

# Create a long message to sign.  This is about 1MB.
my $message = ('a' x 76 . "\n") x 13618;

t/api/whitespace.t  view on Meta::CPAN

use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IO::File;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    } else {
        plan tests => 10;
        use_ok('PGP::Sign');
    }
}

# The key ID and pass phrase to use for testing.
my $keyid      = 'testing';
my $passphrase = 'testing';

# Create the objects to use for tests, one without munging enabled and one
# with.
my ($home, $signer, $munged);
if (gpg_is_gpg1()) {
    $home   = File::Spec->catdir('t', 'data', 'gnupg1');
    $signer = PGP::Sign->new(
        {
            home  => $home,
            path  => 'gpg',
            style => 'GPG1',
        },
    );
    $munged = PGP::Sign->new(
        {
            home  => $home,
            path  => 'gpg',
            munge => 1,
            style => 'GPG1',
        },
    );
} else {
    $home   = File::Spec->catdir('t', 'data', 'gnupg2');
    $signer = PGP::Sign->new({ home => $home });
    $munged = PGP::Sign->new({ home => $home, munge => 1 });
}

t/docs/spdx-license.t  view on Meta::CPAN

    qr{ \A [.] / [.] pc/ }xms,                # quilt metadata files
    qr{ \A [.] /_build/ }xms,                 # Module::Build metadata
    qr{ \A [.] /blib/ }xms,                   # Perl build system artifacts
    qr{ \A [.] /cover_db/ }xms,               # Artifacts from coverage testing
    qr{ \A [.] /debian/ }xms,                 # Found in debian/* branches
    qr{ \A [.] /docs/metadata/ }xms,          # Package license should be fine
    qr{ \A [.] /README ( [.] .* )? \z }xms,   # Package license should be fine
    qr{ \A [.] /share/ }xms,                  # Package license should be fine
    qr{ \A [.] /t/data .* /metadata/ }xms,    # Test metadata
    qr{ \A [.] /t/data .* /output/ }xms,      # Test output
    qr{ \A [.] /t/data .* [.] gpg \z }xms,    # Test GnuPG files
    qr{ \A [.] /t/data .* [.] json \z }xms,   # Test metadata
);
## use critic

# Only run this test during automated testing, since failure doesn't indicate
# any user-noticable flaw in the package itself.
skip_unless_automated('SPDX identifier tests');

# Check a single file for an occurrence of the string.
#

t/legacy/basic.t  view on Meta::CPAN

use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IO::File;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    } else {
        use_ok('PGP::Sign');
    }
}

# Locate our test data directory for later use.
my $data = 't/data';

# Open and load our data file.  This is the sample data that we'll be signing
# and checking signatures against.
open(my $fh, '<', "$data/message");
my @data = <$fh>;
close($fh);

# The key ID and pass phrase to use for testing.
my $keyid      = 'testing';
my $passphrase = 'testing';

# There are three possibilities: gpg is GnuPG v1, gpg is GnuPG v2 and v1 is
# not available, or gpg is GnuPG v2 and gpg1 is GnuPG v1.  We ideally want to
# test both styles, but we'll take what we can get.
my @styles;
if (gpg_is_gpg1()) {
    @styles = qw(GPG1);
} elsif (!can_run('gpg1')) {
    @styles = qw(GPG);
} else {
    @styles = qw(GPG GPG1);
}

# Run all the tests twice, once with GnuPG v2 and then with GnuPG v1.
for my $style (@styles) {
    note("Testing PGPSTYLE $style");
    local $PGP::Sign::PGPSTYLE = $style;
    my $pgpdir = ($style eq 'GPG') ? 'gnupg2' : 'gnupg1';
    local $PGP::Sign::PGPPATH = File::Spec->catdir($data, $pgpdir);
    if ($style eq 'GPG1' && gpg_is_gpg1()) {
        $PGP::Sign::PGPS = 'gpg';
        $PGP::Sign::PGPV = 'gpg';
    }

    # Generate a signature.
    my ($signature, $version) = pgp_sign($keyid, $passphrase, @data);
    ok($signature, 'Sign');
    is(PGP::Sign::pgp_error(), q{}, '...with no errors');
    isnt($signature, undef, 'Signature');
    is(PGP::Sign::pgp_error(), q{}, '...with no errors');

    # Check signature.

t/legacy/basic.t  view on Meta::CPAN

        is(pgp_verify($signature, $version, q{  }),
            $keyid, '...and does match munged');
    }

    # Test error handling.
    is(pgp_verify('asfasdfasf', undef, @data), undef, 'Invalid signature');
    my @errors = PGP::Sign::pgp_error();
    my $errors = PGP::Sign::pgp_error();
    like(
        $errors[-1],
        qr{^ Execution [ ] of [ ] gpg.? [ ] failed}xms,
        'Invalid signature',
    );
    like(
        $errors,
        qr{\n Execution [ ] of [ ] gpg.? [ ] failed}xms,
        'Errors contain newlines',
    );
    is($errors, join(q{}, @errors), 'Two presentations of errors match');
}

# Report the end of testing.
done_testing(21 * scalar(@styles) + 1);

t/legacy/locale.t  view on Meta::CPAN


use 5.020;
use autodie;
use warnings;

use lib 't/lib';

use File::Spec;
use IPC::Cmd qw(can_run);
use Test::More;
use Test::PGP qw(gpg_is_gpg1 gpg_is_new_enough);

# Check that GnuPG is available.  If so, load the module and set the plan.
BEGIN {
    if (!can_run('gpg')) {
        plan skip_all => 'gpg binary not available';
    } elsif (!gpg_is_new_enough('gpg')) {
        plan skip_all => 'gpg binary is older than 1.4.20 or 2.1.23';
    } else {
        plan tests => 5;
        use_ok('PGP::Sign', qw(pgp_sign pgp_verify pgp_error));
    }
}

# Set the locale.  I use French for testing; this won't be a proper test
# unless the locale is available on the local system, so hopefully this will
# be a common one.
local $ENV{LC_ALL} = 'fr_FR';

# Locate our test data directory for later use.
my $data = 't/data';
if (gpg_is_gpg1()) {
    $PGP::Sign::PGPSTYLE = 'GPG1';
    $PGP::Sign::PGPPATH  = File::Spec->catdir($data, 'gnupg1');
    $PGP::Sign::PGPS     = 'gpg';
    $PGP::Sign::PGPV     = 'gpg';
} else {
    $PGP::Sign::PGPPATH = File::Spec->catdir($data, 'gnupg2');
}

# Open and load our data file.  This is the sample data that we'll be signing
# and checking signatures against.
open(my $fh, '<', "$data/message");
my @data = <$fh>;
close($fh);

t/lib/Test/PGP.pm  view on Meta::CPAN

package Test::PGP 1.00;

use 5.020;
use autodie;
use version;
use warnings;

use Exporter qw(import);
use IPC::Cmd qw(run);

our @EXPORT_OK = qw(gpg_is_gpg1 gpg_is_new_enough);

# Test if the gpg binary found first on PATH is actually gpg1.
#
# Returns: 1 if so, undef if not or on any errors
sub gpg_is_gpg1 {
    my $output;
    if (!run(command => ['gpg', '--version'], buffer => \$output)) {
        return;
    }
    return $output =~ m{ ^ gpg [^\n]* \s 1 [.] }xms;
}

# Test if the GnuPG binary is new enough to have the flags we expect.
#
# $path - Path to the GnuPG binary to test
#
# Returns: 1 if so, undef if not or on any errors
sub gpg_is_new_enough {
    my ($path) = @_;
    my $output;
    if (!run(command => [$path, '--version'], buffer => \$output)) {
        return;
    }
    if ($output =~ m{ ^ gpg [^\n]* \s (2 [.\d]+) }xms) {
        my $version = $1;
        return version->parse($version) >= version->parse('2.1.23');
    } elsif ($output =~ m{ ^ gpg [^\n]* \s (1 [.\d]+) }xms) {
        my $version = $1;
        return version->parse($version) >= version->parse('1.4.20');
    } else {
        warn "Cannot determine version of $path\n";
        return;
    }
}

1;



( run in 0.962 second using v1.01-cache-2.11-cpan-df04353d9ac )