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