add rt as bugtracker to metadata if none specified
[p5sagit/Distar.git] / lib / Distar.pm
CommitLineData
42e08a83 1package Distar;
362ec4af 2use strict;
3use warnings FATAL => 'all';
42e08a83 4use base qw(Exporter);
ca3b3b8a 5use ExtUtils::MakeMaker ();
6use ExtUtils::MM ();
8aac77fc 7use File::Spec ();
42e08a83 8
f6286f60 9our $VERSION = '0.002000';
37e295d8 10$VERSION = eval $VERSION;
11
e076eedb 12my $MM_VER = eval $ExtUtils::MakeMaker::VERSION;
13
42e08a83 14our @EXPORT = qw(
be032607 15 author manifest_include readme_generator
42e08a83 16);
17
18sub import {
2852faf3 19 strict->import;
20 warnings->import(FATAL => 'all');
42e08a83 21 shift->export_to_level(1,@_);
22}
23
e076eedb 24sub author {
25 our $Author = shift;
26 $Author = [ $Author ]
27 if !ref $Author;
28}
42e08a83 29
30our @Manifest = (
31 'lib' => '.pm',
7b9318ce 32 'lib' => '.pod',
42e08a83 33 't' => '.t',
34 't/lib' => '.pm',
35 'xt' => '.t',
36 'xt/lib' => '.pm',
1d950b4a 37 '' => qr{[^/]*\.PL},
f1ef306e 38 '' => qr{Changes|MANIFEST|README|LICENSE|META\.yml},
42e08a83 39 'maint' => qr{[^.].*},
40);
41
42sub manifest_include {
43 push @Manifest, @_;
44}
45
6e83c113 46sub readme_generator {
f6286f60 47 die "readme_generator unsupported" if @_ && $_[0];
6e83c113 48}
49
42e08a83 50sub write_manifest_skip {
0fa73f76 51 my ($mm) = @_;
42e08a83 52 my @files = @Manifest;
53 my @parts;
54 while (my ($dir, $spec) = splice(@files, 0, 2)) {
55 my $re = ($dir ? $dir.'/' : '').
56 ((ref($spec) eq 'Regexp')
57 ? $spec
58 : !ref($spec)
59 ? ".*\Q${spec}\E"
a3e39afd 60 # print ref as well as stringification in case of overload ""
42e08a83 61 : die "spec must be string or regexp, was: ${spec} (${\ref $spec})");
62 push @parts, $re;
63 }
0fa73f76 64 my $dist_name = $mm->{DISTNAME};
65 my $include = join '|', map "${_}\$", @parts;
66 my $final = "^(?:\Q$dist_name\E-v?[0-9_.]+/|(?!$include))";
19916679 67 open my $skip, '>', 'MANIFEST.SKIP'
68 or die "can't open MANIFEST.SKIP: $!";
42e08a83 69 print $skip "${final}\n";
70 close $skip;
71}
72
a8fd5adc 73sub _clone {
74 map {
75 if (ref eq 'HASH') {
76 my $struct = $_;
77 { map +($_ => _clone($struct->{$_})), keys %$struct };
78 }
79 elsif (ref eq 'ARRAY') {
80 [ _clone(@$_) ];
81 }
82 else {
83 $_;
84 }
85 } @_;
86}
87
ca3b3b8a 88{
89 package Distar::MM;
9af7ff2e 90 our @ISA = @MM::ISA;
91 @MM::ISA = (__PACKAGE__);
ca3b3b8a 92
93 sub new {
94 my ($class, $args) = @_;
4762e03a 95 my %test = %{$args->{test}||{}};
96 my $tests = $test{TESTS} || 't/*.t';
97 $tests !~ /\b\Q$_\E\b/ and $tests .= " $_"
98 for 'xt/*.t', 'xt/*/*.t';
99 $test{TESTS} = $tests;
ca3b3b8a 100 return $class->SUPER::new({
180e908e 101 LICENSE => 'perl_5',
53e1282f 102 MIN_PERL_VERSION => '5.006',
15f10a18 103 ($Distar::Author ? (
d0f7a429 104 AUTHOR => ($MM_VER >= 6.5702 ? $Distar::Author : join(', ', @$Distar::Author)),
105 ) : ()),
1f136c70 106 (exists $args->{ABSTRACT} ? () : (ABSTRACT_FROM => $args->{VERSION_FROM})),
4a4bb570 107 %$args,
4762e03a 108 test => \%test,
0a3fdb23 109 realclean => { FILES => (
110 ($args->{realclean}{FILES}||'')
111 . ' Distar/ MANIFEST.SKIP MANIFEST MANIFEST.bak'
112 ) },
ca3b3b8a 113 });
114 }
115
a8fd5adc 116 sub metafile_data {
117 my $self = shift;
118 my $meta = { Distar::_clone($self->SUPER::metafile_data(@_) };
119
120 my $spec_ver = ($meta->{'meta-spec'} && $meta->{'meta-spec'}{version} || 1.4;
121 my $resources = $meta->{resources} ||= {};
122
3584e66b 123 {
124 my $no_index_dir = ${ $meta->{no_index}||={} }{directory} ||= [];
125 my %seen;
126 @$no_index_dir = grep !$seen{$_}++, @$no_index_dir, grep -d, qw(t xt);
127 }
128
3d2016bf 129 $resources->{bugtracker} ||= do {
130 (my $queue = $meta->{name} || $self->{NAME}) =~ s/::/-/g;
131 my $rt_link = 'https://rt.cpan.org/Dist/Display.html?Name='.$queue;
132 $spec_ver < 2 ? $rt_link : {
133 web => $rt_link,
134 mailto => "bug-$queue\@rt.cpan.org",
135 };
136 };
137
a8fd5adc 138 %$meta;
139 }
140
eddb1ce6 141 sub flush {
142 my $self = shift;
5371ff52 143 `git ls-files --error-unmatch MANIFEST.SKIP 2>&1`;
144 my $maniskip_tracked = !$?;
145
146 Distar::write_manifest_skip($self)
147 unless $maniskip_tracked;
eddb1ce6 148 $self->SUPER::flush(@_);
149 }
150
7efea54c 151 sub special_targets {
152 my $self = shift;
153 my $targets = $self->SUPER::special_targets(@_);
f57289e3 154 my $phony_targets = join ' ', qw(
155 preflight
7b418de8 156 check-version
5baee32c 157 check-manifest
b0780d0a 158 check-cpan-upload
f57289e3 159 releasetest
160 release
161 readmefile
162 distmanicheck
163 nextrelease
164 refresh
165 bump
166 bumpmajor
167 bumpminor
168 );
169 $targets =~ s/^(\.PHONY *:.*)/$1 $phony_targets/m;
7efea54c 170 $targets;
171 }
172
42d0b436 173 sub init_dist {
174 my $self = shift;
175 my $pre_tar = $self->{TAR};
176 my $out = $self->SUPER::init_dist(@_);
177
178 my $tar = $self->{TAR};
179 my $gtar;
180 my $set_user;
181 my $version = `$tar --version`;
182 if ($version =~ /GNU tar/) {
183 $gtar = 1;
184 }
185 elsif (!$pre_tar && `gtar --version`) {
186 $tar = 'gtar';
187 $gtar = 1;
188 }
189 my $tarflags = $self->{TARFLAGS};
190 if (my ($flags) = $tarflags =~ /^-?([cvhlLf]+)$/) {
191 if ($flags =~ s/c// && $flags =~ s/f//) {
192 $tarflags = '--format=ustar -c'.$flags.'f';
193 if ($gtar) {
194 $tarflags = '--owner=0 --group=0 '.$tarflags;
195 $set_user = 1;
196 }
197 }
198 }
199
200 if (!$set_user) {
201 my $warn = '';
202 if ($> >= 2**21) {
203 $warn .= "uid ($>)";
204 }
205 if ($) >= 2**21) {
206 $warn .= ($warn ? ' and ' : '').'gid('.(0+$)).')';
207 }
208 if ($warn) {
209 warn "$warn too large! Max is ".(2**21-1).".\n"
210 ."Dist creation will likely fail. Install GNU tar to work around.\n";
211 }
212 }
213
214 $self->{TAR} = $tar;
215 $self->{TARFLAGS} = $tarflags;
216
217 $out;
218 }
219
ee801c00 220 sub tarfile_target {
221 my $self = shift;
222 my $out = $self->SUPER::tarfile_target(@_);
223 my $verify = <<'END_FRAG';
224 $(ABSPERLRUN) $(HELPERS)/verify-tarball $(DISTVNAME).tar $(DISTVNAME)/MANIFEST --tar="$(TAR)"
225END_FRAG
226 $out =~ s{(\$\(TAR\).*\n)}{$1$verify};
227 $out;
228 }
229
ca3b3b8a 230 sub dist_test {
231 my $self = shift;
8ab5ea70 232
66a2ecde 233 my $include = '';
234 if (open my $fh, '<', 'maint/Makefile.include') {
235 $include = "\n# --- Makefile.include:\n\n" . do { local $/; <$fh> };
236 $include =~ s/\n?\z/\n/;
237 }
b0401762 238
66a2ecde 239 my @bump_targets =
240 grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
81d518f6 241
8aac77fc 242 my $distar = File::Spec->catdir(
243 File::Spec->catpath((File::Spec->splitpath(__FILE__))[0,1], ''),
244 File::Spec->updir,
245 );
246 my $helpers = File::Spec->catdir($distar, 'helpers');
247
66a2ecde 248 my %vars = (
8aac77fc 249 DISTAR => $self->quote_literal($distar),
250 HELPERS => $self->quote_literal($helpers),
2c75254f 251 REMAKE => join(' ', '$(PERLRUN)', '-I$(DISTAR)/lib', '-mDistar', 'Makefile.PL', map { $self->quote_literal($_) } @ARGV),
190976f8 252 BRANCH => $self->{BRANCH} ||= 'master',
9b920c5c 253 CHANGELOG => $self->{CHANGELOG} ||= 'Changes',
b0780d0a 254 DEV_NULL_STDOUT => ($self->{DEV_NULL} ? '>'.File::Spec->devnull : ''),
f0bbeff7 255 DISTTEST_MAKEFILE_PARAMS => '',
66a2ecde 256 );
257
f0bbeff7 258 my $dist_test = $self->SUPER::dist_test(@_);
259 $dist_test =~ s/(\bMakefile\.PL\b)/$1 \$(DISTTEST_MAKEFILE_PARAMS)/;
260
66a2ecde 261 join('',
f0bbeff7 262 $dist_test,
66a2ecde 263 "\n\n# --- Distar section:\n\n",
264 (map "$_ = $vars{$_}\n", sort keys %vars),
265 <<'END',
81d518f6 266
b0780d0a 267preflight: check-version check-manifest check-cpan-upload
8aac77fc 268 $(ABSPERLRUN) $(HELPERS)/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)
7b418de8 269check-version:
8aac77fc 270 $(ABSPERLRUN) $(HELPERS)/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)
5baee32c 271check-manifest:
8aac77fc 272 $(ABSPERLRUN) $(HELPERS)/check-manifest
b0780d0a 273check-cpan-upload:
274 $(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)
3e632431 275releasetest:
f0bbeff7 276 $(MAKE) disttest RELEASE_TESTING=1 DISTTEST_MAKEFILE_PARAMS="PREREQ_FATAL=1" PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""
49beea48 277release: preflight
278 $(MAKE) releasetest
42e08a83 279 git commit -a -m "Release commit for $(VERSION)"
401ece0b 280 git tag v$(VERSION) -m "release v$(VERSION)"
49beea48 281 $(RM_RF) $(DISTVNAME)
282 $(MAKE) $(DISTVNAME).tar$(SUFFIX)
263b723e 283 $(NOECHO) $(MAKE) pushrelease FAKE_RELEASE=$(FAKE_RELEASE)
284pushrelease ::
285 $(NOECHO) $(NOOP)
286pushrelease$(FAKE_RELEASE) ::
65a1f7d9 287 cpan-upload $(DISTVNAME).tar$(SUFFIX)
2c636792 288 git push origin v$(VERSION) HEAD
5154970c 289distdir: readmefile
75f8311c 290readmefile: create_distdir
5c5deb0a 291 $(NOECHO) $(TEST_F) $(DISTVNAME)/README || $(MAKE) $(DISTVNAME)/README
f6286f60 292$(DISTVNAME)/README: $(VERSION_FROM)
293 $(NOECHO) $(MKPATH) $(DISTVNAME)
294 pod2text $(VERSION_FROM) >$(DISTVNAME)/README
8aac77fc 295 $(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) README
7934d553 296distsignature: readmefile
de048fa8 297disttest: distmanicheck
792c9e91 298distmanicheck: create_distdir
299 cd $(DISTVNAME) && $(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"
e7a78651 300nextrelease:
8aac77fc 301 $(ABSPERLRUN) $(HELPERS)/add-changelog-heading --git $(VERSION) $(CHANGELOG)
0edb27b8 302refresh:
41e593ee 303 cd $(DISTAR) && git pull || $(TRUE)
e9f66489 304 $(RM_F) $(FIRST_MAKEFILE)
0edb27b8 305 $(REMAKE)
8ab5ea70 306END
66a2ecde 307 map(sprintf(<<'END', "bump$_", ($_ || '$(V)')), @bump_targets),
308%s:
8aac77fc 309 $(ABSPERLRUN) $(HELPERS)/bump-version --git $(VERSION) %s
66a2ecde 310 $(RM_F) $(FIRST_MAKEFILE)
311 $(REMAKE)
42e08a83 312END
66a2ecde 313 $include,
314 "\n",
315 );
42e08a83 316 }
317}
318
42e08a83 3191;
edb4539a 320__END__
321
322=head1 NAME
323
324Distar - Additions to ExtUtils::MakeMaker for dist authors
325
326=head1 SYNOPSIS
327
328F<Makefile.PL>:
329
330 use ExtUtils::MakeMaker;
83e12da3 331 (do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
edb4539a 332
333 WriteMakefile(...);
334
335F<maint/Makefile.PL.include>:
336
337 BEGIN { -e 'Distar' or system("git clone git://git.shadowcat.co.uk/p5sagit/Distar.git") }
338 use lib 'Distar/lib';
339 use Distar 0.001;
340
341 author 'A. U. Thor <author@cpan.org>';
342
343 manifest_include t => 'test-helper.pl';
344 manifest_include corpus => '.txt';
345
346make commmands:
347
348 $ perl Makefile.PL
349 $ make bump # bump version
350 $ make bump V=2.000000 # bump to specific version
351 $ make bumpminor # bump minor version component
352 $ make bumpmajor # bump major version component
353 $ make nextrelease # add version heading to Changes file
354 $ make releasetest # build dist and test (with xt/ and RELEASE_TESTING=1)
355 $ make preflight # check that repo and file state is release ready
83e12da3 356 $ make release # check releasetest and preflight, commits and tags,
357 # builds and uploads to CPAN, and pushes commits and
358 # tag
359 $ make release FAKE_RELEASE=1
360 # builds a release INCLUDING committing and tagging,
361 # but does not upload to cpan or push anything to git
edb4539a 362
363=head1 DESCRIPTION
364
365L<ExtUtils::MakeMaker> works well enough as development tool for
366builting and testing, but using it to release is annoying and error prone.
367Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
368author tool. This includes extra commands for releasing and safety checks, and
369automatic generation of some files. It doesn't require any non-core modules and
370is compatible with old versions of perl.
371
372=head1 FUNCTIONS
373
374=head2 author( $author )
375
376Set the author to include in generated META files. Can be a single entry, or
377an arrayref.
378
379=head2 manifest_include( $dir, $pattern )
380
381Add a pattern to include files in the MANIFEST file, and thus in the generated
382dist files.
383
384The pattern can be either a regex, or a path suffix. It will be applied to the
385full path past the directory specified.
386
387The default files that are always included are: F<.pm> and F<.pod> files in
388F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
389F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
390the dist root, and all files in F<maint>.
391
392=head1 AUTOGENERATED FILES
393
394=over 4
395
396=item F<MANIFEST.SKIP>
397
398The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
399explicitly allowed via C<manifest_include> or the included defaults. It will be
400created (or updated) at C<perl Makefile.PL> time.
401
402=item F<README>
403
404The F<README> file will be generated at dist generation time, inside the built
405dist. It will be generated using C<pod2text> on the main module.
406
407If a F<README> file exists in the repo, it will be used directly instead of
408generating the file.
409
410=back
411
412=head1 MAKE COMMMANDS
413
414=head2 test
415
416test will be adjusted to include F<xt/> tests by default. This will only apply
417for authors, not users installing from CPAN.
418
419=head2 release
420
421Releases the dist. Before releasing, checks will be done on the dist using the
422C<preflight> and C<releasetest> commands.
423
424Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
425It will also create a git tag for the release, and push the tag and branch.
426
83e12da3 427=head3 FAKE_RELEASE
263b723e 428
429If release is run with FAKE_RELEASE=1 set, it will skip uploading to CPAN and
430pushing to git. A release commit will still be created and tagged locally.
431
edb4539a 432=head2 preflight
433
434Performs a number of checks on the files and repository, ensuring it is in a
435sane state to do a release. The checks are:
436
437=over 4
438
439=item * All version numbers match
440
441=item * The F<MANIFEST> file is up to date
442
443=item * The branch is correct
444
445=item * There is no existing tag for the version
446
447=item * There are no unmerged upstream changes
448
449=item * There are no outstanding local changes
450
451=item * There is an appropriate staged Changes heading
452
453=item * cpan-upload is available
454
455=back
456
457=head2 releasetest
458
459Test the dist preparing for a release. This generates a dist dir and runs the
460tests from inside it. This ensures all appropriate files are included inside
461the dist. C<RELEASE_TESTING> will be set in the environment.
462
463=head2 nextrelease
464
465Adds an appropriate changelog heading for the release, and prompts to stage the
466change.
467
468=head2 bump
469
470Bumps the version number. This will try to preserve the length and format of
83e12da3 471the version number. The least significant digit will be incremented. Versions
472with underscores will preserve the underscore in the same position.
edb4539a 473
474Optionally accepts a C<V> option to set the version to a specific value.
475
476The version changes will automatically be committed. Unstaged modifications to
477the files will be left untouched.
478
83e12da3 479=head3 V
480
481The V option will be passed along to the version bumping script. It can accept
482a space separated list of options, including an explicit version number.
483
484Options:
485
486=over 4
487
488=item --force
489
490Updates version numbers even if they do not match the current expected version
491number.
492
493=item --stable
494
495Attempts to convert the updated version to a stable version, removing any
496underscore.
497
498=item --alpha
499
500Attempts to convert the updated version to an alpha version, adding an
501underscore in an appropriate place.
502
503=back
504
edb4539a 505=head2 bumpminor
506
507Like bump, but increments the minor segment of the version. This will treat
508numeric versions as x.yyyzzz format, incrementing the yyy segment.
509
510=head2 bumpmajor
511
512Like bumpminor, but bumping the major segment.
513
514=head2 refresh
515
516Updates Distar and re-runs C<perl Makefile.PL>
517
518=head1 SUPPORT
519
520IRC: #web-simple on irc.perl.org
521
522Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
523
524Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
525
526=head1 AUTHOR
527
528mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
529
530=head1 CONTRIBUTORS
531
532haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
533
83e12da3 534ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
edb4539a 535
536frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
537
538Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
539
540=head1 COPYRIGHT
541
542Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
543as listed above.
544
545=head1 LICENSE
546
547This library is free software and may be distributed under the same terms
548as perl itself. See L<http://dev.perl.org/licenses/>.
549
550=cut