NAME

App::GHGen::PerlCustomizer - Customize Perl workflows based on project requirements

SYNOPSIS

use App::GHGen::PerlCustomizer qw(detect_perl_requirements);

my $requirements = detect_perl_requirements();
# Returns: { min_version => '5.036', has_cpanfile => 1, ... }

FUNCTIONS

detect_perl_requirements()

Detect Perl version requirements and dependency-file presence in the current directory.

Purpose

Inspect the current working directory for common Perl distribution files (cpanfile, Makefile.PL, dist.ini, Build.PL) and extract the minimum Perl version declared in any of them.

Arguments

None.

Returns

A hash reference with the following keys, all of which are always present:

{
    min_version    => Str | undef,  # e.g. '5.036'; undef when undetected
    has_cpanfile   => Bool,
    has_makefile_pl => Bool,
    has_dist_ini   => Bool,
    has_build_pl   => Bool,
}

Side Effects

Reads files from the current working directory.

Usage Example

use App::GHGen::PerlCustomizer qw(detect_perl_requirements);
my $reqs = detect_perl_requirements();
say $reqs->{min_version} // 'not specified';

API SPECIFICATION

Input

# No parameters.

Output

{
    type => 'hashref',
    keys => {
        min_version     => { type => 'scalar', optional => 1 },
        has_cpanfile    => { type => 'scalar' },
        has_makefile_pl => { type => 'scalar' },
        has_dist_ini    => { type => 'scalar' },
        has_build_pl    => { type => 'scalar' },
    },
}

FORMAL SPECIFICATION

detect_perl_requirements : → Requirements

Requirements ≔ {
    min_version:     ℤ* ∪ { ⊥ },
    has_cpanfile:    𝔹,
    has_makefile_pl: 𝔹,
    has_dist_ini:    𝔹,
    has_build_pl:    𝔹,
}

min_version ≔
    cpanfile exists ∧ version parseable from cpanfile  → version
    Makefile.PL exists ∧ MIN_PERL_VERSION parseable   → version
    otherwise                                           → ⊥

generate_custom_perl_workflow($options)

Generate a customized Perl CI workflow and return it as a YAML string.

Options:

perl_versions (array ref)

Explicit list of Perl versions to include in the test matrix, e.g. ['5.40', '5.38', '5.36']. Takes precedence over min_perl_version and max_perl_version when supplied.

min_perl_version (string, default '5.36')

Lowest Perl version to include in the matrix when perl_versions is not given. Accepts both '5.036' and '5.36' notation.

max_perl_version (string, default '5.42')

Highest Perl version to include in the default matrix when perl_versions is not given. The built-in version table currently spans 5.22 through 5.44; the default produces the matrix 5.36, 5.38, 5.40, 5.42.

To test against Perl 5.44 (opt-in, not tested by default), pass max_perl_version => '5.44' or set perl_versions explicitly:

generate_custom_perl_workflow({ max_perl_version => '5.44' });
# matrix: 5.36, 5.38, 5.40, 5.42, 5.44

generate_custom_perl_workflow({ perl_versions => ['5.42', '5.44'] });
# matrix: 5.42, 5.44
os (array ref)

Operating systems for the build matrix. Default: ['ubuntu-latest', 'macos-latest', 'windows-latest'].

timeout (integer, default 30)

Value written to timeout-minutes on every matrix job.

enable_linter (boolean, default 1)

When true, inserts a "Lint and syntax check" step after all dependency installation steps and before the test run. The step runs on every matrix cell (all OS and Perl version combinations) so that compile-time errors are caught across the full version range being tested.

The step uses shell: perl {0}, which works identically on Linux, macOS, and Windows without any OS-specific branching. It searches lib/ and bin/ (falling back to . when neither exists), loads each .pm file via do $file with a stub @INC handler that silently satisfies any missing use statements, and exits non-zero if any file fails. Using do $file avoids spawning perl -c subprocesses (which have Windows command-line quoting issues) and avoids false failures caused by missing CPAN dependencies. No additional CPAN modules are required.

enable_linter_unused (boolean, default 1)

When true, appends an unused-variable check to the end of the lint step (i.e. it runs before the test run, not after). The check installs warnings::unused from CPAN, then runs PERL5OPT=-Mwarnings::unused prove -lr t/ so that variable lifetimes are exercised at runtime (perl -c is compile-only and cannot detect unused variables). It is gated on RUNNER_OS == Linux and marked continue-on-error: true because unused-variable warnings can be legitimately noisy on some codebases.

enable_critic (boolean, default 1)

When true, adds a Perl::Critic step on the latest matrix Perl version and ubuntu-latest.

enable_coverage (boolean, default 1)

When true, adds a Devel::Cover test-coverage step on the latest matrix Perl version and ubuntu-latest.

enable_perlimports (boolean, default 1)

When true, adds a "Check imports with perlimports" step on the latest matrix Perl version and ubuntu-latest. The step installs App::perlimports from CPAN and runs perlimports --lint across all .pm files under lib/. It is marked continue-on-error: true because import hygiene warnings are advisory; they should not block a CI run in established codebases.

Generated step order:

1. actions/checkout
2. Setup Perl via shogo82148/actions-setup-perl
3. Cache CPAN modules via actions/cache
4. Install cpanm and local::lib
5. Install project dependencies
6. Lint and syntax check — all matrix cells (when enable_linter is true); the unused-variable check via PERL5OPT=-Mwarnings::unused is embedded at the end of this step when enable_linter_unused is true
7. Run tests
8. Run Perl::Critic — latest Perl + Ubuntu only (when enable_critic is true)
9. Check imports with perlimports — latest Perl + Ubuntu only (when enable_perlimports is true)
10. Test coverage — latest Perl + Ubuntu only (when enable_coverage is true)
11. Show cpanm build log on failure

API SPECIFICATION

Input

{
    opts => {
        type     => 'hashref',
        default  => {},
        keys     => {
            perl_versions        => { type => 'arrayref', optional => 1 },
            min_perl_version     => { type => 'scalar',  default  => '5.36' },
            max_perl_version     => { type => 'scalar',  default  => '5.42' },
            os                   => { type => 'arrayref', optional => 1 },
            timeout              => { type => 'scalar',  default  => 30 },
            enable_linter        => { type => 'scalar',  default  => 1 },
            enable_linter_unused => { type => 'scalar',  default  => 1 },
            enable_critic        => { type => 'scalar',  default  => 1 },
            enable_coverage      => { type => 'scalar',  default  => 1 },
            enable_perlimports   => { type => 'scalar',  default  => 1 },
        },
    },
}

Output

{ type => 'scalar' }   # multi-line YAML string starting with '---'

FORMAL SPECIFICATION

generate_custom_perl_workflow : Opts → YAML

versions ≔ opts.perl_versions ?? range(min, max)
latest   ≔ versions[|versions|−1]

yaml contains "Lint and syntax check" step       ↔  opts.enable_linter = 1
yaml contains "PERL5OPT=-Mwarnings::unused"      ↔  opts.enable_linter_unused = 1
yaml contains "Run Perl::Critic"                 ↔  opts.enable_critic = 1
yaml contains "Test coverage"                    ↔  opts.enable_coverage = 1
yaml contains "Check imports with perlimports"   ↔  opts.enable_perlimports = 1

step ordering invariant:
  pos(lint) < pos(unused) < pos(tests) < pos(critic) < pos(perlimports) < pos(coverage)

LIMITATIONS

Windows + Perl 5.40 excluded from the generated matrix

The shogo82148 perl-5.40.4-thr-win32-x64.zip distribution ships a perl.exe and a libperl540.a from two different builds. Any XS module compiled against that libperl540.a receives handshake key 0x12c00080, but perl.exe needs 0x12d00080. Even Win32::Process — a core Windows XS module bundled in the zip and loaded transitively by IPC::System::Simple — fails to load with this mismatch, so no XS-using test can pass on this combination.

Recompiling XS modules (e.g. via cpanm --reinstall YAML::XS) does not help: the compilation itself links against the broken libperl540.a and inherits the wrong key.

The generated workflow therefore includes a matrix.exclude entry for {os: windows-latest, perl: '5.40'}. This entry is only emitted when both windows-latest and 5.40 are present in the matrix, so workflows that restrict their OS or Perl lists are unaffected.

Remove the exclusion once the upstream shogo82148/build-perl distribution is fixed and the perl.exe and libperl540.a in the 5.40.x zip are built from the same source tree.

See https://github.com/shogo82148/actions-setup-perl/issues/2310 for further information.

AUTHOR

Nigel Horne <njh@nigelhorne.com>

https://github.com/nigelhorne

LICENSE

Usage is subject to the GPL2 licence terms. If you use it, please let me know.