update git clone urls
[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 ),
66a2ecde 232 );
233
f0bbeff7 234 my $dist_test = $self->SUPER::dist_test(@_);
235 $dist_test =~ s/(\bMakefile\.PL\b)/$1 \$(DISTTEST_MAKEFILE_PARAMS)/;
236
77ece1ed 237 my $include = '';
238 if (open my $fh, '<', 'maint/Makefile.include') {
239 $include = do { local $/; <$fh> };
240 $include =~ s/\n?\z/\n/;
241 }
81d518f6 242
77ece1ed 243 my @out;
244
66243909 245 push @out, (
246 'preflight: check-version check-manifest check-cpan-upload' => [
247 '$(ABSPERLRUN) $(HELPERS)/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)',
248 ],
249 'check-version:' => [
250 '$(ABSPERLRUN) $(HELPERS)/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)',
251 ],
252 'check-manifest:' => [
253 '$(ABSPERLRUN) $(HELPERS)/check-manifest',
254 ],
255 'check-cpan-upload:' => [
256 '$(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)',
257 ],
258 'releasetest:' => [
259 '$(MAKE) disttest RELEASE_TESTING=1 DISTTEST_MAKEFILE_PARAMS="PREREQ_FATAL=1" PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""',
260 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE || $(ECHO) "Failed to generate $(DISTVNAME)/LICENSE!" >&2',
261 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE',
262 ],
263 'release: preflight' => [
264 '$(MAKE) releasetest',
265 '$(GET_CHANGELOG) -p"Release commit for $(VERSION)" | git commit -a -F -',
266 '$(GET_CHANGELOG) -p"release v$(VERSION)" | git tag -a -F - "v$(VERSION)"',
267 '$(RM_RF) $(DISTVNAME)',
268 '$(MAKE) $(DISTVNAME).tar$(SUFFIX)',
269 '$(NOECHO) $(MAKE) pushrelease FAKE_RELEASE=$(FAKE_RELEASE)',
270 ],
271 'pushrelease ::' => [
272 '$(NOECHO) $(NOOP)',
273 ],
274 'pushrelease$(FAKE_RELEASE) ::' => [
275 'cpan-upload $(DISTVNAME).tar$(SUFFIX)',
276 'git push origin v$(VERSION) HEAD',
277 ],
278 'distdir: readmefile licensefile',
279 'readmefile: create_distdir' => [
280 '$(NOECHO) $(TEST_F) $(DISTVNAME)/README || $(MAKE) $(DISTVNAME)/README',
281 ],
282 '$(DISTVNAME)/README: $(VERSION_FROM)' => [
283 '$(NOECHO) $(MKPATH) $(DISTVNAME)',
284 'pod2text $(VERSION_FROM) >$(DISTVNAME)/README',
285 '$(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) README',
286 ],
287 'distsignature: readmefile licensefile',
288 'licensefile: create_distdir' => [
289 '$(NOECHO) $(TEST_F) $(DISTVNAME)/LICENSE || $(MAKE) $(DISTVNAME)/LICENSE || $(TRUE)',
290 ],
291 '$(DISTVNAME)/LICENSE: Makefile.PL' => [
292 '$(NOECHO) $(MKPATH) $(DISTVNAME)',
293 '$(ABSPERLRUN) $(HELPERS)/generate-license -o $(DISTVNAME)/LICENSE $(AUTHORS) $(LICENSES)',
294 '$(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) LICENSE',
295 ],
296 'disttest: distmanicheck',
297 'distmanicheck: create_distdir' => [
298 $self->cd('$(DISTVNAME)',
299 '$(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"',
300 ),
301 ],
302 'nextrelease:' => [
303 '$(ABSPERLRUN) $(HELPERS)/add-changelog-heading --git $(VERSION) $(CHANGELOG)',
304 ],
305 'refresh:' => [
306 '$(UPDATE_DISTAR)',
307 '$(RM_F) $(FIRST_MAKEFILE)',
308 '$(REMAKE)',
309 ],
310 );
311
77ece1ed 312 my @bump_targets =
313 grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
314
66243909 315 push @out, map +(
316 "bump$_:" => [
317 '$(ABSPERLRUN) $(HELPERS)/bump-version --git $(VERSION) '.($_ || '$(V)'),
318 '$(RM_F) $(FIRST_MAKEFILE)',
319 '$(REMAKE)',
320 ],
321 ), @bump_targets;
77ece1ed 322
323 join('',
324 $dist_test,
325 "\n\n# --- Distar section:\n\n",
326 (map "$_ = $vars{$_}\n", sort keys %vars),
327 "\n",
18d3ef19 328 _gen_make_section(@out),
77ece1ed 329 ($include ? (
330 "\n",
331 "# --- Makefile.include:\n",
332 "\n",
333 $include,
334 "\n"
335 ) : ()),
66a2ecde 336 "\n",
337 );
42e08a83 338 }
339}
340
42e08a83 3411;
edb4539a 342__END__
343
344=head1 NAME
345
346Distar - Additions to ExtUtils::MakeMaker for dist authors
347
348=head1 SYNOPSIS
349
350F<Makefile.PL>:
351
352 use ExtUtils::MakeMaker;
83e12da3 353 (do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
edb4539a 354
355 WriteMakefile(...);
356
357F<maint/Makefile.PL.include>:
358
f12b221f 359 BEGIN { -e 'Distar' or system qw(git clone https://github.com/p5sagit/Distar.git) }
edb4539a 360 use lib 'Distar/lib';
361 use Distar 0.001;
362
363 author 'A. U. Thor <author@cpan.org>';
364
365 manifest_include t => 'test-helper.pl';
366 manifest_include corpus => '.txt';
367
368make commmands:
369
370 $ perl Makefile.PL
371 $ make bump # bump version
372 $ make bump V=2.000000 # bump to specific version
373 $ make bumpminor # bump minor version component
374 $ make bumpmajor # bump major version component
375 $ make nextrelease # add version heading to Changes file
376 $ make releasetest # build dist and test (with xt/ and RELEASE_TESTING=1)
377 $ make preflight # check that repo and file state is release ready
83e12da3 378 $ make release # check releasetest and preflight, commits and tags,
379 # builds and uploads to CPAN, and pushes commits and
380 # tag
381 $ make release FAKE_RELEASE=1
382 # builds a release INCLUDING committing and tagging,
383 # but does not upload to cpan or push anything to git
edb4539a 384
385=head1 DESCRIPTION
386
387L<ExtUtils::MakeMaker> works well enough as development tool for
388builting and testing, but using it to release is annoying and error prone.
389Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
390author tool. This includes extra commands for releasing and safety checks, and
391automatic generation of some files. It doesn't require any non-core modules and
392is compatible with old versions of perl.
393
394=head1 FUNCTIONS
395
396=head2 author( $author )
397
398Set the author to include in generated META files. Can be a single entry, or
399an arrayref.
400
401=head2 manifest_include( $dir, $pattern )
402
403Add a pattern to include files in the MANIFEST file, and thus in the generated
404dist files.
405
406The pattern can be either a regex, or a path suffix. It will be applied to the
407full path past the directory specified.
408
409The default files that are always included are: F<.pm> and F<.pod> files in
410F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
411F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
412the dist root, and all files in F<maint>.
413
414=head1 AUTOGENERATED FILES
415
416=over 4
417
418=item F<MANIFEST.SKIP>
419
420The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
421explicitly allowed via C<manifest_include> or the included defaults. It will be
422created (or updated) at C<perl Makefile.PL> time.
423
424=item F<README>
425
426The F<README> file will be generated at dist generation time, inside the built
427dist. It will be generated using C<pod2text> on the main module.
428
429If a F<README> file exists in the repo, it will be used directly instead of
430generating the file.
431
432=back
433
434=head1 MAKE COMMMANDS
435
436=head2 test
437
438test will be adjusted to include F<xt/> tests by default. This will only apply
439for authors, not users installing from CPAN.
440
441=head2 release
442
443Releases the dist. Before releasing, checks will be done on the dist using the
444C<preflight> and C<releasetest> commands.
445
446Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
447It will also create a git tag for the release, and push the tag and branch.
448
83e12da3 449=head3 FAKE_RELEASE
263b723e 450
451If release is run with FAKE_RELEASE=1 set, it will skip uploading to CPAN and
452pushing to git. A release commit will still be created and tagged locally.
453
edb4539a 454=head2 preflight
455
456Performs a number of checks on the files and repository, ensuring it is in a
457sane state to do a release. The checks are:
458
459=over 4
460
461=item * All version numbers match
462
463=item * The F<MANIFEST> file is up to date
464
465=item * The branch is correct
466
467=item * There is no existing tag for the version
468
469=item * There are no unmerged upstream changes
470
471=item * There are no outstanding local changes
472
473=item * There is an appropriate staged Changes heading
474
475=item * cpan-upload is available
476
477=back
478
479=head2 releasetest
480
481Test the dist preparing for a release. This generates a dist dir and runs the
482tests from inside it. This ensures all appropriate files are included inside
483the dist. C<RELEASE_TESTING> will be set in the environment.
484
485=head2 nextrelease
486
487Adds an appropriate changelog heading for the release, and prompts to stage the
488change.
489
490=head2 bump
491
492Bumps the version number. This will try to preserve the length and format of
83e12da3 493the version number. The least significant digit will be incremented. Versions
494with underscores will preserve the underscore in the same position.
edb4539a 495
496Optionally accepts a C<V> option to set the version to a specific value.
497
498The version changes will automatically be committed. Unstaged modifications to
499the files will be left untouched.
500
83e12da3 501=head3 V
502
503The V option will be passed along to the version bumping script. It can accept
504a space separated list of options, including an explicit version number.
505
506Options:
507
508=over 4
509
510=item --force
511
512Updates version numbers even if they do not match the current expected version
513number.
514
515=item --stable
516
517Attempts to convert the updated version to a stable version, removing any
518underscore.
519
520=item --alpha
521
522Attempts to convert the updated version to an alpha version, adding an
523underscore in an appropriate place.
524
525=back
526
edb4539a 527=head2 bumpminor
528
529Like bump, but increments the minor segment of the version. This will treat
530numeric versions as x.yyyzzz format, incrementing the yyy segment.
531
532=head2 bumpmajor
533
534Like bumpminor, but bumping the major segment.
535
536=head2 refresh
537
538Updates Distar and re-runs C<perl Makefile.PL>
539
540=head1 SUPPORT
541
542IRC: #web-simple on irc.perl.org
543
544Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
545
546Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
547
548=head1 AUTHOR
549
550mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
551
552=head1 CONTRIBUTORS
553
554haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
555
83e12da3 556ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
edb4539a 557
558frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
559
560Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
561
562=head1 COPYRIGHT
563
564Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
565as listed above.
566
567=head1 LICENSE
568
569This library is free software and may be distributed under the same terms
570as perl itself. See L<http://dev.perl.org/licenses/>.
571
572=cut