App-LintPrereqs

 view release on metacpan or  search on metacpan

lib/App/LintPrereqs.pm  view on Meta::CPAN

given some weird code.

`nqlite` means <pm:Perl::PrereqScanner::NotQuiteLite> which is faster than
`regular` but not as fast as `lite`.

MARKDOWN
        },
        lite => {
            schema => ['bool*'],
            default => 0,
            summary => 'Use Perl::PrereqScanner::Lite instead of Perl::PrereqScanner',
            "summary.alt.bool.not" =>
                'Use Perl::PrereqScanner instead of Perl::PrereqScanner::Lite',
            description => <<'MARKDOWN',

This option is deprecated and has been replaced by `scanner`.

Lite is faster but it might still miss detecting some modules.

MARKDOWN
            tags => ['deprecated', 'hidden'],
        },
        core_prereqs => {
            schema => ['bool*'],
            default => 1,
            summary => 'Whether or not prereqs to core modules are allowed',
            description => <<'MARKDOWN',

If set to 0 (the default), will complain if there are prerequisites to core
modules. If set to 1, prerequisites to core modules are required just like other
modules.

MARKDOWN
        },
        fix => {
            schema => 'bool',
            summary => 'Attempt to automatically fix the errors',
            cmdline_aliases => {F=>{}},
            description => <<'MARKDOWN',

`lint-prereqs` can attempt to automatically fix the errors by
adding/removing/moving prereqs in `dist.ini`. Not all errors can be
automatically fixed. When modifying `dist.ini`, a backup in `dist.ini~` will be
created.

MARKDOWN
        },
    },
};
sub lint_prereqs {
    my %args = @_;

    (-f "dist.ini")
        or return [412, "No dist.ini found. ".
                       "Are you in the right dir (dist top-level)? ".
                           "Is your dist managed by Dist::Zilla?"];

    my $ct = do {
        open my($fh), "<", "dist.ini" or die "Can't open dist.ini: $!";
        local $/;
        binmode $fh, ":encoding(utf8)";
        scalar <$fh>;
    };
    return [200, "Not run (no-lint-prereqs)"] if $ct =~ /^;!no[_-]lint[_-]prereqs$/m;

    my @errs;

    my $ciod = Config::IOD->new(
        ignore_unknown_directive => 1,
    );

    my $cfg = $ciod->read_string($ct);

    my %mods_from_ini;
    my %assume_used;
    my %assume_provided;
    for my $section ($cfg->list_sections) {
        $section =~ m!^(
                          osprereqs \s*/\s* .+ |
                          osprereqs(::\w+)+ |
                          prereqs (?: \s*/\s* (?<prereqs_phase_rel>\w+))? |
                          extras \s*/\s* lint[_-]prereqs \s*/\s* (assume-(?:provided|used))
                      )$!ix or next;
        #$log->errorf("TMP: section=%s, %%+=%s", $section, {%+});

        my ($phase, $rel);
        if (my $pr = $+{prereqs_phase_rel}) {
            if ($pr =~ /^(develop|configure|build|test|runtime|x_\w+)(requires|recommends|suggests|x_\w+)$/i) {
                $phase = ucfirst(lc($1));
                $rel = ucfirst(lc($2));
            } else {
                return [400, "Invalid section '$section' (invalid phase/rel $pr)"];
            }
        } else {
            $phase = "Runtime";
            $rel = "Requires";
        }

        my %params;
        for my $param ($cfg->list_keys($section)) {
            my $v = $cfg->get_value($section, $param);
            if ($param =~ /^-phase$/) {
                $phase = ucfirst(lc($v));
                next;
            } elsif ($param =~ /^-(relationship|type)$/) {
                $rel = ucfirst(lc($v));
                next;
            }
            $params{$param} = $v;
        }
        #$log->tracef("phase=%s, rel=%s", $phase, $rel);

        for my $param (sort keys %params) {
            my $v = $params{$param};
            if (ref($v) eq 'ARRAY') {
                return [412, "Multiple '$param' prereq lines specified in dist.ini"];
            }
            my $dir = $cfg->get_directive_before_key($section, $param);
            my $dir_s = $dir ? join(" ", @$dir) : "";
            log_trace("section=%s, v=%s, param=%s, directive=%s", $section, $param, $v, $dir_s);

lib/App/LintPrereqs.pm  view on Meta::CPAN

                    }
                }
                $v = $versions->{$mod}
                    if $versions->{$mod} && version_gt($versions->{$mod}, $v);
                push @errs, {
                    module  => $mod,
                    req_v   => $v,
                    is_core => $is_core,
                    error   => "Used but not listed in dist.ini",
                    remedy  => "Put '$mod=$v' in dist.ini (e.g. in [Prereqs/${phase}Requires])",
                    remedy_cmds => [
                        ["pdrutil", "add-prereq", $mod, $v, "--phase", lc($phase)],
                    ],
                };
            }
        }
    } # END check modules from scanned

    # check minimum versions specified in [versions] in our config
    {
        last unless $versions;
        log_trace("Checking minimum versions ...");
        for my $mod (keys %{$mods_from_ini{Any}}) {
            next if $mod eq 'perl';
            my $v = $mods_from_ini{Any}{$mod};
            my $is_core = Module::CoreList::More->is_still_core($mod, $v, $perlv);
            my $min_v = $versions->{$mod};
            if (defined($min_v) && version_gt($min_v, $v)) {
                push @errs, {
                    module  => $mod,
                    req_v   => $v,
                    is_core => $is_core,
                    error   => "Less than specified minimum version ($min_v) in lint-prereqs.conf",
                    remedy  => "Increase version to $min_v",
                    remedy_cmds => [
                        ["pdrutil", "inc-prereq-version-to", $mod, $min_v],
                    ],
                };
            }
        }
    } # END check minimum versions

    return [200, "OK", []] unless @errs;

    @errs = sort {prereq_ala_perlancar($a->{module}, $b->{module})} @errs;

    my $resmeta = {};
    $resmeta->{'table.fields'} = [qw/module req_v is_core error remedy/];

    if ($args{fix}) {
        # there is an unfixable error
        if (grep {!$_->{remedy_cmds}} @errs) {
            for my $e (@errs) {
                $e->{remedy} = " (CAN'T FIX AUTOMATICALLY) $e->{remedy}" unless $e->{remedy_cmds};
            }
            $resmeta->{'cmdline.exit_code'} = 112;
        } else {
            # create dist.ini~ first
            if (-f "dist.ini~") { unlink "dist.ini~" or return [500, "Can't unlink dist.ini~: $!"] }
            sysopen my($fh), "dist.ini~", O_WRONLY|O_CREAT|O_EXCL or return [500, "Can't create dist.ini~: $!"];
            binmode $fh, ":encoding(utf8)"; print $fh $ct; close $fh or return [500, "Can't write to dist.ini~: $!"];

            # run the commands
          FIX:
            {
                # add sort-prereqs at the end
                push @{ $errs[-1]{remedy_cmds} }, ["pdrutil", "sort-prereqs"];

              ERR:
                for my $e (@errs) {
                    for my $cmd (@{ $e->{remedy_cmds} }) {
                        system @$cmd;
                        if ($?) {
                            $e->{remedy} = "(FIX FAILED: ".explain_child_error().") $e->{remedy}";
                            $resmeta->{'cmdline.exit_code'} = 1;
                            # restore dist.ini from backup
                            rename "dist.ini~", "dist.ini";
                            last FIX;
                        }
                    }
                }
                for my $e (@errs) {
                    $e->{remedy} = "(DONE) $e->{remedy}";
                }
                $resmeta->{'cmdline.exit_code'} = 0;
                # remove dist.ini~
                #unlink "dist.ini~";
            }
        }
        for my $e (@errs) { delete $e->{$_} for qw/remedy_cmds/ }
        return [200, "OK", \@errs, $resmeta];
    } else {
        for my $e (@errs) { delete $e->{$_} for qw/remedy_cmds/ }
        $resmeta->{'cmdline.exit_code'} = 200;
        return [200, "OK", \@errs, $resmeta];
    }
}

1;
# ABSTRACT: Check extraneous/missing/incorrect prerequisites in dist.ini

__END__

=pod

=encoding UTF-8

=head1 NAME

App::LintPrereqs - Check extraneous/missing/incorrect prerequisites in dist.ini

=head1 VERSION

This document describes version 0.544 of App::LintPrereqs (from Perl distribution App-LintPrereqs), released on 2024-12-21.

=head1 SYNOPSIS

 # Use via lint-prereqs CLI script

=head1 FUNCTIONS



( run in 1.638 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )