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