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