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