| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package CPAN::InGit::ArchiveTree; |
|
2
|
|
|
|
|
|
|
our $VERSION = '0.003'; # VERSION |
|
3
|
|
|
|
|
|
|
# ABSTRACT: An object managing a CPAN file structure in a Git Tree |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
3589
|
use Carp; |
|
|
5
|
|
|
|
|
21
|
|
|
|
5
|
|
|
|
|
432
|
|
|
7
|
5
|
|
|
5
|
|
57
|
use Scalar::Util 'refaddr', 'blessed'; |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
320
|
|
|
8
|
5
|
|
|
5
|
|
47
|
use POSIX 'strftime'; |
|
|
5
|
|
|
|
|
15
|
|
|
|
5
|
|
|
|
|
65
|
|
|
9
|
5
|
|
|
5
|
|
491
|
use IO::Uncompress::Gunzip qw( gunzip $GunzipError ); |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
914
|
|
|
10
|
5
|
|
|
5
|
|
37
|
use JSON::PP; |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
435
|
|
|
11
|
5
|
|
|
5
|
|
44
|
use Time::Piece; |
|
|
5
|
|
|
|
|
13
|
|
|
|
5
|
|
|
|
|
58
|
|
|
12
|
5
|
|
|
5
|
|
470
|
use Log::Any '$log'; |
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
49
|
|
|
13
|
5
|
|
|
5
|
|
1511
|
use Moo; |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
28
|
|
|
14
|
5
|
|
|
5
|
|
2531
|
use v5.36; |
|
|
5
|
|
|
|
|
19
|
|
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
extends 'CPAN::InGit::MutableTree'; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
6
|
|
|
6
|
0
|
68
|
sub BUILD($self, $args, @) { |
|
|
6
|
|
|
|
|
11
|
|
|
|
6
|
|
|
|
|
10
|
|
|
|
6
|
|
|
|
|
8
|
|
|
20
|
6
|
100
|
|
|
|
20
|
$self->load_config if $self->config_blob; |
|
21
|
6
|
100
|
|
|
|
156
|
$self->name($self->branch? $self->branch->shorthand : '(anonymous)') |
|
|
|
50
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
unless defined $self->name; |
|
23
|
|
|
|
|
|
|
} |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
has name => ( is => 'rw' ); |
|
26
|
|
|
|
|
|
|
has config => ( is => 'rw' ); |
|
27
|
|
|
|
|
|
|
|
|
28
|
12
|
|
|
12
|
1
|
18
|
sub config_blob($self) { |
|
|
12
|
|
|
|
|
17
|
|
|
|
12
|
|
|
|
|
14
|
|
|
29
|
12
|
100
|
|
|
|
45
|
my $ent= $self->get_path('cpan_ingit.json') |
|
30
|
|
|
|
|
|
|
or return undef; |
|
31
|
10
|
50
|
|
|
|
160
|
return $ent->[0]->is_blob? $ent->[0] : undef; |
|
32
|
|
|
|
|
|
|
} |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
|
|
35
|
5
|
|
|
5
|
1
|
100
|
sub load_config($self) { |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
7
|
|
|
36
|
5
|
50
|
|
|
|
15
|
my $cfg_blob= $self->config_blob |
|
37
|
|
|
|
|
|
|
or die "Missing '/cpan_ingit.json'"; |
|
38
|
5
|
|
|
|
|
69
|
my $attrs= JSON::PP->new->utf8->relaxed->decode($cfg_blob->content); |
|
39
|
5
|
50
|
|
|
|
4178
|
ref $attrs eq 'HASH' or croak "Configuration file does not contain an object?".$cfg_blob->content; |
|
40
|
5
|
|
|
|
|
17
|
$self->{config}= $attrs; |
|
41
|
5
|
|
|
|
|
24
|
$self->_unpack_config($self->{config}); |
|
42
|
5
|
|
|
|
|
44
|
$attrs; |
|
43
|
|
|
|
|
|
|
} |
|
44
|
|
|
|
|
|
|
|
|
45
|
5
|
|
|
5
|
|
10
|
sub _unpack_config($self, $config) { |
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
8
|
|
|
|
5
|
|
|
|
|
7
|
|
|
46
|
5
|
|
|
|
|
16
|
for (qw( default_import_sources corelist_perl_version canonical_url )) { |
|
47
|
15
|
100
|
|
|
|
73
|
$self->$_($config->{$_}) if defined $config->{$_}; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
1
|
|
|
1
|
|
2
|
sub _pack_config($self, $config) { |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2
|
|
|
52
|
1
|
|
|
|
|
2
|
for (qw( default_import_sources corelist_perl_version canonical_url )) { |
|
53
|
3
|
|
|
|
|
8
|
my $val= $self->$_; |
|
54
|
3
|
50
|
|
|
|
5
|
$val= "$val" if ref $val eq 'version'; |
|
55
|
3
|
|
|
|
|
6
|
$config->{$_}= $val; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
1
|
|
|
1
|
1
|
2
|
sub write_config($self) { |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2
|
|
|
60
|
1
|
|
50
|
|
|
7
|
my $config= $self->config // {}; |
|
61
|
1
|
|
|
|
|
4
|
$self->_pack_config($config); |
|
62
|
1
|
|
|
|
|
11
|
my $json= JSON::PP->new->utf8->canonical->pretty->encode($config); |
|
63
|
1
|
50
|
33
|
|
|
323
|
$self->set_path('cpan_ingit.json', \$json) |
|
64
|
|
|
|
|
|
|
unless $self->config_blob && $self->config_blob->content eq $json; |
|
65
|
1
|
|
|
|
|
4
|
$self; |
|
66
|
|
|
|
|
|
|
} |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
has canonical_url => ( is => 'rw' ); |
|
70
|
|
|
|
|
|
|
has default_import_sources => ( is => 'rw' ); |
|
71
|
|
|
|
|
|
|
has corelist_perl_version => ( is => 'rw', default => '5.008009' ); |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
|
|
74
|
8
|
|
|
8
|
1
|
42
|
sub package_details_blob($self) { |
|
|
8
|
|
|
|
|
14
|
|
|
|
8
|
|
|
|
|
13
|
|
|
75
|
8
|
100
|
|
|
|
33
|
my $ent= $self->get_path('modules/02packages.details.txt') |
|
76
|
|
|
|
|
|
|
or return undef; |
|
77
|
7
|
50
|
|
|
|
78
|
return $ent->[0]->is_blob? $ent->[0] : undef; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
has package_details => ( is => 'rw', lazy => 1, builder => 1, clearer => 1 ); |
|
81
|
3
|
|
|
3
|
|
4080
|
sub _build_package_details($self) { |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
5
|
|
|
82
|
3
|
|
|
|
|
10
|
$self->parse_package_details($self->package_details_blob->content); |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
|
|
86
|
3
|
|
|
3
|
1
|
6
|
sub parse_package_details($self, $content) { |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
3
|
|
|
87
|
3
|
|
|
|
|
7
|
my %attrs; |
|
88
|
3
|
|
|
|
|
32
|
while ($content =~ /\G([^:\n]+):\s+(.*)\n/gc) { |
|
89
|
24
|
|
|
|
|
122
|
$attrs{$1}= $2; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
3
|
50
|
|
|
|
15
|
$content =~ /\G\n/gc or croak "missing blank line after headers"; |
|
92
|
3
|
|
|
|
|
8
|
my %by_mod; |
|
93
|
|
|
|
|
|
|
my %by_dist; |
|
94
|
3
|
|
|
|
|
15
|
while ($content =~ /\G(\S+)\s+(\S+)\s+(\S+)\n/gc) { |
|
95
|
18
|
100
|
|
|
|
81
|
my $row= [ $1, ($2 eq 'undef'? undef : $2), $3 ]; |
|
96
|
18
|
|
|
|
|
49
|
$by_mod{$1}= $row; |
|
97
|
18
|
|
|
|
|
26
|
push @{$by_dist{$3}}, $row; |
|
|
18
|
|
|
|
|
125
|
|
|
98
|
|
|
|
|
|
|
} |
|
99
|
3
|
50
|
|
|
|
14
|
pos $content == length $content |
|
100
|
|
|
|
|
|
|
or croak "Parse error at '".substr($content, pos($content), 10)."'"; |
|
101
|
3
|
50
|
|
|
|
43
|
my $timestamp = $attrs{'Last-Updated'}? Time::Piece->strptime($attrs{'Last-Updated'}, "%a, %d %b %Y %H:%M:%S GMT") |
|
102
|
|
|
|
|
|
|
: undef; # TODO: fall back to date from branch commit |
|
103
|
|
|
|
|
|
|
return { |
|
104
|
3
|
|
|
|
|
311
|
last_update => $timestamp, |
|
105
|
|
|
|
|
|
|
by_module => \%by_mod, |
|
106
|
|
|
|
|
|
|
by_dist => \%by_dist, |
|
107
|
|
|
|
|
|
|
}; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
|
|
111
|
3
|
|
|
3
|
1
|
6
|
sub write_package_details($self) { |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
5
|
|
|
112
|
3
|
|
50
|
|
|
19
|
my $url= $self->canonical_url // 'cpan_mirror_ingit.local'; |
|
113
|
|
|
|
|
|
|
# on initial creation, need to write an empty package_details without triggering |
|
114
|
|
|
|
|
|
|
# lazy-build of package_details |
|
115
|
|
|
|
|
|
|
my @mod_list= !$self->package_details_blob? () |
|
116
|
3
|
100
|
|
|
|
11
|
: values %{$self->package_details->{by_module}}; |
|
|
2
|
|
|
|
|
62
|
|
|
117
|
3
|
|
|
|
|
21
|
my $line_count= @mod_list; |
|
118
|
3
|
|
|
|
|
15
|
my $date= strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime); |
|
119
|
3
|
|
|
|
|
256
|
my $content= <<~END; |
|
120
|
|
|
|
|
|
|
File: 02packages.details.txt |
|
121
|
|
|
|
|
|
|
URL: $url |
|
122
|
|
|
|
|
|
|
Description: Package names found in directory \$CPAN/authors/id/ |
|
123
|
|
|
|
|
|
|
Columns: package name, version, path |
|
124
|
|
|
|
|
|
|
Intended-For: Automated fetch routines, namespace documentation. |
|
125
|
|
|
|
|
|
|
Written-By: PAUSE version 1.005 |
|
126
|
|
|
|
|
|
|
Line-Count: $line_count |
|
127
|
|
|
|
|
|
|
Last-Updated: $date |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
END |
|
130
|
|
|
|
|
|
|
# List can be huge, so try to be efficient about stringifying it |
|
131
|
3
|
|
|
|
|
10
|
@mod_list= sort { fc $a->[0] cmp fc $b->[0] } @mod_list; |
|
|
1
|
|
|
|
|
5
|
|
|
132
|
3
|
|
|
|
|
6
|
my @lines; |
|
133
|
3
|
|
|
|
|
7
|
for (@mod_list) { |
|
134
|
3
|
|
100
|
|
|
29
|
push @lines, sprintf("%s %s %s\n", $_->[0], $_->[1] // 'undef', $_->[2]); |
|
135
|
|
|
|
|
|
|
} |
|
136
|
3
|
|
|
|
|
27
|
$self->set_path('modules/02packages.details.txt', \join('', $content, @lines)); |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
|
|
140
|
7
|
|
|
7
|
1
|
905
|
sub has_module($self, $mod_name, $reqs=undef) { |
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
8
|
|
|
|
7
|
|
|
|
|
8
|
|
|
141
|
7
|
|
|
|
|
13
|
my $mod_ver= $self->get_module_version($mod_name); |
|
142
|
7
|
100
|
100
|
|
|
24
|
if (defined $mod_ver && defined $reqs) { |
|
143
|
4
|
50
|
|
|
|
23
|
$reqs= CPAN::Meta::Requirements->from_string_hash({ $mod_name => $reqs }) |
|
144
|
|
|
|
|
|
|
unless ref $reqs; |
|
145
|
4
|
|
|
|
|
484
|
return !!$reqs->accepts_module($mod_name, $mod_ver); |
|
146
|
|
|
|
|
|
|
} |
|
147
|
3
|
|
|
|
|
7
|
return defined $mod_ver; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
|
|
150
|
14
|
|
|
14
|
1
|
16977
|
sub get_module_version($self, $mod_name) { |
|
|
14
|
|
|
|
|
22
|
|
|
|
14
|
|
|
|
|
18
|
|
|
|
14
|
|
|
|
|
16
|
|
|
151
|
14
|
100
|
|
|
|
369
|
if (my $current= $self->package_details->{by_module}{$mod_name}) { |
|
|
|
50
|
|
|
|
|
|
|
152
|
10
|
|
|
|
|
59
|
my $mod_ver= $current->[1]; |
|
153
|
|
|
|
|
|
|
# grab the version out of the package filename? |
|
154
|
10
|
100
|
|
|
|
22
|
if (!defined $mod_ver) { |
|
155
|
5
|
50
|
|
|
|
33
|
$mod_ver= $current->[2] =~ /-([0-9]+(?:\.[0-9_]+?)*)\./? $1 |
|
156
|
|
|
|
|
|
|
: 0; # return 0 to differentiate from undef=nonexisting |
|
157
|
|
|
|
|
|
|
} |
|
158
|
10
|
|
|
|
|
23
|
return $mod_ver; |
|
159
|
|
|
|
|
|
|
} elsif ($mod_name eq 'perl') { |
|
160
|
0
|
|
|
|
|
0
|
return $self->corelist_perl_version; |
|
161
|
|
|
|
|
|
|
} else { |
|
162
|
4
|
|
|
|
|
49
|
return undef; |
|
163
|
|
|
|
|
|
|
} |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
2
|
|
|
2
|
1
|
6
|
sub get_module_dist($self, $mod_name) { |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
3
|
|
|
167
|
2
|
|
|
|
|
67
|
my $by_name= $self->package_details->{by_module}{$mod_name}; |
|
168
|
2
|
50
|
|
|
|
23
|
return $by_name? $by_name->[2] : undef; |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
|
|
172
|
4
|
|
|
4
|
1
|
8
|
sub meta_path_for_dist($self, $author_path) { |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
6
|
|
|
173
|
|
|
|
|
|
|
# replace archive extension with '.meta.json' |
|
174
|
4
|
|
|
|
|
36
|
$author_path =~ s/\.(zip|tar\.gz|tgz|tar\.bz2|tbz2)\z//; |
|
175
|
4
|
|
|
|
|
14
|
return "authors/id/$author_path.meta"; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
|
|
179
|
2
|
|
|
2
|
1
|
5
|
sub import_dist($self, $peer, $author_path, %options) { |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
180
|
2
|
|
|
|
|
10
|
my $dist_path= "authors/id/$author_path"; |
|
181
|
2
|
50
|
|
|
|
12
|
my $distfile_ent= $peer->get_path($dist_path) |
|
182
|
|
|
|
|
|
|
or croak "Import source branch '".$peer->name."' does not contain $dist_path"; |
|
183
|
2
|
|
|
|
|
23
|
$log->info("Importing $author_path from ".$peer->name." to ".$self->name); |
|
184
|
2
|
|
|
|
|
164
|
my $existing_ent= $self->get_path($dist_path); |
|
185
|
|
|
|
|
|
|
# If exists, must be same gitobj as before or this is an error |
|
186
|
2
|
50
|
|
|
|
9
|
if ($existing_ent) { |
|
187
|
0
|
0
|
|
|
|
0
|
croak "$dist_path already exists with different content" |
|
188
|
|
|
|
|
|
|
unless $existing_ent->[0]->id eq $distfile_ent->[0]->id; |
|
189
|
|
|
|
|
|
|
} |
|
190
|
2
|
|
|
|
|
13
|
$self->set_path($dist_path, $distfile_ent->[0], mode => $distfile_ent->[1]); |
|
191
|
2
|
|
|
|
|
93
|
my $modules_registered= $peer->package_details->{by_dist}{$author_path}; |
|
192
|
2
|
50
|
|
|
|
23
|
if ($modules_registered) { |
|
193
|
2
|
|
|
|
|
50
|
$self->package_details->{by_dist}{$author_path}= [ @$modules_registered ]; |
|
194
|
|
|
|
|
|
|
$self->package_details->{by_module}{$_->[0]}= $_ |
|
195
|
2
|
|
|
|
|
56
|
for @$modules_registered; |
|
196
|
2
|
|
|
|
|
21
|
$self->write_package_details; |
|
197
|
|
|
|
|
|
|
} |
|
198
|
2
|
|
|
|
|
11
|
my $meta_path= $self->meta_path_for_dist($author_path); |
|
199
|
2
|
|
|
|
|
8
|
my $meta_ent= $peer->get_path($meta_path); |
|
200
|
2
|
100
|
|
|
|
10
|
if ($meta_ent) { |
|
201
|
1
|
|
|
|
|
8
|
$self->set_path($meta_path, $meta_ent->[0], mode => $meta_ent->[1]); |
|
202
|
|
|
|
|
|
|
} else { |
|
203
|
|
|
|
|
|
|
# TODO: parse module for META.json and dependnecies |
|
204
|
1
|
|
|
|
|
9
|
$log->warn("No META for $author_path"); |
|
205
|
|
|
|
|
|
|
} |
|
206
|
2
|
|
|
|
|
95
|
return $self; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
|
|
210
|
2
|
|
|
2
|
1
|
4
|
sub get_dist_meta($self, $author_path, %options) { |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
4
|
|
|
211
|
2
|
|
|
|
|
7
|
my $meta_path= $self->meta_path_for_dist($author_path); |
|
212
|
2
|
|
|
|
|
9
|
my $meta_ent= $self->get_path($meta_path); |
|
213
|
2
|
100
|
|
|
|
21
|
return CPAN::Meta->load_string($meta_ent->[0]->content) |
|
214
|
|
|
|
|
|
|
if $meta_ent; |
|
215
|
|
|
|
|
|
|
# TODO: process the tar file to generate the meta |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
|
|
219
|
3
|
|
|
3
|
|
10
|
sub _filter_prereqs($self, $reqs, $corelist={}, $log_prefix='') { |
|
|
3
|
|
|
|
|
10
|
|
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
9
|
|
|
|
3
|
|
|
|
|
5
|
|
|
220
|
3
|
|
|
|
|
12
|
for my $mod (sort $reqs->required_modules) { |
|
221
|
2
|
|
|
|
|
27
|
my $req_version= $reqs->requirements_for_module($mod); |
|
222
|
2
|
|
|
|
|
150
|
my $have_ver= $self->get_module_version($mod); |
|
223
|
|
|
|
|
|
|
# Is this requirement already in the tree? |
|
224
|
2
|
50
|
33
|
|
|
23
|
if (defined $have_ver && $reqs->accepts_module($mod, $have_ver)) { |
|
|
|
50
|
33
|
|
|
|
|
|
225
|
0
|
0
|
|
|
|
0
|
$log->debugf($log_prefix.'(requirement %s %s already satisfied by %s from %s)', |
|
|
|
0
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
$mod, $req_version, $have_ver, |
|
227
|
|
|
|
|
|
|
($mod eq 'perl'? 'corelist_perl_version' : $self->get_module_dist($mod))) |
|
228
|
|
|
|
|
|
|
if $log->is_info; |
|
229
|
0
|
|
|
|
|
0
|
$reqs->clear_requirement($mod); |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
# Is the requirement satisfied by a core perl module in the version of perl |
|
232
|
|
|
|
|
|
|
# the app will be running under? |
|
233
|
|
|
|
|
|
|
elsif (defined $corelist->{$mod} && $reqs->accepts_module($mod, $corelist->{$mod})) { |
|
234
|
0
|
|
|
|
|
0
|
$log->debugf($log_prefix.'(requirement %s %s satisfied by corelist)', $mod, $req_version); |
|
235
|
0
|
|
|
|
|
0
|
$reqs->clear_requirement($mod); |
|
236
|
|
|
|
|
|
|
} |
|
237
|
|
|
|
|
|
|
} |
|
238
|
3
|
|
|
|
|
15
|
return $reqs; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# merges new requirements into existing, and returns a list of anything that changed |
|
242
|
1
|
|
|
1
|
|
2
|
sub _merge_prereqs($self, $reqs, $new_reqs) { |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1
|
|
|
243
|
1
|
|
|
|
|
4
|
my $before= $reqs->as_string_hash; |
|
244
|
1
|
|
|
|
|
42
|
$reqs->add_requirements($new_reqs); |
|
245
|
1
|
|
|
|
|
68
|
my $after= $reqs->as_string_hash; |
|
246
|
1
|
|
|
|
|
78
|
my @changed; |
|
247
|
1
|
|
|
|
|
3
|
for my $mod (sort $new_reqs->required_modules) { |
|
248
|
1
|
50
|
50
|
|
|
20
|
if (($before->{$mod} // '') ne ($after->{$mod} // 0)) { |
|
|
|
|
50
|
|
|
|
|
|
249
|
1
|
|
|
|
|
5
|
push @changed, $mod; |
|
250
|
1
|
50
|
|
|
|
11
|
$log->infof(' requires %s%s', $mod, $after->{$mod}? " $after->{$mod}" : ''); |
|
251
|
|
|
|
|
|
|
} |
|
252
|
|
|
|
|
|
|
} |
|
253
|
1
|
|
|
|
|
128
|
return @changed; |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
1
|
|
|
1
|
1
|
1100
|
sub import_modules($self, $reqs, %options) { |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
1
|
|
|
257
|
1
|
|
|
|
|
1
|
my %imported_dists; |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Build list of source trees |
|
260
|
1
|
|
33
|
|
|
9
|
my $sources= $options{sources} // $self->default_import_sources; |
|
261
|
1
|
50
|
33
|
|
|
4
|
$sources && @$sources |
|
262
|
|
|
|
|
|
|
or croak "No import sources specified"; |
|
263
|
|
|
|
|
|
|
# coerce every source name to an ArchiveTree object |
|
264
|
1
|
|
|
|
|
2
|
my @autocommit; |
|
265
|
1
|
|
|
|
|
2
|
for (@$sources) { |
|
266
|
1
|
50
|
33
|
|
|
4
|
unless (ref $_ and $_->can('package_details')) { |
|
267
|
1
|
50
|
|
|
|
5
|
my $t= $self->parent->get_archive_tree($_) |
|
268
|
|
|
|
|
|
|
or croak "No such archive tree $_"; |
|
269
|
|
|
|
|
|
|
# If we've created new objects for MirrorTree and the MirrorTree has autofetch |
|
270
|
|
|
|
|
|
|
# enabled, then we also need to commit those changes before returning. |
|
271
|
1
|
50
|
33
|
|
|
12
|
push @autocommit, $t if $t->can('autofetch') && $t->autofetch; |
|
272
|
1
|
|
|
|
|
3
|
$_= $t; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
} |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# Coerce the argument to a Requirements object |
|
277
|
1
|
|
|
|
|
8
|
require CPAN::Meta::Requirements; |
|
278
|
1
|
|
|
|
|
3
|
my $prereq_phases= [qw( configure build runtime test )]; |
|
279
|
1
|
|
|
|
|
2
|
my $prereq_types= [qw( requires )]; |
|
280
|
1
|
|
|
|
|
3
|
my $log_recommends= !grep $_ eq 'recommends', @$prereq_types; |
|
281
|
1
|
|
|
|
|
8
|
my $recommended= CPAN::Meta::Requirements->new; |
|
282
|
|
|
|
|
|
|
# coerce the requirements into a CPAN::Meta::Requirements object |
|
283
|
1
|
0
|
0
|
|
|
22
|
$reqs= ref $reqs eq 'HASH'? CPAN::Meta::Requirements->from_string_hash($reqs) |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
: blessed($reqs) && $reqs->isa('CPAN::Meta::Requirements')? $reqs |
|
285
|
|
|
|
|
|
|
: blessed($reqs) && $reqs->isa('CPAN::Meta::Prereqs')? $reqs->merged_requirements($prereq_phases, $prereq_types) |
|
286
|
|
|
|
|
|
|
: croak "Expected CPAN::Meta::Requirements object, ::Prereqs object, or HASH ref"; |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Determine what module versions were available for the app's version of perl. |
|
289
|
1
|
|
|
|
|
4172
|
require Module::CoreList; |
|
290
|
1
|
|
33
|
|
|
200109
|
my $perl_v= $options{corelist_perl_version} // $self->corelist_perl_version; |
|
291
|
1
|
|
|
|
|
17
|
$perl_v= version->parse($perl_v)->numify; |
|
292
|
1
|
50
|
|
|
|
8
|
my $corelist= Module::CoreList::find_version($perl_v) |
|
293
|
|
|
|
|
|
|
or carp "No corelist for $perl_v"; |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Filter out the prereqs we already have, or which are in the corelist |
|
296
|
1
|
|
|
|
|
31
|
$log->tracef('todo reqs: %s', $reqs->as_string_hash); |
|
297
|
1
|
|
|
|
|
135
|
$self->_filter_prereqs($reqs, $corelist); |
|
298
|
1
|
|
|
|
|
5
|
my @initial_list= $reqs->required_modules; |
|
299
|
1
|
|
|
|
|
10
|
my @todo= @initial_list; |
|
300
|
1
|
|
|
|
|
4
|
while (@todo) { |
|
301
|
2
|
|
|
|
|
35
|
my $mod= shift @todo; |
|
302
|
2
|
|
|
|
|
10
|
my $req_version= $reqs->requirements_for_module($mod); |
|
303
|
2
|
|
|
|
|
103
|
$log->infof('Add %s %s', $mod, $req_version); |
|
304
|
|
|
|
|
|
|
# Walk through the list of import sources looking for a version that works |
|
305
|
2
|
|
|
|
|
1883
|
my ($author_path, $prereqs); |
|
306
|
2
|
|
|
|
|
124
|
for my $peer (@$sources) { |
|
307
|
2
|
|
|
|
|
241
|
my $peer_ver= $peer->get_module_version($mod); |
|
308
|
2
|
50
|
|
|
|
15
|
if (!defined $peer_ver) { |
|
|
|
50
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
0
|
$log->debugf(' branch %s does not have module %s', $peer->name, $mod); |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
elsif (!$reqs->accepts_module($mod, $peer_ver)) { |
|
312
|
0
|
|
|
|
|
0
|
$log->debugf(' branch %s module %s version %s does not match %s', $peer->name, $mod, $peer_ver, $req_version); |
|
313
|
|
|
|
|
|
|
} |
|
314
|
|
|
|
|
|
|
else { |
|
315
|
2
|
|
|
|
|
86
|
$log->debugf(' branch %s has %s %s, matching %s', $peer->name, $mod, $peer_ver, $req_version); |
|
316
|
2
|
|
|
|
|
39
|
$author_path= $peer->get_module_dist($mod); |
|
317
|
2
|
|
|
|
|
11
|
$self->import_dist($peer, $author_path); |
|
318
|
2
|
|
|
|
|
10
|
my $meta= $self->get_dist_meta($author_path); |
|
319
|
2
|
100
|
|
|
|
13414
|
$prereqs= $meta->effective_prereqs if $meta; |
|
320
|
2
|
|
|
|
|
385
|
$imported_dists{$author_path}= $peer; |
|
321
|
2
|
|
|
|
|
17
|
last; |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
} |
|
324
|
2
|
50
|
|
|
|
9
|
croak("No import_sources branch had module $mod with version $req_version") |
|
325
|
|
|
|
|
|
|
unless length $author_path; |
|
326
|
|
|
|
|
|
|
# Push things into the TODO list if they aren't already in %$reqs or if they have a higher |
|
327
|
|
|
|
|
|
|
# version requirement. |
|
328
|
2
|
100
|
|
|
|
13
|
if ($prereqs) { |
|
329
|
1
|
|
|
|
|
6
|
my $dist_reqs= $prereqs->merged_requirements($prereq_phases, $prereq_types); |
|
330
|
1
|
|
|
|
|
391
|
$log->infof('Dist %s:', $author_path); |
|
331
|
1
|
|
|
|
|
147
|
my $n= $#todo; |
|
332
|
1
|
|
|
|
|
9
|
push @todo, $self->_merge_prereqs($reqs, $self->_filter_prereqs($dist_reqs, $corelist, ' ')); |
|
333
|
1
|
50
|
|
|
|
24
|
$log->infof(' (no additional reqs)') if $#todo == $n; |
|
334
|
|
|
|
|
|
|
# Collect recommendations |
|
335
|
1
|
50
|
|
|
|
4
|
if ($log_recommends) { |
|
336
|
1
|
|
|
|
|
7
|
my $dist_recommends= $prereqs->merged_requirements(['runtime'], ['recommends']); |
|
337
|
1
|
|
|
|
|
152
|
$self->_filter_prereqs($dist_recommends, $corelist); |
|
338
|
1
|
|
|
|
|
4
|
my @list= sort $dist_recommends->required_modules; |
|
339
|
1
|
50
|
|
|
|
8
|
$log->noticef('Dist %s recommends %s', $mod, [ sort @list ]) |
|
340
|
|
|
|
|
|
|
if @list; |
|
341
|
1
|
|
|
|
|
5
|
$recommended->add_requirements($dist_recommends); |
|
342
|
|
|
|
|
|
|
} |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
} |
|
345
|
1
|
50
|
|
|
|
4
|
if ($log_recommends) { |
|
346
|
1
|
50
|
|
|
|
6
|
if (my @list= sort $recommended->required_modules) { |
|
347
|
0
|
|
|
|
|
0
|
$log->notice('Full list of recommended modules:'); |
|
348
|
|
|
|
|
|
|
$log->noticef(' %s %s', $_, $recommended->requirements_for_module($_)) |
|
349
|
0
|
|
|
|
|
0
|
for @list; |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
# If any sources are 'autofetch' and caller didn't supply the MirrorTree object, |
|
353
|
|
|
|
|
|
|
# commit the changes before returning. |
|
354
|
1
|
|
|
|
|
13
|
for my $mirror (grep $_->has_changes, @autocommit) { |
|
355
|
0
|
|
|
|
|
0
|
my $message= join "\n", |
|
356
|
|
|
|
|
|
|
'Auto-commit packages fetched for branch '.$self->name, |
|
357
|
|
|
|
|
|
|
'', |
|
358
|
|
|
|
|
|
|
'For $archive_tree->import_modules:', |
|
359
|
|
|
|
|
|
|
map(" - $_ ".$reqs->requirements_for_module($_), @initial_list), |
|
360
|
|
|
|
|
|
|
''; |
|
361
|
0
|
|
|
|
|
0
|
$mirror->commit($message); |
|
362
|
|
|
|
|
|
|
} |
|
363
|
1
|
|
|
|
|
15
|
return \%imported_dists; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
|
|
367
|
0
|
|
|
0
|
1
|
|
sub import_cpanfile_snapshot($self, $snapshot_spec, %options) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
my %imported_dists; |
|
369
|
|
|
|
|
|
|
|
|
370
|
0
|
|
0
|
|
|
|
my $sources= $options{sources} // $self->default_import_sources; |
|
371
|
0
|
0
|
0
|
|
|
|
$sources && @$sources |
|
372
|
|
|
|
|
|
|
or croak "No import sources specified"; |
|
373
|
|
|
|
|
|
|
# coerce every source name to an ArchiveTree object |
|
374
|
0
|
|
|
|
|
|
my @autocommit; |
|
375
|
0
|
|
|
|
|
|
for (@$sources) { |
|
376
|
0
|
0
|
0
|
|
|
|
unless (ref $_ and $_->can('package_details')) { |
|
377
|
0
|
0
|
|
|
|
|
my $t= $self->parent->get_archive_tree($_) |
|
378
|
|
|
|
|
|
|
or croak "No such archive tree $_"; |
|
379
|
|
|
|
|
|
|
# If we've created new objects for MirrorTree and the MirrorTree has autofetch |
|
380
|
|
|
|
|
|
|
# enabled, then we also need to commit those changes before returning. |
|
381
|
0
|
0
|
0
|
|
|
|
push @autocommit, $t if $t->can('autofetch') && $t->autofetch; |
|
382
|
0
|
|
|
|
|
|
$_= $t; |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
0
|
|
|
|
|
|
dist: for my $dist_name (sort keys %$snapshot_spec) { |
|
387
|
0
|
|
|
|
|
|
my $dist_info= $snapshot_spec->{$dist_name}; |
|
388
|
|
|
|
|
|
|
# Locate 'pathname' |
|
389
|
0
|
|
|
|
|
|
my $author_path= $dist_info->{pathname}; |
|
390
|
0
|
0
|
|
|
|
|
unless ($author_path) { |
|
391
|
0
|
|
|
|
|
|
my $msg= "Dist $dist_name lacks 'pathname' attribute"; |
|
392
|
0
|
0
|
|
|
|
|
$options{partial}? $log->notice($msg) : croak $msg; |
|
393
|
0
|
|
|
|
|
|
next; |
|
394
|
|
|
|
|
|
|
} |
|
395
|
|
|
|
|
|
|
# Which source has this file? |
|
396
|
0
|
|
|
|
|
|
for my $source (@$sources) { |
|
397
|
0
|
|
|
|
|
|
$log->debugf("check %s for %s", $source->name, $author_path); |
|
398
|
0
|
0
|
|
|
|
|
my $distfile_ent= $source->get_path("authors/id/$author_path") |
|
399
|
|
|
|
|
|
|
or next; |
|
400
|
0
|
|
|
|
|
|
$self->import_dist($source, $author_path); |
|
401
|
0
|
|
|
|
|
|
$imported_dists{$author_path}= $source; |
|
402
|
|
|
|
|
|
|
# Update index with the modules provided by this distribution if it wasn't imported |
|
403
|
|
|
|
|
|
|
# from $source by import_dist. |
|
404
|
0
|
0
|
|
|
|
|
if (!$source->package_details->{by_dist}{$author_path}) { |
|
405
|
|
|
|
|
|
|
# Fall back to the 'provides' from the cpanfile.snapshot |
|
406
|
0
|
0
|
|
|
|
|
if (ref $dist_info->{provides} eq 'HASH') { |
|
407
|
|
|
|
|
|
|
my @mod_index= map [ $_, $dist_info->{provides}{$_}, $author_path ], |
|
408
|
0
|
|
|
|
|
|
keys %{$dist_info->{provides}}; |
|
|
0
|
|
|
|
|
|
|
|
409
|
0
|
|
|
|
|
|
$self->package_details->{by_dist}{$author_path}= \@mod_index; |
|
410
|
|
|
|
|
|
|
$self->package_details->{by_module}{$_->[0]}= $_ |
|
411
|
0
|
|
|
|
|
|
for @mod_index; |
|
412
|
0
|
|
|
|
|
|
$self->write_package_details; |
|
413
|
|
|
|
|
|
|
} else { |
|
414
|
0
|
|
|
|
|
|
my $msg= "Snapshot lacks 'provides' for $dist_name, and not indexed in ".$source->name." either"; |
|
415
|
0
|
0
|
|
|
|
|
$options{partial}? $log->notice($msg) : croak $msg; |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
} |
|
418
|
0
|
|
|
|
|
|
next dist; |
|
419
|
|
|
|
|
|
|
} |
|
420
|
0
|
|
|
|
|
|
my $msg= "No source contains file $author_path"; |
|
421
|
0
|
0
|
|
|
|
|
$options{partial}? $log->notice($msg) : croak $msg; |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
# If any sources are 'autofetch' and caller didn't supply the MirrorTree object, |
|
424
|
|
|
|
|
|
|
# commit the changes before returning. |
|
425
|
0
|
|
|
|
|
|
for my $mirror (grep $_->has_changes, @autocommit) { |
|
426
|
0
|
|
|
|
|
|
my $message= join "\n", |
|
427
|
|
|
|
|
|
|
'Auto-commit packages fetched for branch '.$self->name, |
|
428
|
|
|
|
|
|
|
'', |
|
429
|
|
|
|
|
|
|
'For $archive_tree->import_cpanfile_snapshot', |
|
430
|
|
|
|
|
|
|
''; |
|
431
|
0
|
|
|
|
|
|
$mirror->commit($message); |
|
432
|
|
|
|
|
|
|
} |
|
433
|
0
|
|
|
|
|
|
return \%imported_dists; |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
1; |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
__END__ |