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