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