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