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