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