when refreshing upstream, use push URL
[p5sagit/Distar.git] / lib / Distar.pm
CommitLineData
42e08a83 1package Distar;
362ec4af 2use strict;
3use warnings FATAL => 'all';
42e08a83 4use base qw(Exporter);
ca3b3b8a 5use ExtUtils::MakeMaker ();
6use ExtUtils::MM ();
8aac77fc 7use File::Spec ();
31aab5e1 8use File::Basename ();
42e08a83 9
2b085b99 10our $VERSION = '0.003000';
c562f7dd 11$VERSION =~ tr/_//d;
37e295d8 12
e076eedb 13my $MM_VER = eval $ExtUtils::MakeMaker::VERSION;
14
42e08a83 15our @EXPORT = qw(
be032607 16 author manifest_include readme_generator
42e08a83 17);
18
19sub import {
2852faf3 20 strict->import;
21 warnings->import(FATAL => 'all');
f5cc9f53 22 if (!(@MM::ISA == 1 && $MM::ISA[0] eq 'Distar::MM')) {
23 @Distar::MM::ISA = @MM::ISA;
24 @MM::ISA = qw(Distar::MM);
25 }
788b7f10 26 goto &Exporter::import;
42e08a83 27}
28
e076eedb 29sub author {
30 our $Author = shift;
31 $Author = [ $Author ]
32 if !ref $Author;
adc721c1 33 1;
e076eedb 34}
42e08a83 35
36our @Manifest = (
37 'lib' => '.pm',
7b9318ce 38 'lib' => '.pod',
42e08a83 39 't' => '.t',
40 't/lib' => '.pm',
41 'xt' => '.t',
42 'xt/lib' => '.pm',
1d950b4a 43 '' => qr{[^/]*\.PL},
f1ef306e 44 '' => qr{Changes|MANIFEST|README|LICENSE|META\.yml},
42e08a83 45 'maint' => qr{[^.].*},
46);
47
48sub manifest_include {
49 push @Manifest, @_;
adc721c1 50 1;
42e08a83 51}
52
6e83c113 53sub readme_generator {
f6286f60 54 die "readme_generator unsupported" if @_ && $_[0];
6e83c113 55}
56
42e08a83 57sub write_manifest_skip {
0fa73f76 58 my ($mm) = @_;
42e08a83 59 my @files = @Manifest;
60 my @parts;
61 while (my ($dir, $spec) = splice(@files, 0, 2)) {
62 my $re = ($dir ? $dir.'/' : '').
63 ((ref($spec) eq 'Regexp')
64 ? $spec
65 : !ref($spec)
66 ? ".*\Q${spec}\E"
a3e39afd 67 # print ref as well as stringification in case of overload ""
42e08a83 68 : die "spec must be string or regexp, was: ${spec} (${\ref $spec})");
69 push @parts, $re;
70 }
0fa73f76 71 my $dist_name = $mm->{DISTNAME};
72 my $include = join '|', map "${_}\$", @parts;
73 my $final = "^(?:\Q$dist_name\E-v?[0-9_.]+/|(?!$include))";
19916679 74 open my $skip, '>', 'MANIFEST.SKIP'
75 or die "can't open MANIFEST.SKIP: $!";
42e08a83 76 print $skip "${final}\n";
77 close $skip;
78}
79
ca3b3b8a 80{
81 package Distar::MM;
ca3b3b8a 82
18d3ef19 83 sub _gen_make_section {
84 return join '',
85 map {
86 my @lines;
87 if (ref) {
88 @lines = @$_;
89 s/^\t?/\t/mg for @lines;
90 }
91 else {
92 @lines = $_;
93 }
94 s/\n?\z/\n/ for @lines;
95 @lines;
96 } @_;
97 }
98
ca3b3b8a 99 sub new {
100 my ($class, $args) = @_;
4762e03a 101 my %test = %{$args->{test}||{}};
102 my $tests = $test{TESTS} || 't/*.t';
103 $tests !~ /\b\Q$_\E\b/ and $tests .= " $_"
104 for 'xt/*.t', 'xt/*/*.t';
105 $test{TESTS} = $tests;
ca3b3b8a 106 return $class->SUPER::new({
180e908e 107 LICENSE => 'perl_5',
53e1282f 108 MIN_PERL_VERSION => '5.006',
15f10a18 109 ($Distar::Author ? (
d0f7a429 110 AUTHOR => ($MM_VER >= 6.5702 ? $Distar::Author : join(', ', @$Distar::Author)),
111 ) : ()),
1f136c70 112 (exists $args->{ABSTRACT} ? () : (ABSTRACT_FROM => $args->{VERSION_FROM})),
4a4bb570 113 %$args,
4762e03a 114 test => \%test,
0a3fdb23 115 realclean => { FILES => (
116 ($args->{realclean}{FILES}||'')
117 . ' Distar/ MANIFEST.SKIP MANIFEST MANIFEST.bak'
118 ) },
ca3b3b8a 119 });
120 }
121
eddb1ce6 122 sub flush {
123 my $self = shift;
5371ff52 124 `git ls-files --error-unmatch MANIFEST.SKIP 2>&1`;
125 my $maniskip_tracked = !$?;
126
127 Distar::write_manifest_skip($self)
128 unless $maniskip_tracked;
eddb1ce6 129 $self->SUPER::flush(@_);
130 }
131
7efea54c 132 sub special_targets {
133 my $self = shift;
134 my $targets = $self->SUPER::special_targets(@_);
f57289e3 135 my $phony_targets = join ' ', qw(
136 preflight
7b418de8 137 check-version
5baee32c 138 check-manifest
b0780d0a 139 check-cpan-upload
f57289e3 140 releasetest
141 release
142 readmefile
143 distmanicheck
144 nextrelease
145 refresh
146 bump
147 bumpmajor
148 bumpminor
149 );
150 $targets =~ s/^(\.PHONY *:.*)/$1 $phony_targets/m;
7efea54c 151 $targets;
152 }
153
42d0b436 154 sub init_dist {
155 my $self = shift;
42d0b436 156 my $out = $self->SUPER::init_dist(@_);
157
87c14395 158 my $dn = File::Spec->devnull;
42d0b436 159 my $tar = $self->{TAR};
c3410697 160
42d0b436 161 my $tarflags = $self->{TARFLAGS};
162 if (my ($flags) = $tarflags =~ /^-?([cvhlLf]+)$/) {
163 if ($flags =~ s/c// && $flags =~ s/f//) {
164 $tarflags = '--format=ustar -c'.$flags.'f';
c3410697 165 my $me = __FILE__;
166 for my $options ('--owner=0 --group=0', '--uid=0 --gid=0') {
167 my $try = `$tar -c $options "$me" 2>$dn`;
168 if (length $try) {
169 $tarflags = "$options $tarflags";
170 last;
171 }
9153428b 172 }
42d0b436 173 }
174 }
175
176 $self->{TAR} = $tar;
177 $self->{TARFLAGS} = $tarflags;
178
179 $out;
180 }
181
24612483 182 sub libscan {
183 my $self = shift;
184 my ($path) = @_;
185
186 # default setup for Distar involves checking it out as a subdirectory at
187 # the top of the dist. Without NORECURS, EUMM would try to include
188 # Distar's Makefile.PL. Prevent EUMM from looking inside.
189 return ''
190 if $path eq 'Distar';
191
192 $self->SUPER::libscan(@_);
193 }
194
ee801c00 195 sub tarfile_target {
196 my $self = shift;
197 my $out = $self->SUPER::tarfile_target(@_);
18d3ef19 198 my $verify = _gen_make_section([
199 '$(ABSPERLRUN) $(HELPERS)/verify-tarball $(DISTVNAME).tar $(DISTVNAME)/MANIFEST --tar="$(TAR)"',
200 ]);
ee801c00 201 $out =~ s{(\$\(TAR\).*\n)}{$1$verify};
202 $out;
203 }
204
ca3b3b8a 205 sub dist_test {
206 my $self = shift;
8ab5ea70 207
7e3a1558 208 my $distar_lib = File::Basename::dirname(__FILE__);
3cfbbf37 209 my $helpers = File::Spec->catdir($distar_lib, 'Distar', 'helpers');
8aac77fc 210
c97878e5 211 my $licenses = $self->{LICENSE} || $self->{META_ADD}{license} || $self->{META_MERGE}{license};
212 my $authors = $self->{AUTHOR};
213 $_ = ref $_ ? $_ : [$_ || ()]
214 for $licenses, $authors;
215
66a2ecde 216 my %vars = (
7e3a1558 217 DISTAR_LIB => $self->quote_literal($distar_lib),
8aac77fc 218 HELPERS => $self->quote_literal($helpers),
7e3a1558 219 REMAKE => join(' ', '$(PERLRUN)', '-I$(DISTAR_LIB)', '-MDistar', 'Makefile.PL', map { $self->quote_literal($_) } @ARGV),
190976f8 220 BRANCH => $self->{BRANCH} ||= 'master',
9b920c5c 221 CHANGELOG => $self->{CHANGELOG} ||= 'Changes',
b0780d0a 222 DEV_NULL_STDOUT => ($self->{DEV_NULL} ? '>'.File::Spec->devnull : ''),
f0bbeff7 223 DISTTEST_MAKEFILE_PARAMS => '',
c97878e5 224 AUTHORS => $self->quote_literal(join(', ', @$authors)),
225 LICENSES => join(' ', map $self->quote_literal($_), @$licenses),
c6b7b384 226 GET_CHANGELOG => '$(ABSPERLRUN) $(HELPERS)/get-changelog $(VERSION) $(CHANGELOG)',
7e3a1558 227 UPDATE_DISTAR => (
228 -e File::Spec->catdir($distar_lib, File::Spec->updir, '.git')
229 ? 'git -C $(DISTAR_LIB) pull'
230 : '$(ECHO) "Distar code is not in a git repo, unable to update!"'
231 ),
82d5ea32 232 README_FROM => $self->{ABSTRACT_FROM} || $self->{VERSION_FROM},
66a2ecde 233 );
234
f0bbeff7 235 my $dist_test = $self->SUPER::dist_test(@_);
236 $dist_test =~ s/(\bMakefile\.PL\b)/$1 \$(DISTTEST_MAKEFILE_PARAMS)/;
237
77ece1ed 238 my $include = '';
239 if (open my $fh, '<', 'maint/Makefile.include') {
240 $include = do { local $/; <$fh> };
241 $include =~ s/\n?\z/\n/;
242 }
81d518f6 243
77ece1ed 244 my @out;
245
66243909 246 push @out, (
247 'preflight: check-version check-manifest check-cpan-upload' => [
248 '$(ABSPERLRUN) $(HELPERS)/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)',
249 ],
250 'check-version:' => [
251 '$(ABSPERLRUN) $(HELPERS)/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)',
252 ],
253 'check-manifest:' => [
254 '$(ABSPERLRUN) $(HELPERS)/check-manifest',
255 ],
256 'check-cpan-upload:' => [
257 '$(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)',
258 ],
259 'releasetest:' => [
260 '$(MAKE) disttest RELEASE_TESTING=1 DISTTEST_MAKEFILE_PARAMS="PREREQ_FATAL=1" PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""',
261 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE || $(ECHO) "Failed to generate $(DISTVNAME)/LICENSE!" >&2',
262 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE',
263 ],
264 'release: preflight' => [
265 '$(MAKE) releasetest',
266 '$(GET_CHANGELOG) -p"Release commit for $(VERSION)" | git commit -a -F -',
267 '$(GET_CHANGELOG) -p"release v$(VERSION)" | git tag -a -F - "v$(VERSION)"',
268 '$(RM_RF) $(DISTVNAME)',
269 '$(MAKE) $(DISTVNAME).tar$(SUFFIX)',
270 '$(NOECHO) $(MAKE) pushrelease FAKE_RELEASE=$(FAKE_RELEASE)',
271 ],
272 'pushrelease ::' => [
273 '$(NOECHO) $(NOOP)',
274 ],
275 'pushrelease$(FAKE_RELEASE) ::' => [
276 'cpan-upload $(DISTVNAME).tar$(SUFFIX)',
277 'git push origin v$(VERSION) HEAD',
278 ],
279 'distdir: readmefile licensefile',
280 'readmefile: create_distdir' => [
281 '$(NOECHO) $(TEST_F) $(DISTVNAME)/README || $(MAKE) $(DISTVNAME)/README',
282 ],
82d5ea32 283 '$(DISTVNAME)/README: $(README_FROM)' => [
66243909 284 '$(NOECHO) $(MKPATH) $(DISTVNAME)',
82d5ea32 285 'pod2text $(README_FROM) >$(DISTVNAME)/README',
66243909 286 '$(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) README',
287 ],
288 'distsignature: readmefile licensefile',
289 'licensefile: create_distdir' => [
290 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE || $(MAKE) $(DISTVNAME)/LICENSE || $(TRUE)',
291 ],
292 '$(DISTVNAME)/LICENSE: Makefile.PL' => [
293 '$(NOECHO) $(MKPATH) $(DISTVNAME)',
294 '$(ABSPERLRUN) $(HELPERS)/generate-license -o $(DISTVNAME)/LICENSE $(AUTHORS) $(LICENSES)',
295 '$(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) LICENSE',
296 ],
297 'disttest: distmanicheck',
298 'distmanicheck: create_distdir' => [
299 $self->cd('$(DISTVNAME)',
300 '$(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"',
301 ),
302 ],
303 'nextrelease:' => [
304 '$(ABSPERLRUN) $(HELPERS)/add-changelog-heading --git $(VERSION) $(CHANGELOG)',
305 ],
306 'refresh:' => [
307 '$(UPDATE_DISTAR)',
308 '$(RM_F) $(FIRST_MAKEFILE)',
309 '$(REMAKE)',
310 ],
311 );
312
77ece1ed 313 my @bump_targets =
314 grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
315
66243909 316 push @out, map +(
317 "bump$_:" => [
318 '$(ABSPERLRUN) $(HELPERS)/bump-version --git $(VERSION) '.($_ || '$(V)'),
319 '$(RM_F) $(FIRST_MAKEFILE)',
320 '$(REMAKE)',
321 ],
322 ), @bump_targets;
77ece1ed 323
324 join('',
325 $dist_test,
326 "\n\n# --- Distar section:\n\n",
327 (map "$_ = $vars{$_}\n", sort keys %vars),
328 "\n",
18d3ef19 329 _gen_make_section(@out),
77ece1ed 330 ($include ? (
331 "\n",
332 "# --- Makefile.include:\n",
333 "\n",
334 $include,
335 "\n"
336 ) : ()),
66a2ecde 337 "\n",
338 );
42e08a83 339 }
340}
341
42e08a83 3421;
edb4539a 343__END__
344
345=head1 NAME
346
347Distar - Additions to ExtUtils::MakeMaker for dist authors
348
349=head1 SYNOPSIS
350
351F<Makefile.PL>:
352
353 use ExtUtils::MakeMaker;
83e12da3 354 (do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
edb4539a 355
356 WriteMakefile(...);
357
358F<maint/Makefile.PL.include>:
359
f12b221f 360 BEGIN { -e 'Distar' or system qw(git clone https://github.com/p5sagit/Distar.git) }
edb4539a 361 use lib 'Distar/lib';
362 use Distar 0.001;
363
364 author 'A. U. Thor <author@cpan.org>';
365
366 manifest_include t => 'test-helper.pl';
367 manifest_include corpus => '.txt';
368
369make commmands:
370
371 $ perl Makefile.PL
372 $ make bump # bump version
373 $ make bump V=2.000000 # bump to specific version
374 $ make bumpminor # bump minor version component
375 $ make bumpmajor # bump major version component
376 $ make nextrelease # add version heading to Changes file
377 $ make releasetest # build dist and test (with xt/ and RELEASE_TESTING=1)
378 $ make preflight # check that repo and file state is release ready
83e12da3 379 $ make release # check releasetest and preflight, commits and tags,
380 # builds and uploads to CPAN, and pushes commits and
381 # tag
382 $ make release FAKE_RELEASE=1
383 # builds a release INCLUDING committing and tagging,
384 # but does not upload to cpan or push anything to git
edb4539a 385
386=head1 DESCRIPTION
387
388L<ExtUtils::MakeMaker> works well enough as development tool for
389builting and testing, but using it to release is annoying and error prone.
390Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
391author tool. This includes extra commands for releasing and safety checks, and
392automatic generation of some files. It doesn't require any non-core modules and
393is compatible with old versions of perl.
394
395=head1 FUNCTIONS
396
397=head2 author( $author )
398
399Set the author to include in generated META files. Can be a single entry, or
400an arrayref.
401
402=head2 manifest_include( $dir, $pattern )
403
404Add a pattern to include files in the MANIFEST file, and thus in the generated
405dist files.
406
407The pattern can be either a regex, or a path suffix. It will be applied to the
408full path past the directory specified.
409
410The default files that are always included are: F<.pm> and F<.pod> files in
411F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
412F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
413the dist root, and all files in F<maint>.
414
415=head1 AUTOGENERATED FILES
416
417=over 4
418
419=item F<MANIFEST.SKIP>
420
421The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
422explicitly allowed via C<manifest_include> or the included defaults. It will be
423created (or updated) at C<perl Makefile.PL> time.
424
425=item F<README>
426
427The F<README> file will be generated at dist generation time, inside the built
428dist. It will be generated using C<pod2text> on the main module.
429
430If a F<README> file exists in the repo, it will be used directly instead of
431generating the file.
432
433=back
434
435=head1 MAKE COMMMANDS
436
437=head2 test
438
439test will be adjusted to include F<xt/> tests by default. This will only apply
440for authors, not users installing from CPAN.
441
442=head2 release
443
444Releases the dist. Before releasing, checks will be done on the dist using the
445C<preflight> and C<releasetest> commands.
446
447Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
448It will also create a git tag for the release, and push the tag and branch.
449
83e12da3 450=head3 FAKE_RELEASE
263b723e 451
452If release is run with FAKE_RELEASE=1 set, it will skip uploading to CPAN and
453pushing to git. A release commit will still be created and tagged locally.
454
edb4539a 455=head2 preflight
456
457Performs a number of checks on the files and repository, ensuring it is in a
458sane state to do a release. The checks are:
459
460=over 4
461
462=item * All version numbers match
463
464=item * The F<MANIFEST> file is up to date
465
466=item * The branch is correct
467
468=item * There is no existing tag for the version
469
470=item * There are no unmerged upstream changes
471
472=item * There are no outstanding local changes
473
474=item * There is an appropriate staged Changes heading
475
476=item * cpan-upload is available
477
478=back
479
480=head2 releasetest
481
482Test the dist preparing for a release. This generates a dist dir and runs the
483tests from inside it. This ensures all appropriate files are included inside
484the dist. C<RELEASE_TESTING> will be set in the environment.
485
486=head2 nextrelease
487
488Adds an appropriate changelog heading for the release, and prompts to stage the
489change.
490
491=head2 bump
492
493Bumps the version number. This will try to preserve the length and format of
83e12da3 494the version number. The least significant digit will be incremented. Versions
495with underscores will preserve the underscore in the same position.
edb4539a 496
497Optionally accepts a C<V> option to set the version to a specific value.
498
499The version changes will automatically be committed. Unstaged modifications to
500the files will be left untouched.
501
83e12da3 502=head3 V
503
504The V option will be passed along to the version bumping script. It can accept
505a space separated list of options, including an explicit version number.
506
507Options:
508
509=over 4
510
511=item --force
512
513Updates version numbers even if they do not match the current expected version
514number.
515
516=item --stable
517
518Attempts to convert the updated version to a stable version, removing any
519underscore.
520
521=item --alpha
522
523Attempts to convert the updated version to an alpha version, adding an
524underscore in an appropriate place.
525
526=back
527
edb4539a 528=head2 bumpminor
529
530Like bump, but increments the minor segment of the version. This will treat
531numeric versions as x.yyyzzz format, incrementing the yyy segment.
532
533=head2 bumpmajor
534
535Like bumpminor, but bumping the major segment.
536
537=head2 refresh
538
539Updates Distar and re-runs C<perl Makefile.PL>
540
541=head1 SUPPORT
542
543IRC: #web-simple on irc.perl.org
544
545Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
546
547Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
548
549=head1 AUTHOR
550
551mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
552
553=head1 CONTRIBUTORS
554
555haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
556
83e12da3 557ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
edb4539a 558
559frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
560
561Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
562
563=head1 COPYRIGHT
564
565Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
566as listed above.
567
568=head1 LICENSE
569
570This library is free software and may be distributed under the same terms
571as perl itself. See L<http://dev.perl.org/licenses/>.
572
573=cut