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