include provides metadata
[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
26648fb0 149 $meta->{x_contributors} ||= do {
150 my $author_email =
151 join '|',
152 map quotemeta,
153 map /(\<[^<>]+\@[^<>]\>)/,
154 @$Author;
155 [
156 grep !/$author_email/,
157 map { chomp; s/^\s*\d+\s*//; utf8::decode($_); $_ }
158 `git shortlog -s --email HEAD`
159 ];
160 };
161
f9b5ed23 162 if ( eval { require CPAN::Meta; require Module::Metadata; } ) {
163 $meta->{provides} ||= do {
164 my $meta_obj = CPAN::Meta->new($meta, { lazy_validation => 1 });
165 my @files = `git ls-files`;
166 chomp @files;
167 my $provides = Module::Metadata->package_versions_from_directory('.', \@files);
168 for my $module (keys %$provides) {
169 delete $provides->{$module}
170 unless $meta_obj->should_index_package($module)
171 && $meta_obj->should_index_file($provides->{$module}{file});
172 }
173 $provides;
174 };
175 }
176
a8fd5adc 177 %$meta;
178 }
179
eddb1ce6 180 sub flush {
181 my $self = shift;
5371ff52 182 `git ls-files --error-unmatch MANIFEST.SKIP 2>&1`;
183 my $maniskip_tracked = !$?;
184
185 Distar::write_manifest_skip($self)
186 unless $maniskip_tracked;
b2ec6b47 187
188 my @results = @{$self->{RESULT}};
189 if ($MM_VER < 7) {
190 utf8::encode($_) for @results;
191 }
192 local $self->{RESULT} = \@results;
eddb1ce6 193 $self->SUPER::flush(@_);
194 }
195
7efea54c 196 sub special_targets {
197 my $self = shift;
198 my $targets = $self->SUPER::special_targets(@_);
f57289e3 199 my $phony_targets = join ' ', qw(
200 preflight
7b418de8 201 check-version
5baee32c 202 check-manifest
b0780d0a 203 check-cpan-upload
f57289e3 204 releasetest
205 release
206 readmefile
207 distmanicheck
208 nextrelease
209 refresh
210 bump
211 bumpmajor
212 bumpminor
213 );
214 $targets =~ s/^(\.PHONY *:.*)/$1 $phony_targets/m;
7efea54c 215 $targets;
216 }
217
42d0b436 218 sub init_dist {
219 my $self = shift;
220 my $pre_tar = $self->{TAR};
221 my $out = $self->SUPER::init_dist(@_);
222
223 my $tar = $self->{TAR};
224 my $gtar;
225 my $set_user;
226 my $version = `$tar --version`;
227 if ($version =~ /GNU tar/) {
228 $gtar = 1;
229 }
230 elsif (!$pre_tar && `gtar --version`) {
231 $tar = 'gtar';
232 $gtar = 1;
233 }
234 my $tarflags = $self->{TARFLAGS};
235 if (my ($flags) = $tarflags =~ /^-?([cvhlLf]+)$/) {
236 if ($flags =~ s/c// && $flags =~ s/f//) {
237 $tarflags = '--format=ustar -c'.$flags.'f';
238 if ($gtar) {
239 $tarflags = '--owner=0 --group=0 '.$tarflags;
240 $set_user = 1;
241 }
242 }
243 }
244
245 if (!$set_user) {
246 my $warn = '';
247 if ($> >= 2**21) {
248 $warn .= "uid ($>)";
249 }
250 if ($) >= 2**21) {
251 $warn .= ($warn ? ' and ' : '').'gid('.(0+$)).')';
252 }
253 if ($warn) {
254 warn "$warn too large! Max is ".(2**21-1).".\n"
255 ."Dist creation will likely fail. Install GNU tar to work around.\n";
256 }
257 }
258
259 $self->{TAR} = $tar;
260 $self->{TARFLAGS} = $tarflags;
261
262 $out;
263 }
264
ee801c00 265 sub tarfile_target {
266 my $self = shift;
267 my $out = $self->SUPER::tarfile_target(@_);
268 my $verify = <<'END_FRAG';
269 $(ABSPERLRUN) $(HELPERS)/verify-tarball $(DISTVNAME).tar $(DISTVNAME)/MANIFEST --tar="$(TAR)"
270END_FRAG
271 $out =~ s{(\$\(TAR\).*\n)}{$1$verify};
272 $out;
273 }
274
ca3b3b8a 275 sub dist_test {
276 my $self = shift;
8ab5ea70 277
66a2ecde 278 my $include = '';
279 if (open my $fh, '<', 'maint/Makefile.include') {
280 $include = "\n# --- Makefile.include:\n\n" . do { local $/; <$fh> };
281 $include =~ s/\n?\z/\n/;
282 }
b0401762 283
66a2ecde 284 my @bump_targets =
285 grep { $include !~ /^bump$_(?: +\w+)*:/m } ('', 'minor', 'major');
81d518f6 286
8aac77fc 287 my $distar = File::Spec->catdir(
288 File::Spec->catpath((File::Spec->splitpath(__FILE__))[0,1], ''),
289 File::Spec->updir,
290 );
291 my $helpers = File::Spec->catdir($distar, 'helpers');
292
66a2ecde 293 my %vars = (
8aac77fc 294 DISTAR => $self->quote_literal($distar),
295 HELPERS => $self->quote_literal($helpers),
2c75254f 296 REMAKE => join(' ', '$(PERLRUN)', '-I$(DISTAR)/lib', '-mDistar', 'Makefile.PL', map { $self->quote_literal($_) } @ARGV),
190976f8 297 BRANCH => $self->{BRANCH} ||= 'master',
9b920c5c 298 CHANGELOG => $self->{CHANGELOG} ||= 'Changes',
b0780d0a 299 DEV_NULL_STDOUT => ($self->{DEV_NULL} ? '>'.File::Spec->devnull : ''),
f0bbeff7 300 DISTTEST_MAKEFILE_PARAMS => '',
66a2ecde 301 );
302
f0bbeff7 303 my $dist_test = $self->SUPER::dist_test(@_);
304 $dist_test =~ s/(\bMakefile\.PL\b)/$1 \$(DISTTEST_MAKEFILE_PARAMS)/;
305
66a2ecde 306 join('',
f0bbeff7 307 $dist_test,
66a2ecde 308 "\n\n# --- Distar section:\n\n",
309 (map "$_ = $vars{$_}\n", sort keys %vars),
310 <<'END',
81d518f6 311
b0780d0a 312preflight: check-version check-manifest check-cpan-upload
8aac77fc 313 $(ABSPERLRUN) $(HELPERS)/preflight $(VERSION) --changelog=$(CHANGELOG) --branch=$(BRANCH)
7b418de8 314check-version:
8aac77fc 315 $(ABSPERLRUN) $(HELPERS)/check-version $(VERSION) $(TO_INST_PM) $(EXE_FILES)
5baee32c 316check-manifest:
8aac77fc 317 $(ABSPERLRUN) $(HELPERS)/check-manifest
b0780d0a 318check-cpan-upload:
319 $(NOECHO) cpan-upload -h $(DEV_NULL_STDOUT)
3e632431 320releasetest:
f0bbeff7 321 $(MAKE) disttest RELEASE_TESTING=1 DISTTEST_MAKEFILE_PARAMS="PREREQ_FATAL=1" PASTHRU="$(PASTHRU) TEST_FILES=\"$(TEST_FILES)\""
49beea48 322release: preflight
323 $(MAKE) releasetest
42e08a83 324 git commit -a -m "Release commit for $(VERSION)"
401ece0b 325 git tag v$(VERSION) -m "release v$(VERSION)"
49beea48 326 $(RM_RF) $(DISTVNAME)
327 $(MAKE) $(DISTVNAME).tar$(SUFFIX)
263b723e 328 $(NOECHO) $(MAKE) pushrelease FAKE_RELEASE=$(FAKE_RELEASE)
329pushrelease ::
330 $(NOECHO) $(NOOP)
331pushrelease$(FAKE_RELEASE) ::
65a1f7d9 332 cpan-upload $(DISTVNAME).tar$(SUFFIX)
2c636792 333 git push origin v$(VERSION) HEAD
5154970c 334distdir: readmefile
75f8311c 335readmefile: create_distdir
5c5deb0a 336 $(NOECHO) $(TEST_F) $(DISTVNAME)/README || $(MAKE) $(DISTVNAME)/README
f6286f60 337$(DISTVNAME)/README: $(VERSION_FROM)
338 $(NOECHO) $(MKPATH) $(DISTVNAME)
339 pod2text $(VERSION_FROM) >$(DISTVNAME)/README
8aac77fc 340 $(NOECHO) $(ABSPERLRUN) $(HELPERS)/add-to-manifest -d $(DISTVNAME) README
7934d553 341distsignature: readmefile
de048fa8 342disttest: distmanicheck
792c9e91 343distmanicheck: create_distdir
344 cd $(DISTVNAME) && $(ABSPERLRUN) "-MExtUtils::Manifest=manicheck" -e "exit manicheck"
e7a78651 345nextrelease:
8aac77fc 346 $(ABSPERLRUN) $(HELPERS)/add-changelog-heading --git $(VERSION) $(CHANGELOG)
0edb27b8 347refresh:
41e593ee 348 cd $(DISTAR) && git pull || $(TRUE)
e9f66489 349 $(RM_F) $(FIRST_MAKEFILE)
0edb27b8 350 $(REMAKE)
8ab5ea70 351END
66a2ecde 352 map(sprintf(<<'END', "bump$_", ($_ || '$(V)')), @bump_targets),
353%s:
8aac77fc 354 $(ABSPERLRUN) $(HELPERS)/bump-version --git $(VERSION) %s
66a2ecde 355 $(RM_F) $(FIRST_MAKEFILE)
356 $(REMAKE)
42e08a83 357END
66a2ecde 358 $include,
359 "\n",
360 );
42e08a83 361 }
362}
363
42e08a83 3641;
edb4539a 365__END__
366
367=head1 NAME
368
369Distar - Additions to ExtUtils::MakeMaker for dist authors
370
371=head1 SYNOPSIS
372
373F<Makefile.PL>:
374
375 use ExtUtils::MakeMaker;
83e12da3 376 (do './maint/Makefile.PL.include' or die $@) unless -f 'META.yml';
edb4539a 377
378 WriteMakefile(...);
379
380F<maint/Makefile.PL.include>:
381
382 BEGIN { -e 'Distar' or system("git clone git://git.shadowcat.co.uk/p5sagit/Distar.git") }
383 use lib 'Distar/lib';
384 use Distar 0.001;
385
386 author 'A. U. Thor <author@cpan.org>';
387
388 manifest_include t => 'test-helper.pl';
389 manifest_include corpus => '.txt';
390
391make commmands:
392
393 $ perl Makefile.PL
394 $ make bump # bump version
395 $ make bump V=2.000000 # bump to specific version
396 $ make bumpminor # bump minor version component
397 $ make bumpmajor # bump major version component
398 $ make nextrelease # add version heading to Changes file
399 $ make releasetest # build dist and test (with xt/ and RELEASE_TESTING=1)
400 $ make preflight # check that repo and file state is release ready
83e12da3 401 $ make release # check releasetest and preflight, commits and tags,
402 # builds and uploads to CPAN, and pushes commits and
403 # tag
404 $ make release FAKE_RELEASE=1
405 # builds a release INCLUDING committing and tagging,
406 # but does not upload to cpan or push anything to git
edb4539a 407
408=head1 DESCRIPTION
409
410L<ExtUtils::MakeMaker> works well enough as development tool for
411builting and testing, but using it to release is annoying and error prone.
412Distar adds just enough to L<ExtUtils::MakeMaker> for it to be a usable dist
413author tool. This includes extra commands for releasing and safety checks, and
414automatic generation of some files. It doesn't require any non-core modules and
415is compatible with old versions of perl.
416
417=head1 FUNCTIONS
418
419=head2 author( $author )
420
421Set the author to include in generated META files. Can be a single entry, or
422an arrayref.
423
424=head2 manifest_include( $dir, $pattern )
425
426Add a pattern to include files in the MANIFEST file, and thus in the generated
427dist files.
428
429The pattern can be either a regex, or a path suffix. It will be applied to the
430full path past the directory specified.
431
432The default files that are always included are: F<.pm> and F<.pod> files in
433F<lib>, F<.t> files in F<t> and F<xt>, F<.pm> files in F<t/lib> and F<xt/lib>,
434F<Changes>, F<MANIFEST>, F<README>, F<LICENSE>, F<META.yml>, and F<.PL> files in
435the dist root, and all files in F<maint>.
436
437=head1 AUTOGENERATED FILES
438
439=over 4
440
441=item F<MANIFEST.SKIP>
442
443The F<MANIFEST.SKIP> will be automatically generated to exclude any files not
444explicitly allowed via C<manifest_include> or the included defaults. It will be
445created (or updated) at C<perl Makefile.PL> time.
446
447=item F<README>
448
449The F<README> file will be generated at dist generation time, inside the built
450dist. It will be generated using C<pod2text> on the main module.
451
452If a F<README> file exists in the repo, it will be used directly instead of
453generating the file.
454
455=back
456
457=head1 MAKE COMMMANDS
458
459=head2 test
460
461test will be adjusted to include F<xt/> tests by default. This will only apply
462for authors, not users installing from CPAN.
463
464=head2 release
465
466Releases the dist. Before releasing, checks will be done on the dist using the
467C<preflight> and C<releasetest> commands.
468
469Releasing will generate a dist tarball and upload it to CPAN using cpan-upload.
470It will also create a git tag for the release, and push the tag and branch.
471
83e12da3 472=head3 FAKE_RELEASE
263b723e 473
474If release is run with FAKE_RELEASE=1 set, it will skip uploading to CPAN and
475pushing to git. A release commit will still be created and tagged locally.
476
edb4539a 477=head2 preflight
478
479Performs a number of checks on the files and repository, ensuring it is in a
480sane state to do a release. The checks are:
481
482=over 4
483
484=item * All version numbers match
485
486=item * The F<MANIFEST> file is up to date
487
488=item * The branch is correct
489
490=item * There is no existing tag for the version
491
492=item * There are no unmerged upstream changes
493
494=item * There are no outstanding local changes
495
496=item * There is an appropriate staged Changes heading
497
498=item * cpan-upload is available
499
500=back
501
502=head2 releasetest
503
504Test the dist preparing for a release. This generates a dist dir and runs the
505tests from inside it. This ensures all appropriate files are included inside
506the dist. C<RELEASE_TESTING> will be set in the environment.
507
508=head2 nextrelease
509
510Adds an appropriate changelog heading for the release, and prompts to stage the
511change.
512
513=head2 bump
514
515Bumps the version number. This will try to preserve the length and format of
83e12da3 516the version number. The least significant digit will be incremented. Versions
517with underscores will preserve the underscore in the same position.
edb4539a 518
519Optionally accepts a C<V> option to set the version to a specific value.
520
521The version changes will automatically be committed. Unstaged modifications to
522the files will be left untouched.
523
83e12da3 524=head3 V
525
526The V option will be passed along to the version bumping script. It can accept
527a space separated list of options, including an explicit version number.
528
529Options:
530
531=over 4
532
533=item --force
534
535Updates version numbers even if they do not match the current expected version
536number.
537
538=item --stable
539
540Attempts to convert the updated version to a stable version, removing any
541underscore.
542
543=item --alpha
544
545Attempts to convert the updated version to an alpha version, adding an
546underscore in an appropriate place.
547
548=back
549
edb4539a 550=head2 bumpminor
551
552Like bump, but increments the minor segment of the version. This will treat
553numeric versions as x.yyyzzz format, incrementing the yyy segment.
554
555=head2 bumpmajor
556
557Like bumpminor, but bumping the major segment.
558
559=head2 refresh
560
561Updates Distar and re-runs C<perl Makefile.PL>
562
563=head1 SUPPORT
564
565IRC: #web-simple on irc.perl.org
566
567Git repository: L<git://git.shadowcat.co.uk/p5sagit/Distar>
568
569Git browser: L<http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit/Distar.git;a=summary>
570
571=head1 AUTHOR
572
573mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
574
575=head1 CONTRIBUTORS
576
577haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
578
83e12da3 579ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
edb4539a 580
581frew - Arthur Axel "fREW" Schmidt (cpan:FREW) <frioux@gmail.com>
582
583Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
584
585=head1 COPYRIGHT
586
587Copyright (c) 2011-2015 the Distar L</AUTHOR> and L</CONTRIBUTORS>
588as listed above.
589
590=head1 LICENSE
591
592This library is free software and may be distributed under the same terms
593as perl itself. See L<http://dev.perl.org/licenses/>.
594
595=cut