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