add x_contributors data
[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
a12a101a 138 {
139 my $license = $meta->{license} = [
140 map { $_ eq 'perl' ? 'perl_5' : $_ }
141 map { ref ? @$_ : $_ }
142 ( $meta->{license} || $self->{LICENSE} || 'perl_5' )
143 ];
144
145 $resources->{license} ||= [ 'http://dev.perl.org/licenses/' ]
146 if @$license == 1 && $license->[0] eq 'perl_5';
147 }
148
26648fb0 149 $meta->{x_contributors} ||= do {
150 my $author_email =
151 join '|',
152 map quotemeta,
153 map /(\<[^<>]+\@[^<>]\>)/,
154 @$Author;
155 [
156 grep !/$author_email/,
157 map { chomp; s/^\s*\d+\s*//; utf8::decode($_); $_ }
158 `git shortlog -s --email HEAD`
159 ];
160 };
161
a8fd5adc 162 %$meta;
163 }
164
eddb1ce6 165 sub flush {
166 my $self = shift;
5371ff52 167 `git ls-files --error-unmatch MANIFEST.SKIP 2>&1`;
168 my $maniskip_tracked = !$?;
169
170 Distar::write_manifest_skip($self)
171 unless $maniskip_tracked;
b2ec6b47 172
173 my @results = @{$self->{RESULT}};
174 if ($MM_VER < 7) {
175 utf8::encode($_) for @results;
176 }
177 local $self->{RESULT} = \@results;
eddb1ce6 178 $self->SUPER::flush(@_);
179 }
180
7efea54c 181 sub special_targets {
182 my $self = shift;
183 my $targets = $self->SUPER::special_targets(@_);
f57289e3 184 my $phony_targets = join ' ', qw(
185 preflight
7b418de8 186 check-version
5baee32c 187 check-manifest
b0780d0a 188 check-cpan-upload
f57289e3 189 releasetest
190 release
191 readmefile
192 distmanicheck
193 nextrelease
194 refresh
195 bump
196 bumpmajor
197 bumpminor
198 );
199 $targets =~ s/^(\.PHONY *:.*)/$1 $phony_targets/m;
7efea54c 200 $targets;
201 }
202
42d0b436 203 sub init_dist {
204 my $self = shift;
205 my $pre_tar = $self->{TAR};
206 my $out = $self->SUPER::init_dist(@_);
207
208 my $tar = $self->{TAR};
209 my $gtar;
210 my $set_user;
211 my $version = `$tar --version`;
212 if ($version =~ /GNU tar/) {
213 $gtar = 1;
214 }
215 elsif (!$pre_tar && `gtar --version`) {
216 $tar = 'gtar';
217 $gtar = 1;
218 }
219 my $tarflags = $self->{TARFLAGS};
220 if (my ($flags) = $tarflags =~ /^-?([cvhlLf]+)$/) {
221 if ($flags =~ s/c// && $flags =~ s/f//) {
222 $tarflags = '--format=ustar -c'.$flags.'f';
223 if ($gtar) {
224 $tarflags = '--owner=0 --group=0 '.$tarflags;
225 $set_user = 1;
226 }
227 }
228 }
229
230 if (!$set_user) {
231 my $warn = '';
232 if ($> >= 2**21) {
233 $warn .= "uid ($>)";
234 }
235 if ($) >= 2**21) {
236 $warn .= ($warn ? ' and ' : '').'gid('.(0+$)).')';
237 }
238 if ($warn) {
239 warn "$warn too large! Max is ".(2**21-1).".\n"
240 ."Dist creation will likely fail. Install GNU tar to work around.\n";
241 }
242 }
243
244 $self->{TAR} = $tar;
245 $self->{TARFLAGS} = $tarflags;
246
247 $out;
248 }
249
ee801c00 250 sub tarfile_target {
251 my $self = shift;
252 my $out = $self->SUPER::tarfile_target(@_);
253 my $verify = <<'END_FRAG';
254 $(ABSPERLRUN) $(HELPERS)/verify-tarball $(DISTVNAME).tar $(DISTVNAME)/MANIFEST --tar="$(TAR)"
255END_FRAG
256 $out =~ s{(\$\(TAR\).*\n)}{$1$verify};
257 $out;
258 }
259
ca3b3b8a 260 sub dist_test {
261 my $self = shift;
8ab5ea70 262
66a2ecde 263 my $include = '';
264 if (open my $fh, '<', 'maint/Makefile.include') {
265 $include = "\n# --- Makefile.include:\n\n" . do { local $/; <$fh> };
266 $include =~ s/\n?\z/\n/;
267 }
b0401762 268
66a2ecde 269 my @bump_targets =
270 grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
81d518f6 271
8aac77fc 272 my $distar = File::Spec->catdir(
273 File::Spec->catpath((File::Spec->splitpath(__FILE__))[0,1], ''),
274 File::Spec->updir,
275 );
276 my $helpers = File::Spec->catdir($distar, 'helpers');
277
66a2ecde 278 my %vars = (
8aac77fc 279 DISTAR => $self->quote_literal($distar),
280 HELPERS => $self->quote_literal($helpers),
2c75254f 281 REMAKE => join(' ', '$(PERLRUN)', '-I$(DISTAR)/lib', '-mDistar', 'Makefile.PL', map { $self->quote_literal($_) } @ARGV),
190976f8 282 BRANCH => $self->{BRANCH} ||= 'master',
9b920c5c 283 CHANGELOG => $self->{CHANGELOG} ||= 'Changes',
b0780d0a 284 DEV_NULL_STDOUT => ($self->{DEV_NULL} ? '>'.File::Spec->devnull : ''),
f0bbeff7 285 DISTTEST_MAKEFILE_PARAMS => '',
66a2ecde 286 );
287
f0bbeff7 288 my $dist_test = $self->SUPER::dist_test(@_);
289 $dist_test =~ s/(\bMakefile\.PL\b)/$1 \$(DISTTEST_MAKEFILE_PARAMS)/;
290
66a2ecde 291 join('',
f0bbeff7 292 $dist_test,
66a2ecde 293 "\n\n# --- Distar section:\n\n",
294 (map "$_ = $vars{$_}\n", sort keys %vars),
295 <<'END',
81d518f6 296
b0780d0a 297preflight: check-version check-manifest check-cpan-upload
8aac77fc 298 $(ABSPERLRUN) $(HELPERS)/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)
7b418de8 299check-version:
8aac77fc 300 $(ABSPERLRUN) $(HELPERS)/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)
5baee32c 301check-manifest:
8aac77fc 302 $(ABSPERLRUN) $(HELPERS)/check-manifest
b0780d0a 303check-cpan-upload:
304 $(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)
3e632431 305releasetest:
f0bbeff7 306 $(MAKE) disttest RELEASE_TESTING=1 DISTTEST_MAKEFILE_PARAMS="PREREQ_FATAL=1" PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""
49beea48 307release: preflight
308 $(MAKE) releasetest
42e08a83 309 git commit -a -m "Release commit for $(VERSION)"
401ece0b 310 git tag v$(VERSION) -m "release v$(VERSION)"
49beea48 311 $(RM_RF) $(DISTVNAME)
312 $(MAKE) $(DISTVNAME).tar$(SUFFIX)
263b723e 313 $(NOECHO) $(MAKE) pushrelease FAKE_RELEASE=$(FAKE_RELEASE)
314pushrelease ::
315 $(NOECHO) $(NOOP)
316pushrelease$(FAKE_RELEASE) ::
65a1f7d9 317 cpan-upload $(DISTVNAME).tar$(SUFFIX)
2c636792 318 git push origin v$(VERSION) HEAD
5154970c 319distdir: readmefile
75f8311c 320readmefile: create_distdir
5c5deb0a 321 $(NOECHO) $(TEST_F) $(DISTVNAME)/README || $(MAKE) $(DISTVNAME)/README
f6286f60 322$(DISTVNAME)/README: $(VERSION_FROM)
323 $(NOECHO) $(MKPATH) $(DISTVNAME)
324 pod2text $(VERSION_FROM) >$(DISTVNAME)/README
8aac77fc 325 $(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) README
7934d553 326distsignature: readmefile
de048fa8 327disttest: distmanicheck
792c9e91 328distmanicheck: create_distdir
329 cd $(DISTVNAME) && $(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"
e7a78651 330nextrelease:
8aac77fc 331 $(ABSPERLRUN) $(HELPERS)/add-changelog-heading --git $(VERSION) $(CHANGELOG)
0edb27b8 332refresh:
41e593ee 333 cd $(DISTAR) && git pull || $(TRUE)
e9f66489 334 $(RM_F) $(FIRST_MAKEFILE)
0edb27b8 335 $(REMAKE)
8ab5ea70 336END
66a2ecde 337 map(sprintf(<<'END', "bump$_", ($_ || '$(V)')), @bump_targets),
338%s:
8aac77fc 339 $(ABSPERLRUN) $(HELPERS)/bump-version --git $(VERSION) %s
66a2ecde 340 $(RM_F) $(FIRST_MAKEFILE)
341 $(REMAKE)
42e08a83 342END
66a2ecde 343 $include,
344 "\n",
345 );
42e08a83 346 }
347}
348
42e08a83 3491;
edb4539a 350__END__
351
352=head1 NAME
353
354Distar - Additions to ExtUtils::MakeMaker for dist authors
355
356=head1 SYNOPSIS
357
358F<Makefile.PL>:
359
360 use ExtUtils::MakeMaker;
83e12da3 361 (do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
edb4539a 362
363 WriteMakefile(...);
364
365F<maint/Makefile.PL.include>:
366
367 BEGIN { -e 'Distar' or system("git clone git://git.shadowcat.co.uk/p5sagit/Distar.git") }
368 use lib 'Distar/lib';
369 use Distar 0.001;
370
371 author 'A. U. Thor <author@cpan.org>';
372
373 manifest_include t => 'test-helper.pl';
374 manifest_include corpus => '.txt';
375
376make commmands:
377
378 $ perl Makefile.PL
379 $ make bump # bump version
380 $ make bump V=2.000000 # bump to specific version
381 $ make bumpminor # bump minor version component
382 $ make bumpmajor # bump major version component
383 $ make nextrelease # add version heading to Changes file
384 $ make releasetest # build dist and test (with xt/ and RELEASE_TESTING=1)
385 $ make preflight # check that repo and file state is release ready
83e12da3 386 $ make release # check releasetest and preflight, commits and tags,
387 # builds and uploads to CPAN, and pushes commits and
388 # tag
389 $ make release FAKE_RELEASE=1
390 # builds a release INCLUDING committing and tagging,
391 # but does not upload to cpan or push anything to git
edb4539a 392
393=head1 DESCRIPTION
394
395L<ExtUtils::MakeMaker> works well enough as development tool for
396builting and testing, but using it to release is annoying and error prone.
397Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
398author tool. This includes extra commands for releasing and safety checks, and
399automatic generation of some files. It doesn't require any non-core modules and
400is compatible with old versions of perl.
401
402=head1 FUNCTIONS
403
404=head2 author( $author )
405
406Set the author to include in generated META files. Can be a single entry, or
407an arrayref.
408
409=head2 manifest_include( $dir, $pattern )
410
411Add a pattern to include files in the MANIFEST file, and thus in the generated
412dist files.
413
414The pattern can be either a regex, or a path suffix. It will be applied to the
415full path past the directory specified.
416
417The default files that are always included are: F<.pm> and F<.pod> files in
418F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
419F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
420the dist root, and all files in F<maint>.
421
422=head1 AUTOGENERATED FILES
423
424=over 4
425
426=item F<MANIFEST.SKIP>
427
428The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
429explicitly allowed via C<manifest_include> or the included defaults. It will be
430created (or updated) at C<perl Makefile.PL> time.
431
432=item F<README>
433
434The F<README> file will be generated at dist generation time, inside the built
435dist. It will be generated using C<pod2text> on the main module.
436
437If a F<README> file exists in the repo, it will be used directly instead of
438generating the file.
439
440=back
441
442=head1 MAKE COMMMANDS
443
444=head2 test
445
446test will be adjusted to include F<xt/> tests by default. This will only apply
447for authors, not users installing from CPAN.
448
449=head2 release
450
451Releases the dist. Before releasing, checks will be done on the dist using the
452C<preflight> and C<releasetest> commands.
453
454Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
455It will also create a git tag for the release, and push the tag and branch.
456
83e12da3 457=head3 FAKE_RELEASE
263b723e 458
459If release is run with FAKE_RELEASE=1 set, it will skip uploading to CPAN and
460pushing to git. A release commit will still be created and tagged locally.
461
edb4539a 462=head2 preflight
463
464Performs a number of checks on the files and repository, ensuring it is in a
465sane state to do a release. The checks are:
466
467=over 4
468
469=item * All version numbers match
470
471=item * The F<MANIFEST> file is up to date
472
473=item * The branch is correct
474
475=item * There is no existing tag for the version
476
477=item * There are no unmerged upstream changes
478
479=item * There are no outstanding local changes
480
481=item * There is an appropriate staged Changes heading
482
483=item * cpan-upload is available
484
485=back
486
487=head2 releasetest
488
489Test the dist preparing for a release. This generates a dist dir and runs the
490tests from inside it. This ensures all appropriate files are included inside
491the dist. C<RELEASE_TESTING> will be set in the environment.
492
493=head2 nextrelease
494
495Adds an appropriate changelog heading for the release, and prompts to stage the
496change.
497
498=head2 bump
499
500Bumps the version number. This will try to preserve the length and format of
83e12da3 501the version number. The least significant digit will be incremented. Versions
502with underscores will preserve the underscore in the same position.
edb4539a 503
504Optionally accepts a C<V> option to set the version to a specific value.
505
506The version changes will automatically be committed. Unstaged modifications to
507the files will be left untouched.
508
83e12da3 509=head3 V
510
511The V option will be passed along to the version bumping script. It can accept
512a space separated list of options, including an explicit version number.
513
514Options:
515
516=over 4
517
518=item --force
519
520Updates version numbers even if they do not match the current expected version
521number.
522
523=item --stable
524
525Attempts to convert the updated version to a stable version, removing any
526underscore.
527
528=item --alpha
529
530Attempts to convert the updated version to an alpha version, adding an
531underscore in an appropriate place.
532
533=back
534
edb4539a 535=head2 bumpminor
536
537Like bump, but increments the minor segment of the version. This will treat
538numeric versions as x.yyyzzz format, incrementing the yyy segment.
539
540=head2 bumpmajor
541
542Like bumpminor, but bumping the major segment.
543
544=head2 refresh
545
546Updates Distar and re-runs C<perl Makefile.PL>
547
548=head1 SUPPORT
549
550IRC: #web-simple on irc.perl.org
551
552Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
553
554Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
555
556=head1 AUTHOR
557
558mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
559
560=head1 CONTRIBUTORS
561
562haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
563
83e12da3 564ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
edb4539a 565
566frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
567
568Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
569
570=head1 COPYRIGHT
571
572Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
573as listed above.
574
575=head1 LICENSE
576
577This library is free software and may be distributed under the same terms
578as perl itself. See L<http://dev.perl.org/licenses/>.
579
580=cut