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