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