| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package CPANPLUS::Dist::Debora::Util; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 9 |  |  | 9 |  | 26362 | use 5.016; | 
|  | 9 |  |  |  |  | 96 |  | 
| 6 | 9 |  |  | 9 |  | 85 | use warnings; | 
|  | 9 |  |  |  |  | 18 |  | 
|  | 9 |  |  |  |  | 234 |  | 
| 7 | 9 |  |  | 9 |  | 47 | use utf8; | 
|  | 9 |  |  |  |  | 36 |  | 
|  | 9 |  |  |  |  | 107 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | our $VERSION = '0.011'; | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 9 |  |  | 9 |  | 477 | use parent qw(Exporter); | 
|  | 9 |  |  |  |  | 35 |  | 
|  | 9 |  |  |  |  | 54 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | our @EXPORT_OK = qw( | 
| 14 |  |  |  |  |  |  | parse_version | 
| 15 |  |  |  |  |  |  | module_is_distributed_with_perl | 
| 16 |  |  |  |  |  |  | decode_utf8 | 
| 17 |  |  |  |  |  |  | slurp_utf8 | 
| 18 |  |  |  |  |  |  | spew_utf8 | 
| 19 |  |  |  |  |  |  | can_run | 
| 20 |  |  |  |  |  |  | run | 
| 21 |  |  |  |  |  |  | unix_path | 
| 22 |  |  |  |  |  |  | filetype | 
| 23 |  |  |  |  |  |  | find_most_recent_mtime | 
| 24 |  |  |  |  |  |  | find_shared_objects | 
| 25 |  |  |  |  |  |  | is_testing | 
| 26 |  |  |  |  |  |  | ); | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 9 |  |  | 9 |  | 869 | use Carp qw(croak); | 
|  | 9 |  |  |  |  | 21 |  | 
|  | 9 |  |  |  |  | 513 |  | 
| 29 | 9 |  |  | 9 |  | 65 | use Cwd qw(cwd); | 
|  | 9 |  |  |  |  | 19 |  | 
|  | 9 |  |  |  |  | 454 |  | 
| 30 | 9 |  |  | 9 |  | 1852 | use Encode qw(decode); | 
|  | 9 |  |  |  |  | 31483 |  | 
|  | 9 |  |  |  |  | 533 |  | 
| 31 | 9 |  |  | 9 |  | 975 | use English qw(-no_match_vars); | 
|  | 9 |  |  |  |  | 5359 |  | 
|  | 9 |  |  |  |  | 98 |  | 
| 32 | 9 |  |  | 9 |  | 3591 | use File::Spec::Functions qw(catfile splitdir splitpath); | 
|  | 9 |  |  |  |  | 20 |  | 
|  | 9 |  |  |  |  | 589 |  | 
| 33 | 9 |  |  | 9 |  | 65 | use File::Spec::Unix qw(); | 
|  | 9 |  |  |  |  | 21 |  | 
|  | 9 |  |  |  |  | 181 |  | 
| 34 | 9 |  |  | 9 |  | 4898 | use IPC::Cmd qw(can_run); | 
|  | 9 |  |  |  |  | 336239 |  | 
|  | 9 |  |  |  |  | 567 |  | 
| 35 | 9 |  |  | 9 |  | 29004 | use Module::CoreList 2.32; | 
|  | 9 |  |  |  |  | 989295 |  | 
|  | 9 |  |  |  |  | 89 |  | 
| 36 | 9 |  |  | 9 |  | 6496 | use version 0.77; | 
|  | 9 |  |  |  |  | 155 |  | 
|  | 9 |  |  |  |  | 91 |  | 
| 37 |  |  |  |  |  |  |  | 
| 38 | 9 |  |  | 9 |  | 6342 | use CPANPLUS::Error qw(error); | 
|  | 9 |  |  |  |  | 71752 |  | 
|  | 9 |  |  |  |  | 14593 |  | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | # Avoid warnings from IO::Select by using IPC::Run. | 
| 41 |  |  |  |  |  |  | $IPC::Cmd::USE_IPC_RUN = IPC::Cmd->can_use_ipc_run; | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | my $perl_version = parse_version($PERL_VERSION); | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub parse_version { | 
| 46 | 29 |  |  | 29 | 1 | 1772 | my $string = shift; | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 29 |  |  |  |  | 431 | return version->parse($string); | 
| 49 |  |  |  |  |  |  | } | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | sub module_is_distributed_with_perl { | 
| 52 | 13 |  |  | 13 | 1 | 537 | my ($module_name, $version) = @_; | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 13 |  |  |  |  | 24 | my $ok = 0; | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | # cpan2dist is run with -w, which triggers a warning in Module::CoreList. | 
| 57 | 13 |  |  |  |  | 73 | local $WARNING = 0; | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 13 |  |  |  |  | 112 | my $upper = Module::CoreList->removed_from($module_name); | 
| 60 | 13 | 100 | 66 |  |  | 18505 | if (!defined $upper || $perl_version < parse_version($upper)) { | 
| 61 | 7 |  |  |  |  | 56 | my $lower = Module::CoreList->first_release($module_name, $version); | 
| 62 | 7 | 100 | 66 |  |  | 3819 | if (defined $lower && $perl_version >= parse_version($lower)) { | 
| 63 | 1 |  |  |  |  | 4 | $ok = 1; | 
| 64 |  |  |  |  |  |  | } | 
| 65 |  |  |  |  |  |  | } | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 13 |  |  |  |  | 205 | return $ok; | 
| 68 |  |  |  |  |  |  | } | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | sub decode_utf8 { | 
| 71 | 7 |  |  | 7 | 1 | 23 | my $bytes = shift; | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 7 |  |  |  |  | 32 | return decode('UTF-8', $bytes); | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | sub slurp_utf8 { | 
| 77 | 3 |  |  | 3 | 1 | 10 | my $filename = shift; | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 3 |  |  |  |  | 6 | my $data; | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 3 |  |  |  |  | 121 | my $ok = open my $fh, '<:encoding(UTF-8)', $filename; | 
| 82 | 3 | 50 |  |  |  | 239 | if ($ok) { | 
| 83 | 3 |  |  |  |  | 27 | local $RS = undef; | 
| 84 | 3 |  |  |  |  | 167 | $data = <$fh>; | 
| 85 | 3 | 50 |  |  |  | 262 | close $fh or $ok = 0; | 
| 86 |  |  |  |  |  |  | } | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 3 |  |  |  |  | 25 | return $data; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | sub spew_utf8 { | 
| 92 | 1 |  |  | 1 | 1 | 4 | my ($filename, $string) = @_; | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 1 |  |  | 1 |  | 46 | my $ok = open my $fh, '>:encoding(UTF-8)', $filename; | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 7 |  | 
| 95 | 1 | 50 |  |  |  | 1330 | if ($ok) { | 
| 96 | 1 |  |  |  |  | 2 | $ok = print {$fh} $string; | 
|  | 1 |  |  |  |  | 8 |  | 
| 97 | 1 | 50 |  |  |  | 75 | close $fh or $ok = 0; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 1 |  |  |  |  | 27 | return $ok; | 
| 101 |  |  |  |  |  |  | } | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | sub run { | 
| 104 | 0 |  |  | 0 | 1 | 0 | my (%options) = @_; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 0 |  |  |  |  | 0 | my $command = $options{command}; | 
| 109 | 0 | 0 |  |  |  | 0 | if (!$command) { | 
| 110 | 0 |  |  |  |  | 0 | error('No command'); | 
| 111 | 0 |  |  |  |  | 0 | return $ok; | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 0 |  |  |  |  | 0 | my $dir = $options{dir}; | 
| 115 | 0 |  |  |  |  | 0 | delete $options{dir}; | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 0 | 0 |  |  |  | 0 | if (!exists $options{buffer}) { | 
| 118 | 0 |  |  |  |  | 0 | my $buf = q{}; | 
| 119 | 0 |  |  |  |  | 0 | $options{buffer} = \$buf; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | my $on_error = $options{on_error} | 
| 123 | 0 |  | 0 | 0 |  | 0 | // sub { error("Could not run '$_[0]': $_[1]") }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 124 | 0 |  |  |  |  | 0 | delete $options{on_error}; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 0 |  |  |  |  | 0 | my $origdir; | 
| 127 | 0 | 0 |  |  |  | 0 | if ($dir) { | 
| 128 | 0 |  |  |  |  | 0 | $origdir = cwd; | 
| 129 | 0 | 0 |  |  |  | 0 | if (!chdir $dir) { | 
| 130 | 0 |  |  |  |  | 0 | return $ok; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 0 |  |  |  |  | 0 | $ok = IPC::Cmd::run(%options); | 
| 135 | 0 | 0 |  |  |  | 0 | if (!$ok) { | 
| 136 | 0 |  |  |  |  | 0 | my $cmdline = join q{ }, @{$command}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 137 | 0 |  | 0 |  |  | 0 | my $output  = ${$options{buffer}} // q{}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 138 | 0 |  |  |  |  | 0 | $on_error->($cmdline, $output); | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 0 | 0 |  |  |  | 0 | if ($origdir) { | 
| 142 | 0 | 0 |  |  |  | 0 | if (!chdir $origdir) { | 
| 143 | 0 |  |  |  |  | 0 | $ok = 0; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 0 |  |  |  |  | 0 | return $ok; | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub unix_path { | 
| 151 | 6 |  |  | 6 | 1 | 17 | my $path = shift; | 
| 152 |  |  |  |  |  |  |  | 
| 153 | 6 |  |  |  |  | 18 | (undef, $path) = splitpath($path, 1); | 
| 154 | 6 |  |  |  |  | 48 | $path = File::Spec::Unix->catfile(splitdir($path)); | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 6 |  |  |  |  | 69 | return $path; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | sub filetype { | 
| 160 | 7 |  |  | 7 | 1 | 26 | my $filename = shift; | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 7 |  |  |  |  | 158 | my %type_for = ( | 
| 163 |  |  |  |  |  |  | '1'     => 'text', | 
| 164 |  |  |  |  |  |  | '1p'    => 'text', | 
| 165 |  |  |  |  |  |  | '3'     => 'text', | 
| 166 |  |  |  |  |  |  | '3perl' => 'text', | 
| 167 |  |  |  |  |  |  | '3pm'   => 'text', | 
| 168 |  |  |  |  |  |  | 'bat'   => 'script', | 
| 169 |  |  |  |  |  |  | 'dll'   => 'executable', | 
| 170 |  |  |  |  |  |  | 'dylib' => 'executable', | 
| 171 |  |  |  |  |  |  | 'exe'   => 'executable', | 
| 172 |  |  |  |  |  |  | 'pl'    => 'script', | 
| 173 |  |  |  |  |  |  | 'pm'    => 'text', | 
| 174 |  |  |  |  |  |  | 'pod'   => 'text', | 
| 175 |  |  |  |  |  |  | 'so'    => 'executable', | 
| 176 |  |  |  |  |  |  | ); | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 7 |  |  |  |  | 91 | my @magic = ( | 
| 179 |  |  |  |  |  |  | [0, 4, '7F454C46', 'executable'],    # ELF | 
| 180 |  |  |  |  |  |  | [0, 4, 'FEEDFACE', 'executable'],    # Mach-O | 
| 181 |  |  |  |  |  |  | [0, 4, 'CEFAEDFE', 'executable'],    # Mach-O | 
| 182 |  |  |  |  |  |  | [0, 4, 'FEEDFACF', 'executable'],    # Mach-O | 
| 183 |  |  |  |  |  |  | [0, 4, 'CFFAEDFE', 'executable'],    # Mach-O | 
| 184 |  |  |  |  |  |  | [0, 2, '4D5A',     'executable'],    # PE | 
| 185 |  |  |  |  |  |  | [0, 2, '2321',     'script'],        # Shebang | 
| 186 |  |  |  |  |  |  | ); | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 7 |  |  |  |  | 20 | my $type = 'data'; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 7 | 50 |  |  |  | 84 | if ($filename =~ m{[.]([^.]+) \z}xms) { | 
| 191 | 7 |  |  |  |  | 42 | my $suffix = lc $1; | 
| 192 | 7 | 50 |  |  |  | 25 | if (exists $type_for{$suffix}) { | 
| 193 | 7 |  |  |  |  | 29 | $type = $type_for{$suffix}; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 7 | 50 |  |  |  | 35 | if ($type eq 'data') { | 
| 198 | 0 | 0 |  |  |  | 0 | if (open my $fh, '<:raw', $filename) { | 
| 199 | 0 | 0 |  |  |  | 0 | if (read $fh, my $data, 16) { | 
| 200 |  |  |  |  |  |  | TYPE: | 
| 201 | 0 |  |  |  |  | 0 | for (@magic) { | 
| 202 | 0 | 0 |  |  |  | 0 | if (substr($data, $_->[0], $_->[1]) eq pack 'H*', $_->[2]) { | 
| 203 | 0 |  |  |  |  | 0 | $type = $_->[3]; | 
| 204 | 0 |  |  |  |  | 0 | last TYPE; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | } | 
| 208 | 0 | 0 |  |  |  | 0 | close $fh or undef; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 | 7 |  |  |  |  | 74 | return $type; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | sub find_most_recent_mtime { | 
| 216 | 6 |  |  | 6 | 1 | 93 | my $sourcedir = shift; | 
| 217 |  |  |  |  |  |  |  | 
| 218 | 6 |  |  |  |  | 96 | my $most_recent_mtime = 0; | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | my $find = sub { | 
| 221 | 230 |  |  | 230 |  | 489 | my $dir = shift; | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 230 | 50 |  |  |  | 6834 | opendir my $dh, $dir or croak "Could not traverse '$dir': $OS_ERROR"; | 
| 224 |  |  |  |  |  |  | ENTRY: | 
| 225 | 230 |  |  |  |  | 5889 | while (defined(my $entry = readdir $dh)) { | 
| 226 | 1642 | 100 | 100 |  |  | 6931 | next ENTRY if $entry eq q{.} || $entry eq q{..}; | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 1182 |  |  |  |  | 5747 | my $path = catfile($dir, $entry); | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # Skip symbolic links. | 
| 231 | 1182 | 50 |  |  |  | 20081 | next ENTRY if -l $path; | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 1182 | 100 |  |  |  | 14231 | if (-d $path) { | 
| 234 | 224 |  |  |  |  | 910 | __SUB__->($path); | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  | else { | 
| 237 | 958 |  |  |  |  | 11908 | my @stat = stat $path; | 
| 238 | 958 | 50 |  |  |  | 2991 | if (@stat) { | 
| 239 | 958 |  |  |  |  | 1604 | my $mtime = $stat[9]; | 
| 240 | 958 | 100 |  |  |  | 5046 | if ($most_recent_mtime < $mtime) { | 
| 241 | 12 |  |  |  |  | 76 | $most_recent_mtime = $mtime; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  | } | 
| 246 | 230 |  |  |  |  | 2684 | closedir $dh; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 230 |  |  |  |  | 1677 | return; | 
| 249 | 6 |  |  |  |  | 187 | }; | 
| 250 | 6 |  |  |  |  | 70 | $find->($sourcedir); | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 6 |  |  |  |  | 135 | return $most_recent_mtime; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | sub find_shared_objects { | 
| 256 | 3 |  |  | 3 | 1 | 10 | my $stagingdir = shift; | 
| 257 |  |  |  |  |  |  |  | 
| 258 | 3 |  |  |  |  | 15 | my @shared_objects; | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | my $find = sub { | 
| 261 | 18 |  |  | 18 |  | 37 | my $dir = shift; | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 18 | 50 |  |  |  | 406 | opendir my $dh, $dir | 
| 264 |  |  |  |  |  |  | or croak "Could not traverse '$dir': $OS_ERROR"; | 
| 265 |  |  |  |  |  |  | ENTRY: | 
| 266 | 18 |  |  |  |  | 266 | while (defined(my $entry = readdir $dh)) { | 
| 267 | 57 | 100 | 100 |  |  | 319 | next ENTRY if $entry eq q{.} || $entry eq q{..}; | 
| 268 |  |  |  |  |  |  |  | 
| 269 | 21 |  |  |  |  | 118 | my $path = catfile($dir, $entry); | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | # Skip symbolic links. | 
| 272 | 21 | 50 |  |  |  | 266 | next ENTRY if -l $path; | 
| 273 |  |  |  |  |  |  |  | 
| 274 | 21 | 100 |  |  |  | 235 | if (-d $path) { | 
| 275 | 15 |  |  |  |  | 98 | __SUB__->($path); | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  | else { | 
| 278 | 6 | 50 |  |  |  | 53 | if (filetype($path) eq 'executable') { | 
| 279 | 0 |  |  |  |  | 0 | push @shared_objects, $path; | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | } | 
| 283 | 18 |  |  |  |  | 169 | closedir $dh; | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 18 |  |  |  |  | 156 | return; | 
| 286 | 3 |  |  |  |  | 45 | }; | 
| 287 | 3 |  |  |  |  | 15 | $find->($stagingdir); | 
| 288 |  |  |  |  |  |  |  | 
| 289 | 3 |  |  |  |  | 60 | return \@shared_objects; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | sub is_testing { | 
| 293 | 2 |  | 33 | 2 | 1 | 26 | return $ENV{AUTOMATED_TESTING} || $ENV{RELEASE_TESTING}; | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | 1; | 
| 297 |  |  |  |  |  |  | __END__ |