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