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