split out checking for cpan-upload from preflight
[p5sagit/Distar.git] / lib / Distar.pm
1 package Distar;
2 use strict;
3 use warnings FATAL => 'all';
4 use base qw(Exporter);
5 use ExtUtils::MakeMaker ();
6 use ExtUtils::MM ();
7
8 our $VERSION = '0.002000';
9 $VERSION = eval $VERSION;
10
11 my $MM_VER = eval $ExtUtils::MakeMaker::VERSION;
12
13 our @EXPORT = qw(
14   author manifest_include readme_generator
15 );
16
17 sub import {
18   strict->import;
19   warnings->import(FATAL => 'all');
20   shift->export_to_level(1,@_);
21 }
22
23 sub author {
24   our $Author = shift;
25   $Author = [ $Author ]
26     if !ref $Author;
27 }
28
29 our @Manifest = (
30   'lib' => '.pm',
31   'lib' => '.pod',
32   't' => '.t',
33   't/lib' => '.pm',
34   'xt' => '.t',
35   'xt/lib' => '.pm',
36   '' => qr{[^/]*\.PL},
37   '' => qr{Changes|MANIFEST|README|LICENSE|META\.yml},
38   'maint' => qr{[^.].*},
39 );
40
41 sub manifest_include {
42   push @Manifest, @_;
43 }
44
45 sub readme_generator {
46   die "readme_generator unsupported" if @_ && $_[0];
47 }
48
49 sub write_manifest_skip {
50   my ($mm) = @_;
51   my @files = @Manifest;
52   my @parts;
53   while (my ($dir, $spec) = splice(@files, 0, 2)) {
54     my $re = ($dir ? $dir.'/' : '').
55       ((ref($spec) eq 'Regexp')
56         ? $spec
57         : !ref($spec)
58           ? ".*\Q${spec}\E"
59             # print ref as well as stringification in case of overload ""
60           : die "spec must be string or regexp, was: ${spec} (${\ref $spec})");
61     push @parts, $re;
62   }
63   my $dist_name = $mm->{DISTNAME};
64   my $include = join '|', map "${_}\$", @parts;
65   my $final = "^(?:\Q$dist_name\E-v?[0-9_.]+/|(?!$include))";
66   open my $skip, '>', 'MANIFEST.SKIP'
67     or die "can't open MANIFEST.SKIP: $!";
68   print $skip "${final}\n";
69   close $skip;
70 }
71
72 {
73   package Distar::MM;
74   our @ISA = @MM::ISA;
75   @MM::ISA = (__PACKAGE__);
76
77   sub new {
78     my ($class, $args) = @_;
79     return $class->SUPER::new({
80       LICENSE => 'perl_5',
81       MIN_PERL_VERSION => '5.006',
82       AUTHOR => ($MM_VER >= 6.5702 ? $Distar::Author : join(', ', @$Distar::Author)),
83       ABSTRACT_FROM => $args->{VERSION_FROM},
84       %$args,
85       test => { TESTS => ($args->{test}{TESTS}||'t/*.t').' xt/*.t xt/*/*.t' },
86       realclean => { FILES => (
87         ($args->{realclean}{FILES}||'')
88         . ' Distar/ MANIFEST.SKIP MANIFEST MANIFEST.bak'
89       ) },
90     });
91   }
92
93   sub flush {
94     my $self = shift;
95     Distar::write_manifest_skip($self);
96     $self->SUPER::flush(@_);
97   }
98
99   sub special_targets {
100     my $self = shift;
101     my $targets = $self->SUPER::special_targets(@_);
102     my $phony_targets = join ' ', qw(
103       preflight
104       check-version
105       check-manifest
106       check-cpan-upload
107       releasetest
108       release
109       readmefile
110       distmanicheck
111       nextrelease
112       refresh
113       bump
114       bumpmajor
115       bumpminor
116     );
117     $targets =~ s/^(\.PHONY *:.*)/$1 $phony_targets/m;
118     $targets;
119   }
120
121   sub dist_test {
122     my $self = shift;
123
124     my $include = '';
125     if (open my $fh, '<', 'maint/Makefile.include') {
126       $include = "\n# --- Makefile.include:\n\n" . do { local $/; <$fh> };
127       $include =~ s/\n?\z/\n/;
128     }
129
130     my @bump_targets =
131       grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
132
133     my %vars = (
134       REMAKE => join(' ', '$(PERLRUN)', 'Makefile.PL', map { $self->quote_literal($_) } @ARGV),
135       BRANCH => $self->{BRANCH} ||= 'master',
136       CHANGELOG => $self->{CHANGELOG} ||= 'Changes',
137       DEV_NULL_STDOUT => ($self->{DEV_NULL} ? '>'.File::Spec->devnull : ''),
138     );
139
140     join('',
141       $self->SUPER::dist_test(@_),
142       "\n\n# --- Distar section:\n\n",
143       (map "$_ = $vars{$_}\n", sort keys %vars),
144       <<'END',
145
146 preflight: check-version check-manifest check-cpan-upload
147         $(ABSPERLRUN) Distar/helpers/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)
148 check-version:
149         $(ABSPERLRUN) Distar/helpers/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)
150 check-manifest:
151         $(ABSPERLRUN) Distar/helpers/check-manifest
152 check-cpan-upload:
153         $(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)
154 releasetest:
155         $(MAKE) disttest RELEASE_TESTING=1 PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""
156 release: preflight releasetest
157         $(RM_RF) $(DISTVNAME)
158         $(MAKE) $(DISTVNAME).tar$(SUFFIX)
159         git commit -a -m "Release commit for $(VERSION)"
160         git tag v$(VERSION) -m "release v$(VERSION)"
161         cpan-upload $(DISTVNAME).tar$(SUFFIX)
162         git push origin v$(VERSION) HEAD
163 distdir: readmefile
164 readmefile: create_distdir
165         $(NOECHO) $(MAKE) $(DISTVNAME)/README
166 $(DISTVNAME)/README: $(VERSION_FROM)
167         $(NOECHO) $(MKPATH) $(DISTVNAME)
168         pod2text $(VERSION_FROM) >$(DISTVNAME)/README
169         $(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) ../Distar/helpers/add-to-manifest README
170 disttest: distmanicheck
171 distmanicheck: create_distdir
172         cd $(DISTVNAME) && $(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"
173 nextrelease:
174         $(ABSPERLRUN) Distar/helpers/add-changelog-heading --git $(VERSION) $(CHANGELOG)
175 refresh:
176         cd Distar && git pull
177         $(RM_F) $(FIRST_MAKEFILE)
178         $(REMAKE)
179 END
180       map(sprintf(<<'END', "bump$_", ($_ || '$(V)')), @bump_targets),
181 %s:
182         $(ABSPERLRUN) Distar/helpers/bump-version --git $(VERSION) %s
183         $(RM_F) $(FIRST_MAKEFILE)
184         $(REMAKE)
185 END
186       $include,
187       "\n",
188     );
189   }
190 }
191
192 1;
193 __END__
194
195 =head1 NAME
196
197 Distar - Additions to ExtUtils::MakeMaker for dist authors
198
199 =head1 SYNOPSIS
200
201 F<Makefile.PL>:
202
203   use ExtUtils::MakeMaker;
204   (do 'maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
205
206   WriteMakefile(...);
207
208 F<maint/Makefile.PL.include>:
209
210   BEGIN { -e 'Distar' or system("git clone git://git.shadowcat.co.uk/p5sagit/Distar.git") }
211   use lib 'Distar/lib';
212   use Distar 0.001;
213
214   author 'A. U. Thor <author@cpan.org>';
215
216   manifest_include t => 'test-helper.pl';
217   manifest_include corpus => '.txt';
218
219 make commmands:
220
221   $ perl Makefile.PL
222   $ make bump             # bump version
223   $ make bump V=2.000000  # bump to specific version
224   $ make bumpminor        # bump minor version component
225   $ make bumpmajor        # bump major version component
226   $ make nextrelease      # add version heading to Changes file
227   $ make releasetest      # build dist and test (with xt/ and RELEASE_TESTING=1)
228   $ make preflight        # check that repo and file state is release ready
229   $ make release          # check releasetest and preflight, then build and
230                           # upload to CPAN, tag release, push tag and branch
231
232 =head1 DESCRIPTION
233
234 L<ExtUtils::MakeMaker> works well enough as development tool for
235 builting and testing, but using it to release is annoying and error prone.
236 Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
237 author tool.  This includes extra commands for releasing and safety checks, and
238 automatic generation of some files.  It doesn't require any non-core modules and
239 is compatible with old versions of perl.
240
241 =head1 FUNCTIONS
242
243 =head2 author( $author )
244
245 Set the author to include in generated META files.  Can be a single entry, or
246 an arrayref.
247
248 =head2 manifest_include( $dir, $pattern )
249
250 Add a pattern to include files in the MANIFEST file, and thus in the generated
251 dist files.
252
253 The pattern can be either a regex, or a path suffix.  It will be applied to the
254 full path past the directory specified.
255
256 The default files that are always included are: F<.pm> and F<.pod> files in
257 F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
258 F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
259 the dist root, and all files in F<maint>.
260
261 =head1 AUTOGENERATED FILES
262
263 =over 4
264
265 =item F<MANIFEST.SKIP>
266
267 The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
268 explicitly allowed via C<manifest_include> or the included defaults.  It will be
269 created (or updated) at C<perl Makefile.PL> time.
270
271 =item F<README>
272
273 The F<README> file will be generated at dist generation time, inside the built
274 dist.  It will be generated using C<pod2text> on the main module.
275
276 If a F<README> file exists in the repo, it will be used directly instead of
277 generating the file.
278
279 =back
280
281 =head1 MAKE COMMMANDS
282
283 =head2 test
284
285 test will be adjusted to include F<xt/> tests by default.  This will only apply
286 for authors, not users installing from CPAN.
287
288 =head2 release
289
290 Releases the dist.  Before releasing, checks will be done on the dist using the
291 C<preflight> and C<releasetest> commands.
292
293 Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
294 It will also create a git tag for the release, and push the tag and branch.
295
296 =head2 preflight
297
298 Performs a number of checks on the files and repository, ensuring it is in a
299 sane state to do a release.  The checks are:
300
301 =over 4
302
303 =item * All version numbers match
304
305 =item * The F<MANIFEST> file is up to date
306
307 =item * The branch is correct
308
309 =item * There is no existing tag for the version
310
311 =item * There are no unmerged upstream changes
312
313 =item * There are no outstanding local changes
314
315 =item * There is an appropriate staged Changes heading
316
317 =item * cpan-upload is available
318
319 =back
320
321 =head2 releasetest
322
323 Test the dist preparing for a release.  This generates a dist dir and runs the
324 tests from inside it.  This ensures all appropriate files are included inside
325 the dist.  C<RELEASE_TESTING> will be set in the environment.
326
327 =head2 nextrelease
328
329 Adds an appropriate changelog heading for the release, and prompts to stage the
330 change.
331
332 =head2 bump
333
334 Bumps the version number.  This will try to preserve the length and format of
335 the version number.  The least significant digit will be incremented.
336
337 Optionally accepts a C<V> option to set the version to a specific value.
338
339 The version changes will automatically be committed.  Unstaged modifications to
340 the files will be left untouched.
341
342 =head2 bumpminor
343
344 Like bump, but increments the minor segment of the version.  This will treat
345 numeric versions as x.yyyzzz format, incrementing the yyy segment.
346
347 =head2 bumpmajor
348
349 Like bumpminor, but bumping the major segment.
350
351 =head2 refresh
352
353 Updates Distar and re-runs C<perl Makefile.PL>
354
355 =head1 SUPPORT
356
357 IRC: #web-simple on irc.perl.org
358
359 Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
360
361 Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
362
363 =head1 AUTHOR
364
365 mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
366
367 =head1 CONTRIBUTORS
368
369 haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
370
371 ether = Karen Etheridge (cpan:ETHER) <ether@cpan.org>
372
373 frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
374
375 Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
376
377 =head1 COPYRIGHT
378
379 Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
380 as listed above.
381
382 =head1 LICENSE
383
384 This library is free software and may be distributed under the same terms
385 as perl itself. See L<http://dev.perl.org/licenses/>.
386
387 =cut