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