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