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