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