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