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