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