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