File Coverage

blib/lib/App/ModuleBuildTiny/Dist.pm
Criterion Covered Total %
statement 62 374 16.5
branch 0 144 0.0
condition 0 103 0.0
subroutine 21 53 39.6
pod 0 24 0.0
total 83 698 11.8


line stmt bran cond sub pod time code
1             package App::ModuleBuildTiny::Dist;
2              
3 1     1   28 use 5.014;
  1         4  
4 1     1   6 use warnings;
  1         1  
  1         72  
5             our $VERSION = '0.051';
6              
7 1     1   7 use CPAN::Meta;
  1         2  
  1         64  
8 1     1   6 use Config;
  1         2  
  1         61  
9 1     1   5 use Encode qw/encode_utf8 decode_utf8/;
  1         3  
  1         60  
10 1     1   6 use File::Basename qw/basename dirname/;
  1         2  
  1         71  
11 1     1   5 use File::Copy qw/copy/;
  1         3  
  1         42  
12 1     1   13 use File::Path qw/mkpath rmtree/;
  1         2  
  1         51  
13 1     1   5 use File::Spec::Functions qw/catfile catdir rel2abs/;
  1         2  
  1         60  
14 1     1   7 use File::Slurper qw/write_text read_text read_binary/;
  1         2  
  1         60  
15 1     1   619 use File::chdir;
  1         2346  
  1         149  
16 1     1   8 use ExtUtils::Manifest qw/manifind maniskip maniread/;
  1         2  
  1         103  
17 1     1   15 use Module::Runtime 'require_module';
  1         2  
  1         8  
18 1     1   738 use Module::Metadata 1.000037;
  1         11211  
  1         56  
19 1     1   704 use Pod::Escapes qw/e2char/;
  1         4558  
  1         131  
20 1     1   1259 use Pod::Simple::Text 3.23;
  1         54058  
  1         49  
21 1     1   634 use POSIX 'strftime';
  1         8581  
  1         7  
22 1     1   2693 use Text::ParseWords 'shellwords';
  1         2033  
  1         83  
23              
24 1     1   1547 use Env qw/@PERL5LIB @PATH/;
  1         3610  
  1         7  
25              
26             my $Build = $^O eq 'MSWin32' ? 'Build' : './Build';
27              
28             sub find {
29 0     0 0   my ($re, @dir) = @_;
30 0           my $ret;
31 0 0   0     File::Find::find(sub { $ret++ if /$re/ }, @dir);
  0            
32 0           return $ret;
33             }
34              
35             sub mbt_version {
36 0 0   0 0   return -f 'dynamic-prereqs.yml' ? '0.048' : '0.044';
37             }
38              
39             sub prereqs_for {
40 0     0 0   my ($meta, $phase, $type, $module, $default) = @_;
41 0   0       return $meta->effective_prereqs->requirements_for($phase, $type)->requirements_for_module($module) // $default // 0;
      0        
42             }
43              
44             sub uptodate {
45 0     0 0   my ($destination, @source) = @_;
46 0 0         return if not -e $destination;
47 0 0         for my $source (grep { defined && -e } @source) {
  0            
48 0 0         return if -M $destination > -M $source;
49             }
50 0           return 1;
51             }
52              
53             sub distfilename {
54 0     0 0   my $distname = shift;
55 0           return catfile('lib', split /-/, $distname) . '.pm';
56             }
57              
58             sub generate_readme {
59 0     0 0   my $distname = shift;
60 0           my $filename = distfilename($distname);
61 0 0         die "Main module file $filename doesn't exist\n" if not -f $filename;
62 0           my $parser = Pod::Simple::Text->new;
63 0           $parser->output_string( \my $content );
64 0           $parser->parse_characters(1);
65 0           $parser->parse_file($filename);
66 0           return decode_utf8($content);
67             }
68              
69             sub load_jsonyaml {
70 0     0 0   my $file = shift;
71 0           require Parse::CPAN::Meta;
72 0           return Parse::CPAN::Meta->load_file($file);
73             }
74              
75             sub load_mergedata {
76 0     0 0   my $mergefile = shift;
77 0 0 0       if (defined $mergefile and -r $mergefile) {
78 0           return load_jsonyaml($mergefile);
79             }
80 0           return;
81             }
82              
83             sub distname {
84 0     0 0   my $extra = shift;
85 0 0         return delete $extra->{name} if defined $extra->{name};
86 0           return basename(rel2abs('.')) =~ s/ (?: ^ (?: perl|p5 ) - | [\-\.]pm $ )//xr;
87             }
88              
89             sub detect_license {
90 0     0 0   my ($data, $filename, $authors, $mergedata) = @_;
91 0 0 0       if ($mergedata->{license} && @{$mergedata->{license}} == 1) {
  0            
92 0           require Software::LicenseUtils;
93 0           Software::LicenseUtils->VERSION(0.103014);
94 0 0 0       my $spec_version = $mergedata->{'meta-spec'} && $mergedata->{'meta-spec'}{version} ? $mergedata->{'meta-spec'}{version} : 2;
95 0           my @guess = Software::LicenseUtils->guess_license_from_meta_key($mergedata->{license}[0], $spec_version);
96 0 0         die "Couldn't parse license from metamerge: @guess\n" if @guess > 1;
97 0 0         if (@guess) {
98 0           my $class = $guess[0];
99 0           require_module($class);
100 0           return $class->new({holder => join(', ', @{$authors})});
  0            
101             }
102             }
103 0           my (@license_sections) = grep { /licen[cs]e|licensing|copyright|legal|authors?\b/i } $data->pod_inside;
  0            
104 0           for my $license_section (@license_sections) {
105 0 0         next unless defined ( my $license_pod = $data->pod($license_section) );
106 0           require Software::LicenseUtils;
107 0           Software::LicenseUtils->VERSION(0.103014);
108 0           my $content = "=head1 LICENSE\n" . $license_pod;
109 0           my @guess = Software::LicenseUtils->guess_license_from_pod($content);
110 0 0         next if not @guess;
111 0 0         die "Couldn't parse license from $license_section in $filename: @guess\n" if @guess != 1;
112 0           my $class = $guess[0];
113 0           my ($year) = $license_pod =~ /.*? copyright .*? ([\d\-]+)/;
114 0           require_module($class);
115 0           return $class->new({holder => join(', ', @{$authors}), year => $year});
  0            
116             }
117 0           die "No license found in $filename\n";
118             }
119              
120             sub get_changes {
121 0     0 0   my $self = shift;
122 0           my $version = quotemeta $self->meta->version;
123 0 0         open my $changes, '<:raw', 'Changes' or die "Couldn't open Changes file";
124 0           my (undef, @content) = grep { / ^ $version (?:-TRIAL)? (?:\s+|$) /x ... /^\S/ } <$changes>;
  0            
125 0   0       pop @content while @content && $content[-1] =~ / ^ (?: \S | \s* $ ) /x;
126 0           return @content;
127             }
128              
129             sub preflight_check {
130 0     0 0   my ($self, %opts) = @_;
131              
132 0 0         die "Changes appears to be empty\n" if not $self->get_changes;
133              
134 0           my $meta_version = $self->{meta}->version;
135 0 0         die "Version is still zero\n" if $meta_version eq '0.000';
136              
137 0 0         die "Abstract is not set\n" if $self->{meta}->abstract eq 'INSERT YOUR ABSTRACT HERE';
138              
139 0 0         if ($opts{tag}) {
140 0           require Git::Wrapper;
141 0           my $git = Git::Wrapper->new('.');
142              
143 0 0         die "Dirty state in repository\n" if $git->status->is_dirty;
144 0 0         die "Tag v$meta_version already exists\n" if eval { $git->rev_parse({ quiet => 1, verify => 1}, "v$meta_version") };
  0            
145             }
146              
147 0           my $module_name = $self->{meta}->name =~ s/-/::/gr;
148 0           my $detected_version = $self->{data}->version($module_name);
149 0 0         die sprintf "Version mismatch between module and meta, did you forgot to run regenerate? (%s versus %s)\n", $detected_version, $meta_version if $detected_version != $meta_version;
150             }
151              
152             sub scan_files {
153 0     0 0   my ($files, $omit, %extra_args) = @_;
154 0           my $combined = CPAN::Meta::Requirements->new;
155 0           require Perl::PrereqScanner;
156 0           my $scanner = Perl::PrereqScanner->new(\%extra_args);
157 0           for my $file (@{$files}) {
  0            
158 0           my $prereqs = $scanner->scan_file($file);
159 0           $combined->add_requirements($prereqs);
160             }
161 0           $combined->clear_requirement($_) for @{$omit};
  0            
162 0           return $combined
163             }
164              
165             sub _scan_prereqs {
166 0     0     my ($omit, %opts) = @_;
167 0           my (@runtime_files, @test_files, @planner_files);
168 0 0 0 0     File::Find::find(sub { push @runtime_files, $File::Find::name if -f && /\.pm$/ }, 'lib') if -d 'lib';
  0 0          
169 0 0   0     File::Find::find(sub { push @runtime_files, $File::Find::name if -f }, 'script') if -d 'script';
  0 0          
170 0 0 0 0     File::Find::find(sub { push @test_files, $File::Find::name if -f && /\.(t|pm)$/ }, 't') if -d 't';
  0 0          
171              
172 0           my $runtime = scan_files(\@runtime_files, $omit);
173 0           my $test = scan_files(\@test_files, $omit);
174              
175 0           my %prereqs = (
176             runtime => { requires => $runtime->as_string_hash },
177             test => { requires => $test->as_string_hash },
178             );
179              
180 0 0         if (-d 'planner') {
181 0 0 0 0     File::Find::find(sub { push @planner_files, $File::Find::name if -f && /\.pl$/ }, 'planner');
  0            
182 0           require Perl::PrereqScanner::Scanner::DistBuild;
183 0           my $configure = scan_files(\@planner_files, ['strict', 'warnings', @{$omit}], extra_scanners => [ 'DistBuild' ]);
  0            
184 0           $configure->add_minimum('Dist::Build' => '0.003');
185 0           $prereqs{configure} = { requires => $configure->as_string_hash };
186             } else {
187 0           $prereqs{configure} = { requires => { 'Module::Build::Tiny' => mbt_version() } };
188             }
189              
190 0           my $prereqs = CPAN::Meta::Prereqs->new(\%prereqs);
191 0           require CPAN::Meta::Prereqs::Filter;
192 0           return CPAN::Meta::Prereqs::Filter::filter_prereqs($prereqs, %opts);
193             }
194              
195              
196             sub scan_prereqs {
197 0     0 0   my ($self, %opts) = @_;
198 0   0       my @omit = (@{ $opts{omit} // [] }, keys %{ $self->{meta}->provides });
  0            
  0            
199 0           return _scan_prereqs(\@omit, %opts);
200             }
201              
202             sub load_prereqs {
203 0     0 0   my ($provides, %opts) = @_;
204 0           my @prereqs;
205 0 0         if (-f 'prereqs.json') {
206 0           push @prereqs, load_jsonyaml('prereqs.json');
207             }
208 0 0         if (-f 'prereqs.yml') {
209 0           push @prereqs, load_jsonyaml('prereqs.yml');
210             }
211 0 0         if (-f 'cpanfile') {
212 0           require Module::CPANfile;
213 0           push @prereqs, Module::CPANfile->load('cpanfile')->prereq_specs;
214             }
215 0 0         if ($opts{scan}) {
216 0           push @prereqs, _scan_prereqs([ keys %{$provides} ])->as_string_hash;
  0            
217             }
218              
219 0 0         if (@prereqs == 1) {
    0          
220 0           return $prereqs[0];
221             }
222             elsif (@prereqs == 0) {
223 0           return {};
224             }
225             else {
226 0           @prereqs = map { CPAN::Meta::Prereqs->new($_) } @prereqs;
  0            
227 0           my $prereqs = $prereqs[0]->with_merged_prereqs([ @prereqs[1..$#prereqs] ]);
228 0           $prereqs->as_string_hash;
229             }
230             }
231              
232             sub new {
233 0     0 0   my ($class, %opts) = @_;
234 0   0       my $mergefile = $opts{mergefile} // (grep { -f } qw/metamerge.json metamerge.yml/)[0];
  0            
235 0   0       my $mergedata = load_mergedata($mergefile) // {};
236 0           my $distname = distname($mergedata);
237 0           my $filename = distfilename($distname);
238 0           my $podname = $filename =~ s/\.pm$/.pod/r;
239              
240 0 0         my $data = Module::Metadata->new_from_file($filename, collect_pod => 1, decode_pod => 1) or die "Couldn't analyse $filename: $!";
241 0   0       my $pod_data = -e $podname && Module::Metadata->new_from_file($podname, collect_pod => 1, decode_pod => 1) // $data;
      0        
242 0 0 0       my @authors = map { s/E<([^>]+)>/e2char($1)/ge; m/ \A \s* (.+?) \s* \z /x } grep { /\S/ } split /\n/, $pod_data->pod('AUTHOR') // $pod_data->pod('AUTHORS') // '' or warn "Could not parse any authors from `=head1 AUTHOR` in $filename";
  0   0        
  0            
  0            
  0            
243 0           my $license = detect_license($pod_data, $filename, \@authors, $mergedata);
244              
245 0   0       my $load_meta = !%{ $opts{regenerate} // {} } && uptodate('META.json', 'cpanfile', 'prereqs.json', 'prereqs.yml', $mergefile);
246              
247 0 0         my $mode = -d 'planner' ? 'DB' : 'MBT';
248 0 0         my $meta = $load_meta ? CPAN::Meta->load_file('META.json', { lazy_validation => 0 }) : do {
249 0 0 0       my ($abstract) = ($pod_data->pod('NAME') // '') =~ / \A \s+ \S+ \s? - \s? (.+?) \s* \z /x or warn "Could not parse abstract from `=head1 NAME` in $filename";
250 0   0       my $version = $data->version($data->name) // die "Cannot parse \$VERSION from $filename";
251              
252 0           my $provides = Module::Metadata->provides(version => 2, dir => 'lib');
253 0           my $prereqs = load_prereqs($provides, %opts);
254 0 0         if ($mode eq 'MBT') {
255 0   0       $prereqs->{configure}{requires}{'Module::Build::Tiny'} //= mbt_version();
256             } else {
257 0   0       $prereqs->{configure}{requires}{'Dist::Build'} //= '0.003';
258             }
259 0   0       $prereqs->{develop}{requires}{'App::ModuleBuildTiny'} //= $VERSION;
260 0           my %resources = $class->generate_resources(%opts);
261              
262             my $metahash = {
263             name => $distname,
264             version => $version->stringify,
265             author => \@authors,
266             abstract => $abstract,
267             dynamic_config => 0,
268             license => [ $license->meta2_name ],
269             prereqs => $prereqs,
270 0 0 0       release_status => $opts{trial} // $version =~ /_/ ? 'testing' : 'stable',
271             generated_by => "App::ModuleBuildTiny version $VERSION",
272             'meta-spec' => {
273             version => 2,
274             url => 'http://search.cpan.org/perldoc?CPAN::Meta::Spec'
275             },
276             (resources => \%resources) x!! %resources,
277             x_spdx_expression => $license->spdx_expression,
278             x_generated_by_perl => "$^V",
279             };
280              
281 0 0         if (-e 'dynamic-prereqs.yml') {
282 0           my $dynamic_prereqs = load_jsonyaml('dynamic-prereqs.yml');
283 0           for my $expression (@{ $dynamic_prereqs->{expressions}}) {
  0            
284 0 0         $expression->{condition} = [ shellwords($expression->{condition}) ] unless ref $expression->{condition};
285 0 0         if (not ref $expression->{prereqs}) {
286 0           my ($module, $version) = shellwords($expression->{prereqs});
287 0   0       $version //= '0';
288 0           $expression->{prereqs} = { $module => $version };
289             }
290             }
291 0           $metahash->{x_dynamic_prereqs} = $dynamic_prereqs;
292             }
293 0 0         if (%{$mergedata}) {
  0            
294 0           require CPAN::Meta::Merge;
295 0           $metahash = CPAN::Meta::Merge->new(default_version => '2')->merge($metahash, $mergedata);
296             }
297 0 0         if ($metahash->{x_dynamic_prereqs}) {
298 0           $metahash->{dynamic_config} = 1;
299 0 0 0       $metahash->{prereqs}{configure}{requires}{'CPAN::Requirements::Dynamic'} //= '0.002' if $mode eq 'MBT';
300             }
301              
302             # this avoids a long-standing CPAN.pm bug that incorrectly merges runtime and
303             # "build" (build+test) requirements by ensuring requirements stay unified
304             # across all three phases
305 0           require CPAN::Meta::Prereqs::Filter;
306 0           my $filtered = CPAN::Meta::Prereqs::Filter::filter_prereqs(CPAN::Meta::Prereqs->new($metahash->{prereqs}), sanitize => 1);
307 0           my $merged_prereqs = $filtered->merged_requirements([qw/runtime build test/], ['requires']);
308 0           my %seen;
309 0           for my $phase (qw/runtime build test/) {
310 0           my $requirements = $filtered->requirements_for($phase, 'requires');
311 0           for my $module ($requirements->required_modules) {
312 0           $requirements->clear_requirement($module);
313 0 0         next if $seen{$module}++;
314 0           my $module_requirement = $merged_prereqs->requirements_for_module($module);
315 0           $requirements->add_string_requirement($module => $module_requirement);
316             }
317             }
318 0           $metahash->{prereqs} = $filtered->as_string_hash;
319              
320 0 0 0       $metahash->{provides} //= $provides if not $metahash->{no_index};
321 0           CPAN::Meta->create($metahash, { lazy_validation => 0 });
322             };
323              
324 0           my %files;
325 0 0 0       if (not $opts{regenerate}{MANIFEST} and -r 'MANIFEST') {
326 0           %files = %{ maniread() };
  0            
327             }
328             else {
329 0           my $maniskip = maniskip;
330 0           %files = %{ manifind() };
  0            
331 0           delete $files{$_} for grep { $maniskip->($_) } keys %files;
  0            
332             }
333 0           delete $files{$_} for keys %{ $opts{regenerate} };
  0            
334            
335 0           my $dist_name = $meta->name;
336 0   0       $files{'Build.PL'} //= do {
337 0           my $minimum_perl = prereqs_for($meta, qw/runtime requires perl 5.008/);
338 0           my $header = "# This Build.PL for $dist_name was generated by mbtiny $VERSION.";
339 0 0         if ($mode eq 'MBT') {
340 0           my $minimum_mbt = prereqs_for($meta, qw/configure requires Module::Build::Tiny/);
341 0           "$header\nuse $minimum_perl;\nuse Module::Build::Tiny $minimum_mbt;\nBuild_PL();\n";
342             } else {
343 0           my $minimum_db = prereqs_for($meta, qw/configure requires Dist::Build/);
344 0           "$header\nuse $minimum_perl;\nuse Dist::Build $minimum_db;\nBuild_PL(\\\@ARGV, \\\%ENV);\n";
345             }
346             };
347             {
348 0           local $ENV{CPAN_META_JSON_BACKEND} = JSON::MaybeXS::JSON();
  0            
349 0   0       $files{'META.json'} //= $meta->as_string;
350 0   0       $files{'META.yml'} //= $meta->as_string({ version => 1.4 });
351             }
352 0   0       $files{LICENSE} //= $license->fulltext;
353 0   0       $files{README} //= generate_readme($dist_name);
354 0 0         if ($opts{regenerate}{Changes}) {
355 0           my $time = strftime("%Y-%m-%d %H:%M:%S%z", localtime);
356 0           my $header = sprintf "%-9s %s\n", $meta->version, $time;
357 0           $files{Changes} = read_text('Changes') =~ s/(?<=\n\n)/$header/er;
  0            
358             }
359             # This must come last
360 0   0       $files{MANIFEST} //= join '', map { "$_\n" } sort keys %files;
  0            
361              
362 0           return bless {
363             files => \%files,
364             meta => $meta,
365             license => $license,
366             data => $data,
367             }, $class
368             }
369              
370              
371             sub generate_resources {
372 0     0 0   my ($class, %opts) = @_;
373 0           my %result;
374              
375 0 0         if ($opts{add_repository}) {
376 0           require Git::Wrapper;
377 0           my $git = Git::Wrapper->new('.');
378 0           my ($origin) = $git->remote('get-url' => 'origin');
379 0 0         if ($origin =~ m{https://github.com/([\w.-]+)/([\w.-]+).git}) {
    0          
    0          
380             $result{repository} = {
381 0           type => 'git',
382             web => "https://github.com/$1/$2",
383             url => $origin,
384             };
385             } elsif ($origin =~ m{git\@github.com:([\w.-]+)/([\w.-]+).git}) {
386             $result{repository} = {
387 0           type => 'git',
388             web => "https://github.com/$1/$2",
389             url => "https://github.com/$1/$2.git",
390             };
391             } elsif ($origin =~ m{^https?://}) {
392             $result{repository} = {
393 0           type => 'git',
394             url => $origin,
395             };
396             }
397             }
398              
399 0 0         if ($opts{add_bugtracker}) {
400 0           require Git::Wrapper;
401 0           my $git = Git::Wrapper->new('.');
402 0           my ($origin) = $git->remote('get-url' => 'origin');
403 0 0         if ($origin =~ m{https://github.com/([\w.-]+)/([\w.-]+).git}) {
    0          
404             $result{bugtracker} = {
405 0           web => "https://github.com/$1/$2/issues",
406             };
407             } elsif ($origin =~ m{git\@github.com:([\w.-]+)/([\w.-]+).git}) {
408             $result{bugtracker} = {
409 0           web => "https://github.com/$1/$2/issues",
410             };
411             }
412             }
413 0           return %result;
414             }
415              
416             sub write_dir {
417 0     0 0   my ($self, $dir, $verbose) = @_;
418 0           mkpath($dir, $verbose, oct '755');
419 0           my $files = $self->{files};
420 0           for my $filename (keys %{$files}) {
  0            
421 0           my $target = "$dir/$filename";
422 0 0         mkpath(dirname($target)) if not -d dirname($target);
423 0 0         if ($files->{$filename}) {
424 0           write_text($target, $files->{$filename});
425             }
426             else {
427 0           copy($filename, $target);
428             }
429             }
430             }
431              
432             sub write_tarball {
433 0     0 0   my ($self, $name) = @_;
434 0           require Archive::Tar;
435 0           my $arch = Archive::Tar->new;
436 0           for my $filename ($self->files) {
437 0           $arch->add_data($filename, $self->get_file($filename), { mode => oct '0644'} );
438             }
439 0           $arch->write($name, &Archive::Tar::COMPRESS_GZIP, $name =~ s/.tar.gz$//r);
440 0           return $name;
441             }
442              
443             sub files {
444 0     0 0   my $self = shift;
445 0           return keys %{ $self->{files} };
  0            
446             }
447              
448             sub get_file {
449 0     0 0   my ($self, $filename) = @_;
450 0 0         return if not exists $self->{files}{$filename};
451 0           my $raw = $self->{files}{$filename};
452 0 0         return $raw ? encode_utf8($raw) : read_binary($filename);
453             }
454              
455             sub run {
456 0     0 0   my ($self, %opts) = @_;
457 0           require File::Temp;
458 0           my $dir = File::Temp::tempdir(CLEANUP => 1);
459 0           $self->write_dir($dir, $opts{verbose});
460 0           local $CWD = $dir;
461 0           my $ret = !!1;
462 0 0         if ($opts{build}) {
463 0 0 0       system $Config{perlpath}, 'Build.PL' and ($opts{allow_failure} or die "Could not run Build.PL");
464 0 0 0       system $Config{perlpath}, 'Build' and ($opts{allow_failure} or die "Could not run Build");
465 0           my @extralib = map { rel2abs("blib/$_") } 'arch', 'lib';
  0            
466 0           local @PERL5LIB = (@extralib, @PERL5LIB);
467 0           local @PATH = (rel2abs(catdir('blib', 'script')), @PATH);
468 0           for my $command (@{ $opts{commands} }) {
  0            
469 0 0         say join ' ', @{$command} if $opts{verbose};
  0            
470 0   0       $ret &&= not system @{$command};
  0            
471             }
472             }
473             else {
474 0           for my $command (@{ $opts{commands} }) {
  0            
475 0 0         say join ' ', @{$command} if $opts{verbose};
  0            
476 0   0       $ret &&= not system @{$command};
  0            
477             }
478             }
479 0           return $ret;
480             }
481              
482             for my $method (qw/meta license/) {
483 1     1   7438 no strict 'refs';
  1         3  
  1         131  
484 0     0     *$method = sub { my $self = shift; return $self->{$method}; };
  0            
485             }
486              
487             for my $method (qw/name version release_status/) {
488 1     1   7 no strict 'refs';
  1         2  
  1         283  
489 0     0     *$method = sub { my $self = shift; return $self->{meta}->$method; }
  0            
490             }
491              
492             sub fullname {
493 0     0 0   my $self = shift;
494 0   0       my $trial = $self->release_status eq 'testing' && $self->version !~ /_/;
495 0 0         return $self->meta->name . '-' . $self->meta->version . ($trial ? '-TRIAL' : '' );
496             }
497              
498             sub archivename {
499 0     0 0   my $self = shift;
500 0           return $self->fullname . '.tar.gz';
501             }
502              
503             1;