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