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