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