File Coverage

blib/lib/Dist/Zilla/Plugin/Conflicts.pm
Criterion Covered Total %
statement 79 91 86.8
branch 7 12 58.3
condition n/a
subroutine 19 20 95.0
pod 0 4 0.0
total 105 127 82.6


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::Conflicts;
2              
3 1     1   3694184 use strict;
  1         3  
  1         33  
4 1     1   6 use warnings;
  1         2  
  1         28  
5 1     1   5 use namespace::autoclean;
  1         3  
  1         7  
6              
7             our $VERSION = '0.20';
8              
9 1     1   590 use Dist::CheckConflicts 0.02 ();
  1         1821  
  1         30  
10 1     1   7 use Dist::Zilla 4.0 ();
  1         18  
  1         20  
11 1     1   5 use Dist::Zilla::File::InMemory;
  1         3  
  1         23  
12 1     1   453 use Dist::Zilla::File::FromCode;
  1         70002  
  1         35  
13              
14 1     1   7 use Moose;
  1         2  
  1         7  
15              
16             with qw(
17             Dist::Zilla::Role::FileGatherer
18             Dist::Zilla::Role::InstallTool
19             Dist::Zilla::Role::MetaProvider
20             Dist::Zilla::Role::PrereqSource
21             Dist::Zilla::Role::TextTemplate
22             );
23              
24             has _conflicts => (
25             is => 'ro',
26             isa => 'HashRef',
27             default => sub { {} },
28             );
29              
30             has _script => (
31             is => 'ro',
32             isa => 'Str',
33             predicate => '_has_script',
34             );
35              
36             has _conflicts_module_name => (
37             is => 'ro',
38             isa => 'Str',
39             init_arg => undef,
40             lazy => 1,
41             builder => '_build_conflicts_module_name',
42             );
43              
44             has _conflicts_module_path => (
45             is => 'ro',
46             isa => 'Str',
47             init_arg => undef,
48             lazy => 1,
49             builder => '_build_conflicts_module_path',
50             );
51              
52             around BUILDARGS => sub {
53             my $orig = shift;
54             my $class = shift;
55              
56             my $args = $class->$orig(@_);
57              
58             my $zilla = delete $args->{zilla};
59             my $name = delete $args->{plugin_name};
60             my $bin = delete $args->{'-script'};
61              
62             return {
63             zilla => $zilla,
64             plugin_name => $name,
65             ( defined $bin ? ( _script => $bin ) : () ),
66             _conflicts => $args,
67             };
68             };
69              
70             sub _build_conflicts_module_name {
71 1     1   3 my $self = shift;
72              
73 1         27 ( my $base = $self->zilla()->name() ) =~ s/-/::/g;
74              
75 1         67 return $base . '::Conflicts';
76             }
77              
78             sub _build_conflicts_module_path {
79 1     1   3 my $self = shift;
80              
81 1         29 my $path = join '/', split /-/, $self->zilla()->name();
82              
83 1         71 return "lib/$path/Conflicts.pm";
84             }
85              
86             sub register_prereqs {
87 1     1 0 5186 my ($self) = @_;
88              
89 1         30 $self->zilla->register_prereqs(
90             { phase => 'configure' },
91             'Dist::CheckConflicts' => '0.02',
92             );
93              
94 1         342 $self->zilla->register_prereqs(
95             { phase => 'runtime' },
96             'Dist::CheckConflicts' => '0.02',
97             );
98             }
99              
100             sub gather_files {
101 1     1 0 63431 my $self = shift;
102              
103             $self->add_file(
104             Dist::Zilla::File::FromCode->new(
105             name => $self->_conflicts_module_path(),
106 1     1   7278 code => sub { $self->_generate_conflicts_module },
107             )
108 1         36 );
109              
110 1 50       964 if ( $self->_has_script() ) {
111 1         33 $self->add_file(
112             Dist::Zilla::File::InMemory->new(
113             name => $self->_script(),
114             content => $self->_generate_conflicts_script(),
115             )
116             );
117             }
118              
119 1         2889 return;
120             }
121              
122             {
123             # The weird {{ '=' . 'pod' }} stuff is to hide this POD from
124             # podtidy. Otherwise it starts formatting it, which we don't want.
125             my $conflicts_module_template = <<'EOF';
126             package # hide from PAUSE
127             {{ $module_name }};
128              
129             use strict;
130             use warnings;
131              
132             # this module was generated with {{ ref($plugin) . ' ' . ($plugin->VERSION || '<self>') }}
133              
134             use Dist::CheckConflicts
135             -dist => '{{ $dist_name }}',
136             -conflicts => {
137             {{ $conflicts_dump }},
138             },
139             {{ $also_dump }}
140             ;
141              
142             1;
143              
144             !!closing comments!!
145              
146             __END__
147              
148             {{ '=' . 'pod' }}
149              
150             {{ '=' . 'for' }} Pod::Coverage *EVERYTHING*
151              
152             {{ '=' . 'head1' }} NAME
153              
154             {{ $module_name }} - Check for conflicts between {{ $dist_name }} and installed packages
155              
156             {{ '=' . 'head1' }} DESCRIPTION
157              
158             This module contains information about conflicts between this distribution and
159             other CPAN distributions. It does not have any user-facing parts.
160              
161             This module was generated by {{ ( ref $plugin ) . q{ } . ( $plugin->VERSION //
162             '<no version>' ) }}.
163              
164             {{ '=' . 'cut' }}
165             EOF
166              
167             # Avoid false positives in toolchain things -- e.g. [SurgicalPodWeaver],
168             # and MetaCPAN seems to look for this when picking a summary for the
169             # recent uploads page.
170              
171             $conflicts_module_template =~ s/\Q!!closing comments!!/
172             '# ABST'
173             . 'RACT: Check for conflicts between {{ $dist_name }} and installed packages' . "\n"
174             . '# Dist::Zilla: -'
175             . 'PodWeaver'
176             /e;
177              
178             sub _generate_conflicts_module {
179 1     1   22 my $self = shift;
180              
181 1         39 my $conflicts = $self->_conflicts();
182              
183             my $conflicts_dump = join ",\n ",
184 1         3 map {qq['$_' => '$conflicts->{$_}']} sort keys %{$conflicts};
  1         9  
  1         5  
185              
186 2         14 my $also_dump = join "\n ", sort grep { $_ ne 'perl' }
187 1         32 map { $_->required_modules() }
  1         114  
188             $self->zilla()->prereqs()->requirements_for(qw(runtime requires));
189              
190 1 50       12 $also_dump
191             = ' -also => [ qw(' . "\n"
192             . q{ }
193             . $also_dump . "\n"
194             . ' ) ],' . "\n"
195             if length $also_dump;
196              
197 1         73 ( my $dist_name = $self->zilla()->name() ) =~ s/-/::/g;
198              
199 1         74 return $self->fill_in_string(
200             $conflicts_module_template,
201             {
202             plugin => \$self,
203             dist_name => \$dist_name,
204             module_name => \( $self->_conflicts_module_name() ),
205             conflicts_dump => \$conflicts_dump,
206             also_dump => \$also_dump,
207             },
208             );
209             }
210             }
211              
212             {
213             # If dzil sees this string PODXXXX anywhere in this code it uses that as the
214             # name for the module.
215             my $podname_hack = 'POD' . 'NAME';
216             my $script_template = <<'EOF';
217             #!/usr/bin/perl
218              
219             use strict;
220             use warnings;
221             # %s: {{ $filename }}
222              
223             # this script was generated with {{ ref($plugin) . ' ' . ($plugin->VERSION || '<self>') }}
224              
225             use Getopt::Long;
226             use {{ $module_name }};
227              
228             my $verbose;
229             GetOptions( 'verbose|v' => \$verbose );
230              
231             if ($verbose) {
232             {{ $module_name }}->check_conflicts;
233             }
234             else {
235             my @conflicts = {{ $module_name }}->calculate_conflicts;
236             print "$_\n" for map { $_->{package} } @conflicts;
237             exit @conflicts;
238             }
239             EOF
240             $script_template = sprintf( $script_template, $podname_hack );
241              
242             sub _generate_conflicts_script {
243 1     1   2 my $self = shift;
244              
245 1         30 ( my $filename = $self->_script() ) =~ s{^.*/}{};
246              
247 1         40 return $self->fill_in_string(
248             $script_template,
249             {
250             plugin => \$self,
251             filename => \$filename,
252             module_name => \( $self->_conflicts_module_name() ),
253             },
254             );
255             }
256             }
257              
258             # XXX - this should really be a separate phase that runs after InstallTool -
259             # until then, all we can do is die if we are run too soon
260             sub setup_installer {
261 1     1 0 17747 my $self = shift;
262              
263 1         3 my $found_installer;
264 1         3 for my $file ( @{ $self->zilla()->files() } ) {
  1         39  
265 5 100       314 if ( $file->name() =~ /Makefile\.PL$/ ) {
    50          
266 1         49 $self->_munge_makefile_pl($file);
267 1         3 $found_installer++;
268             }
269             elsif ( $file->name() =~ /Build\.PL$/ ) {
270 0         0 $self->_munge_build_pl($file);
271 0         0 $found_installer++;
272             }
273             }
274              
275 1 50       88 return if $found_installer;
276              
277 0         0 $self->log_fatal( 'No Makefile.PL or Build.PL was found.'
278             . ' [Conflicts] should appear in your dist.ini'
279             . ' after [MakeMaker] or [ModuleBuild]!' );
280             }
281              
282             sub _munge_makefile_pl {
283 1     1   3 my $self = shift;
284 1         3 my $makefile = shift;
285              
286 1         4 my $content = $makefile->content();
287              
288 1         79 $content =~ s/(use ExtUtils::MakeMaker.*)/$1\ncheck_conflicts();/;
289 1         5 $content .= "\n" . $self->_check_conflicts_sub();
290              
291 1         1245 $makefile->content($content);
292              
293 1         278 return;
294             }
295              
296             sub _munge_build_pl {
297 0     0   0 my $self = shift;
298 0         0 my $build = shift;
299              
300 0         0 my $content = $build->content();
301              
302 0         0 $content =~ s/(use Module::Build.*)/$1\ncheck_conflicts();/;
303 0         0 $content .= "\n" . $self->_check_conflicts_sub();
304              
305 0         0 $build->content($content);
306              
307 0         0 return;
308             }
309              
310             {
311             my $check_conflicts_template = <<'CC_SUB';
312             sub check_conflicts {
313             if ( eval { require './{{ $conflicts_module_path }}'; 1; } ) {
314             if ( eval { {{ $conflicts_module_name }}->check_conflicts; 1 } ) {
315             return;
316             }
317             else {
318             my $err = $@;
319             $err =~ s/^/ /mg;
320             warn "***\n$err***\n";
321             }
322             }
323             else {
324             print <<'EOF';
325             ***
326             {{ $warning }}
327             ***
328             EOF
329             }
330              
331             return if $ENV{AUTOMATED_TESTING} || $ENV{NONINTERACTIVE_TESTING};
332              
333             # More or less copied from Module::Build
334             return if $ENV{PERL_MM_USE_DEFAULT};
335             return unless -t STDIN && ( -t STDOUT || !( -f STDOUT || -c STDOUT ) );
336              
337             sleep 4;
338             }
339             CC_SUB
340              
341             sub _check_conflicts_sub {
342 1     1   2 my $self = shift;
343              
344 1         2 my $warning;
345 1 50       38 if ( $self->_has_script() ) {
346 1         55 ( my $filename = $self->_script() ) =~ s{^.*/}{};
347 1         7 $warning = <<"EOF";
348             Your toolchain doesn't support configure_requires, so Dist::CheckConflicts
349             hasn't been installed yet. You should check for conflicting modules
350             manually using the '$filename' script that is installed with
351             this distribution once the installation finishes.
352             EOF
353             }
354             else {
355 0         0 my $mod = $self->_conflicts_module_name();
356 0         0 $warning = <<"EOF";
357             Your toolchain doesn't support configure_requires, so Dist::CheckConflicts
358             hasn't been installed yet. You should check for conflicting modules
359             manually by examining the list of conflicts in $mod once the installation
360             finishes.
361             EOF
362             }
363              
364 1         5 chomp $warning;
365              
366 1         37 return $self->fill_in_string(
367             $check_conflicts_template,
368             {
369             conflicts_module_path => \( $self->_conflicts_module_path() ),
370             conflicts_module_name => \( $self->_conflicts_module_name() ),
371             warning => \$warning,
372             },
373             );
374             }
375             }
376              
377             sub metadata {
378 1     1 0 23265 my $self = shift;
379              
380 1         44 my $conflicts = $self->_conflicts;
381             return { x_breaks =>
382 1         6 { map { $_ => '<= ' . $conflicts->{$_} } keys %$conflicts } };
  1         10  
383             }
384              
385             __PACKAGE__->meta->make_immutable;
386              
387             1;
388              
389             # ABSTRACT: Declare conflicts for your distro
390              
391             __END__
392              
393             =pod
394              
395             =encoding UTF-8
396              
397             =head1 NAME
398              
399             Dist::Zilla::Plugin::Conflicts - Declare conflicts for your distro
400              
401             =head1 VERSION
402              
403             version 0.20
404              
405             =head1 SYNOPSIS
406              
407             In your F<dist.ini>:
408              
409             [Conflicts]
410             Foo::Bar = 0.05
411             Thing = 2
412              
413             =head1 DESCRIPTION
414              
415             This module lets you declare conflicts on other modules (usually dependencies
416             of your module) in your F<dist.ini>.
417              
418             Declaring conflicts does several thing to your distro.
419              
420             First, it generates a module named something like C<Your::Distro::Conflicts>.
421             This module will use L<Dist::CheckConflicts> to declare and check conflicts.
422             The package name will be obscured from PAUSE by putting a newline after the
423             C<package> keyword.
424              
425             All of your runtime prereqs will be passed in the C<-also> parameter to
426             L<Dist::CheckConflicts>.
427              
428             Second, it adds code to your F<Makefile.PL> or F<Build.PL> to load the
429             generated module and print warnings if conflicts are detected.
430              
431             Finally, it adds the conflicts to the F<META.json> and/or F<META.yml> files
432             under the "x_breaks" key.
433              
434             =for Pod::Coverage gather_files
435             metadata
436             register_prereqs
437             setup_installer
438              
439             =head1 USAGE
440              
441             Using this module is simple, add a "[Conflicts]" section and list each module
442             you conflict with:
443              
444             [Conflicts]
445             Module::X = 0.02
446              
447             The version listed is the last version that I<doesn't> work. In other words,
448             any version of C<Module::X> greater than 0.02 should work with this release.
449              
450             The special key C<-script> can also be set, and given the name of a script to
451             generate, as in:
452              
453             [Conflicts]
454             -script = bin/foo-conflicts
455             Module::X = 0.02
456              
457             This script will be installed with your module, and can be run to check for
458             currently installed modules which conflict with your module. This allows users
459             an easy way to fix their conflicts - simply run a command such as
460             C<foo-conflicts | cpanm> to bring all of your conflicting modules up to date.
461              
462             B<Note:> Currently, this plugin only works properly if it is listed in your
463             F<dist.ini> I<after> the plugin which generates your F<Makefile.PL> or
464             F<Build.PL>. This is a limitation of L<Dist::Zilla> that will hopefully be
465             addressed in a future release.
466              
467             =head1 SEE ALSO
468              
469             =over 4
470              
471             =item *
472              
473             L<Dist::CheckConflicts>
474              
475             =item *
476              
477             L<Dist::Zilla::Plugin::Breaks>
478              
479             =item *
480              
481             L<Dist::Zilla::Plugin::Test::CheckBreaks>
482              
483             =back
484              
485             =head1 SUPPORT
486              
487             Please report any bugs or feature requests to
488             C<bug-dist-zilla-plugin-conflicts@rt.cpan.org>, or through the web interface at
489             L<http://rt.cpan.org>. I will be notified, and then you'll automatically be
490             notified of progress on your bug as I make changes.
491              
492             Bugs may be submitted at L<https://github.com/moose/Dist-Zilla-Plugin-Conflicts/issues>.
493              
494             =head1 SOURCE
495              
496             The source code repository for Dist-Zilla-Plugin-Conflicts can be found at L<https://github.com/moose/Dist-Zilla-Plugin-Conflicts>.
497              
498             =head1 DONATIONS
499              
500             If you'd like to thank me for the work I've done on this module, please
501             consider making a "donation" to me via PayPal. I spend a lot of free time
502             creating free software, and would appreciate any support you'd care to offer.
503              
504             Please note that B<I am not suggesting that you must do this> in order for me
505             to continue working on this particular software. I will continue to do so,
506             inasmuch as I have in the past, for as long as it interests me.
507              
508             Similarly, a donation made in this way will probably not make me work on this
509             software much more, unless I get so many donations that I can consider working
510             on free software full time (let's all have a chuckle at that together).
511              
512             To donate, log into PayPal and send money to autarch@urth.org, or use the
513             button at L<https://houseabsolute.com/foss-donations/>.
514              
515             =head1 AUTHOR
516              
517             Dave Rolsky <autarch@urth.org>
518              
519             =head1 CONTRIBUTORS
520              
521             =for stopwords Karen Etheridge Philippe Bruhat (BooK)
522              
523             =over 4
524              
525             =item *
526              
527             Karen Etheridge <ether@cpan.org>
528              
529             =item *
530              
531             Philippe Bruhat (BooK) <book@cpan.org>
532              
533             =back
534              
535             =head1 COPYRIGHT AND LICENSE
536              
537             This software is Copyright (c) 2023 by Dave Rolsky.
538              
539             This is free software, licensed under:
540              
541             The Artistic License 2.0 (GPL Compatible)
542              
543             The full text of the license can be found in the
544             F<LICENSE> file included with this distribution.
545              
546             =cut