File Coverage

blib/lib/App/GHGen/PerlCustomizer.pm
Criterion Covered Total %
statement 161 167 96.4
branch 7 16 43.7
condition 11 23 47.8
subroutine 9 9 100.0
pod 2 2 100.0
total 190 217 87.5


line stmt bran cond sub pod time code
1             package App::GHGen::PerlCustomizer;
2              
3 1     1   21 use v5.36;
  1         4  
4 1     1   7 use strict;
  1         2  
  1         25  
5 1     1   2 use warnings;
  1         2  
  1         65  
6              
7 1     1   4 use Path::Tiny;
  1         1  
  1         46  
8              
9 1     1   4 use Exporter 'import';
  1         2  
  1         1491  
10             our @EXPORT_OK = qw(
11             detect_perl_requirements
12             generate_custom_perl_workflow
13             );
14              
15             our $VERSION = '0.03';
16              
17             =head1 NAME
18              
19             App::GHGen::PerlCustomizer - Customize Perl workflows based on project requirements
20              
21             =head1 SYNOPSIS
22              
23             use App::GHGen::PerlCustomizer qw(detect_perl_requirements);
24              
25             my $requirements = detect_perl_requirements();
26             # Returns: { min_version => '5.036', has_cpanfile => 1, ... }
27              
28             =head1 FUNCTIONS
29              
30             =head2 detect_perl_requirements()
31              
32             Detect Perl version requirements from cpanfile, Makefile.PL, or dist.ini.
33              
34             =cut
35              
36 1     1 1 2 sub detect_perl_requirements() {
  1         1  
37 1         8 my %reqs = (
38             min_version => undef,
39             has_cpanfile => 0,
40             has_makefile_pl => 0,
41             has_dist_ini => 0,
42             has_build_pl => 0,
43             );
44              
45             # Check for dependency files
46 1         7 $reqs{has_cpanfile} = path('cpanfile')->exists;
47 1         189 $reqs{has_makefile_pl} = path('Makefile.PL')->exists;
48 1         60 $reqs{has_dist_ini} = path('dist.ini')->exists;
49 1         76 $reqs{has_build_pl} = path('Build.PL')->exists;
50              
51             # Try to detect minimum Perl version
52 1 50       45 if ($reqs{has_cpanfile}) {
53 1         5 my $content = path('cpanfile')->slurp_utf8;
54 1 50       2468 if ($content =~ /requires\s+['"]perl['"],?\s+['"]([0-9.]+)['"]/) {
55 1         6 $reqs{min_version} = $1;
56             }
57             }
58              
59 1 0 33     5 if (!$reqs{min_version} && $reqs{has_makefile_pl}) {
60 0         0 my $content = path('Makefile.PL')->slurp_utf8;
61 0 0       0 if ($content =~ /MIN_PERL_VERSION\s*=>\s*['"]([0-9.]+)['"]/) {
62 0         0 $reqs{min_version} = $1;
63             }
64             }
65              
66 1         6 return \%reqs;
67             }
68              
69             =head2 generate_custom_perl_workflow($options)
70              
71             Generate a customized Perl workflow based on options hash.
72              
73             Options:
74             - perl_versions: Array ref of explicit Perl versions (e.g., ['5.40', '5.38'])
75             - min_perl_version: Minimum Perl version (e.g., '5.036')
76             - max_perl_version: Maximum Perl version to test (e.g., '5.40')
77             - os: Array ref of operating systems ['ubuntu', 'macos', 'windows']
78             - enable_critic: Boolean
79             - enable_coverage: Boolean
80              
81             If perl_versions is provided, it takes precedence over min/max versions.
82              
83             =cut
84              
85 1     1 1 2 sub generate_custom_perl_workflow($opts = {}) {
  1         3  
  1         1  
86 1   50     5 my $min_version = $opts->{min_perl_version} // '5.36';
87 1   50     3 my $max_version = $opts->{max_perl_version} // '5.40';
88 1   50     1 my @os = @{$opts->{os} // ['ubuntu-latest', 'macos-latest', 'windows-latest']};
  1         7  
89 1   50     4 my $enable_critic = $opts->{enable_critic} // 1;
90 1   50     3 my $enable_coverage = $opts->{enable_coverage} // 1;
91              
92             # Generate Perl version list - use explicit list if provided, otherwise min/max
93 1         2 my @perl_versions;
94 1 50 33     6 if ($opts->{perl_versions} && @{$opts->{perl_versions}}) {
  0         0  
95 0         0 @perl_versions = @{$opts->{perl_versions}};
  0         0  
96             } else {
97 1         4 @perl_versions = _get_perl_versions($min_version, $max_version);
98             }
99              
100 1         2 my $yaml = "---\n";
101 1         3 $yaml .= '# Created by ' . __PACKAGE__ . "\n";
102              
103 1         2 $yaml .= "name: Perl CI\n\n";
104 1         3 $yaml .= "'on':\n";
105 1         1 $yaml .= " push:\n";
106 1         3 $yaml .= " branches:\n";
107 1         1 $yaml .= " - main\n";
108 1         24 $yaml .= " - master\n";
109 1         2 $yaml .= " pull_request:\n";
110 1         1 $yaml .= " branches:\n";
111 1         2 $yaml .= " - main\n";
112 1         1 $yaml .= " - master\n\n";
113              
114 1         2 $yaml .= "concurrency:\n";
115 1         1 $yaml .= " group: \${{ github.workflow }}-\${{ github.ref }}\n";
116 1         2 $yaml .= " cancel-in-progress: true\n\n";
117              
118 1         2 $yaml .= "permissions:\n";
119 1         1 $yaml .= " contents: read\n\n";
120              
121 1         1 $yaml .= "jobs:\n";
122 1         1 $yaml .= " test:\n";
123 1         2 $yaml .= " runs-on: \${{ matrix.os }}\n";
124 1         2 $yaml .= " strategy:\n";
125 1         1 $yaml .= " fail-fast: false\n";
126 1         2 $yaml .= " matrix:\n";
127 1         2 $yaml .= " os:\n";
128 1         2 for my $os (@os) {
129 3         5 $yaml .= " - $os\n";
130             }
131 1         1 $yaml .= " perl:\n";
132 1         2 for my $version (@perl_versions) {
133 3         4 $yaml .= " - '$version'\n";
134             }
135 1         2 $yaml .= " name: Perl \${{ matrix.perl }} on \${{ matrix.os }}\n";
136 1         2 $yaml .= " env:\n";
137 1         16 $yaml .= " AUTOMATED_TESTING: 1\n";
138 1         2 $yaml .= " NO_NETWORK_TESTING: 1\n";
139 1         7 $yaml .= " NONINTERACTIVE_TESTING: 1\n";
140 1         2 $yaml .= " steps:\n";
141 1         1 $yaml .= " - uses: actions/checkout\@v6\n\n";
142              
143 1         2 $yaml .= " - name: Setup Perl\n";
144 1         1 $yaml .= " uses: shogo82148/actions-setup-perl\@v1\n";
145 1         5 $yaml .= " with:\n";
146 1         2 $yaml .= " perl-version: \${{ matrix.perl }}\n\n";
147              
148 1         1 $yaml .= " - name: Cache CPAN modules\n";
149 1         2 $yaml .= " uses: actions/cache\@v5\n";
150 1         1 $yaml .= " with:\n";
151 1         2 $yaml .= " path: ~/perl5\n";
152 1         12 $yaml .= " key: \${{ runner.os }}-\${{ matrix.perl }}-\${{ hashFiles('cpanfile') }}\n";
153 1         1 $yaml .= " restore-keys: |\n";
154 1         2 $yaml .= " \${{ runner.os }}-\${{ matrix.perl }}-\n\n";
155              
156 1         2 $yaml .= " - name: Install cpanm and local::lib\n";
157 1         2 $yaml .= " if: runner.os != 'Windows'\n";
158 1         2 $yaml .= " run: cpanm --notest --local-lib=~/perl5 local::lib\n\n";
159              
160 1         3 $yaml .= " - name: Install cpanm and local::lib (Windows)\n";
161 1         2 $yaml .= " if: runner.os == 'Windows'\n";
162 1         1 $yaml .= " run: cpanm --notest App::cpanminus local::lib\n\n";
163              
164 1         1 $yaml .= " - name: Install dependencies\n";
165 1         2 $yaml .= " if: runner.os != 'Windows'\n";
166 1         2 $yaml .= " shell: bash\n";
167 1         1 $yaml .= " run: |\n";
168 1         6 $yaml .= " eval \$(perl -I ~/perl5/lib/perl5 -Mlocal::lib)\n";
169 1         2 $yaml .= " cpanm --notest --installdeps .\n\n";
170              
171 1         2 $yaml .= " - name: Install dependencies (Windows)\n";
172 1         1 $yaml .= " if: runner.os == 'Windows'\n";
173 1         1 $yaml .= " shell: cmd\n";
174 1         2 $yaml .= " run: |\n";
175 1         1 $yaml .= " \@echo off\n";
176 1         1 $yaml .= " set \"PATH=%USERPROFILE%\\perl5\\bin;%PATH%\"\n";
177 1         1 $yaml .= " set \"PERL5LIB=%USERPROFILE%\\perl5\\lib\\perl5\"\n";
178 1         2 $yaml .= " cpanm --notest --installdeps .\n\n";
179              
180 1         1 $yaml .= " - name: Run tests\n";
181 1         6 $yaml .= " if: runner.os != 'Windows'\n";
182 1         2 $yaml .= " shell: bash\n";
183 1         1 $yaml .= " run: |\n";
184 1         1 $yaml .= " eval \$(perl -I ~/perl5/lib/perl5 -Mlocal::lib)\n";
185 1         1 $yaml .= " prove -lr t/\n\n";
186              
187 1         2 $yaml .= " - name: Run tests (Windows)\n";
188 1         2 $yaml .= " if: runner.os == 'Windows'\n";
189 1         2 $yaml .= " shell: cmd\n";
190 1         1 $yaml .= " run: |\n";
191 1         1 $yaml .= " \@echo off\n";
192 1         2 $yaml .= " set \"PATH=%USERPROFILE%\\perl5\\bin;%PATH%\"\n";
193 1         2 $yaml .= " set \"PERL5LIB=%USERPROFILE%\\perl5\\lib\\perl5\"\n";
194 1         3 $yaml .= " prove -lr t/\n\n";
195              
196 1 50       8 if ($enable_critic) {
197 1         4 my $latest = $perl_versions[-1];
198 1         2 $yaml .= " - name: Run Perl::Critic\n";
199 1         13 $yaml .= " if: matrix.perl == '$latest' && matrix.os == 'ubuntu-latest'\n";
200 1         3 $yaml .= " continue-on-error: true\n";
201 1         2 $yaml .= " run: |\n";
202 1         2 $yaml .= " eval \$(perl -I ~/perl5/lib/perl5 -Mlocal::lib)\n";
203 1         4 $yaml .= " cpanm --notest Perl::Critic\n";
204 1         2 $yaml .= " perlcritic --severity 3 lib/ || true\n";
205 1         4 $yaml .= " shell: bash\n\n";
206             }
207              
208 1 50       5 if ($enable_coverage) {
209 1         2 my $latest = $perl_versions[-1];
210 1         3 $yaml .= " - name: Test coverage\n";
211 1         3 $yaml .= " if: matrix.perl == '$latest' && matrix.os == 'ubuntu-latest'\n";
212 1         2 $yaml .= " run: |\n";
213 1         2 $yaml .= " eval \$(perl -I ~/perl5/lib/perl5 -Mlocal::lib)\n";
214 1         3 $yaml .= " cpanm --notest Devel::Cover\n";
215 1         2 $yaml .= " cover -delete\n";
216 1         2 $yaml .= " HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lr t/\n";
217 1         2 $yaml .= " cover\n";
218 1         3 $yaml .= " shell: bash\n";
219             }
220              
221 1         25 return $yaml;
222             }
223              
224 1     1   2 sub _get_perl_versions($min, $max) {
  1         2  
  1         2  
  1         2  
225             # All available Perl versions in descending order
226 1         5 my @all_versions = qw(5.40 5.38 5.36 5.34 5.32 5.30 5.28 5.26 5.24 5.22);
227              
228             # Normalize version strings for comparison
229 1         5 my $min_normalized = _normalize_version($min);
230 1         5 my $max_normalized = _normalize_version($max);
231              
232 1         4 my @selected;
233 1         4 for my $version (@all_versions) {
234 10         16 my $v_normalized = _normalize_version($version);
235 10 100 66     45 if ($v_normalized >= $min_normalized && $v_normalized <= $max_normalized) {
236 3         9 push @selected, $version;
237             }
238             }
239              
240 1         5 return reverse @selected; # Return in ascending order
241             }
242              
243 12     12   15 sub _normalize_version($version) {
  12         17  
  12         15  
244             # Convert "5.036" or "5.36" to comparable number
245 12         42 $version =~ s/^v?//;
246 12         33 my @parts = split /\./, $version;
247 12   50     107 return sprintf("%d.%03d", $parts[0] // 5, $parts[1] // 0);
      50        
248             }
249              
250             =head1 AUTHOR
251              
252             Nigel Horne Enjh@nigelhorne.comE
253              
254             L
255              
256             =head1 LICENSE
257              
258             This is free software; you can redistribute it and/or modify it under
259             the same terms as the Perl 5 programming language system itself.
260              
261             =cut
262              
263             1;