Sun Aug 14 03:18:37 2022 UTC ()
lintpkgsrc: properly clean up after chdir

When running 'lintpkgsrc -I tmp/lp-85 -yro', lintpkgsrc couldn't find
the cache file due to the intermediate chdir($pkgdistdir).


(rillig)
diff -r1.90 -r1.91 pkgsrc/pkgtools/lintpkgsrc/files/lintpkgsrc.pl

cvs diff -r1.90 -r1.91 pkgsrc/pkgtools/lintpkgsrc/files/lintpkgsrc.pl (switch to unified diff)

--- pkgsrc/pkgtools/lintpkgsrc/files/lintpkgsrc.pl 2022/08/14 03:12:02 1.90
+++ pkgsrc/pkgtools/lintpkgsrc/files/lintpkgsrc.pl 2022/08/14 03:18:36 1.91
@@ -1,1683 +1,1684 @@ @@ -1,1683 +1,1684 @@
1#!@PERL5@ 1#!@PERL5@
2# $NetBSD: lintpkgsrc.pl,v 1.90 2022/08/14 03:12:02 rillig Exp $ 2# $NetBSD: lintpkgsrc.pl,v 1.91 2022/08/14 03:18:36 rillig Exp $
3 3
4# Written by David Brownlee <abs@netbsd.org>. 4# Written by David Brownlee <abs@netbsd.org>.
5# 5#
6# Caveats: 6# Caveats:
7# The 'Makefile parsing' algorithm used to obtain package versions and 7# The 'Makefile parsing' algorithm used to obtain package versions and
8# DEPENDS information is geared towards speed rather than perfection, 8# DEPENDS information is geared towards speed rather than perfection,
9# though it has gotten somewhat better over time, it only parses the 9# though it has gotten somewhat better over time, it only parses the
10# simpler Makefile conditionals. 10# simpler Makefile conditionals.
11# 11#
12# TODO: Handle fun DEPENDS like avifile-devel with 12# TODO: Handle fun DEPENDS like avifile-devel with
13# {qt2-designer>=2.2.4,qt2-designer-kde>=2.3.1nb1} 13# {qt2-designer>=2.2.4,qt2-designer-kde>=2.3.1nb1}
14 14
15use v5.36; 15use v5.36;
16use locale; 16use locale;
17use strict; 17use strict;
18use warnings; 18use warnings;
19use Cwd 'realpath', 'getcwd'; 19use Cwd 'realpath', 'getcwd';
20use File::Basename; 20use File::Basename;
21use File::Find; 21use File::Find;
22use Getopt::Std; 22use Getopt::Std;
23use IPC::Open3; 23use IPC::Open3;
24 24
25# PkgVer is a PKGBASE + PKGVERSION, including some of the variables that 25# PkgVer is a PKGBASE + PKGVERSION, including some of the variables that
26# have been extracted from the package Makefile. 26# have been extracted from the package Makefile.
27package PkgVer; 27package PkgVer;
28 28
29sub new($class, $pkgbase, $pkgversion) { 29sub new($class, $pkgbase, $pkgversion) {
30 my $self = { 30 my $self = {
31 pkgbase => $pkgbase, 31 pkgbase => $pkgbase,
32 pkgversion => $pkgversion, 32 pkgversion => $pkgversion,
33 vars => {}, 33 vars => {},
34 }; 34 };
35 bless $self, $class; 35 bless $self, $class;
36 return $self; 36 return $self;
37} 37}
38 38
39sub pkgbase($self) { 39sub pkgbase($self) {
40 $self->{pkgbase}; 40 $self->{pkgbase};
41} 41}
42 42
43sub pkgversion($self) { 43sub pkgversion($self) {
44 $self->{pkgversion}; 44 $self->{pkgversion};
45} 45}
46 46
47sub pkgname($self) { 47sub pkgname($self) {
48 $self->pkgbase . '-' . $self->pkgversion; 48 $self->pkgbase . '-' . $self->pkgversion;
49} 49}
50 50
51sub var($self, $name, $value = undef) { 51sub var($self, $name, $value = undef) {
52 defined $value 52 defined $value
53 ? ($self->{vars}->{$name} = $value) 53 ? ($self->{vars}->{$name} = $value)
54 : $self->{vars}->{$name}; 54 : $self->{vars}->{$name};
55} 55}
56 56
57sub vars($self) { 57sub vars($self) {
58 keys $self->{vars}->%*; 58 keys $self->{vars}->%*;
59} 59}
60 60
61# All versions of a given PKGBASE, e.g. apache-1.3.27 and apache-2.0.46. 61# All versions of a given PKGBASE, e.g. apache-1.3.27 and apache-2.0.46.
62# Multi-prefix packages like py39-* are stored as simply py-*. 62# Multi-prefix packages like py39-* are stored as simply py-*.
63package Pkgs; 63package Pkgs;
64 64
65sub new($class, $pkgbase) { 65sub new($class, $pkgbase) {
66 my $self = { 66 my $self = {
67 pkgbase => $pkgbase, 67 pkgbase => $pkgbase,
68 pkgvers => {}, 68 pkgvers => {},
69 }; 69 };
70 bless $self, $class; 70 bless $self, $class;
71 return $self; 71 return $self;
72} 72}
73 73
74sub pkgbase($self) { 74sub pkgbase($self) {
75 $self->{pkgbase}; 75 $self->{pkgbase};
76} 76}
77 77
78# All available versions of the package, in decreasing alphabetical(!) order. 78# All available versions of the package, in decreasing alphabetical(!) order.
79sub versions($self) { 79sub versions($self) {
80 reverse sort keys $self->{pkgvers}->%*; 80 reverse sort keys $self->{pkgvers}->%*;
81} 81}
82 82
83sub add($self, $pkgbase, $pkgversion) { 83sub add($self, $pkgbase, $pkgversion) {
84 $self->{pkgvers}->{$pkgversion} = PkgVer->new($pkgbase, $pkgversion); 84 $self->{pkgvers}->{$pkgversion} = PkgVer->new($pkgbase, $pkgversion);
85} 85}
86 86
87# All PkgVers of this pkgbase, in decreasing alphabetical(!) version order. 87# All PkgVers of this pkgbase, in decreasing alphabetical(!) version order.
88sub pkgvers_all($self) { 88sub pkgvers_all($self) {
89 my $pkgvers = $self->{pkgvers}; 89 my $pkgvers = $self->{pkgvers};
90 sort { $b->pkgversion cmp $a->pkgversion } values %$pkgvers; 90 sort { $b->pkgversion cmp $a->pkgversion } values %$pkgvers;
91} 91}
92 92
93sub pkgver($self, $pkgversion) { 93sub pkgver($self, $pkgversion) {
94 $self->{pkgvers}->{$pkgversion} 94 $self->{pkgvers}->{$pkgversion}
95} 95}
96 96
97# PkgDb is a small database of all packages in pkgsrc. 97# PkgDb is a small database of all packages in pkgsrc.
98package PkgDb; 98package PkgDb;
99 99
100sub new($class) { 100sub new($class) {
101 my $self = {}; # pkgbase => Pkgs 101 my $self = {}; # pkgbase => Pkgs
102 bless $self, $class; 102 bless $self, $class;
103 return $self; 103 return $self;
104} 104}
105 105
106sub add($self, $pkgbase, $pkgversion) { 106sub add($self, $pkgbase, $pkgversion) {
107 my $pkgs = ($self->{$pkgbase} ||= Pkgs->new($pkgbase)); 107 my $pkgs = ($self->{$pkgbase} ||= Pkgs->new($pkgbase));
108 $pkgs->add($pkgbase, $pkgversion); 108 $pkgs->add($pkgbase, $pkgversion);
109} 109}
110 110
111# All PkgVers, sorted by pkgbase, then by version in decreasing 111# All PkgVers, sorted by pkgbase, then by version in decreasing
112# alphabetical(!) order. 112# alphabetical(!) order.
113sub pkgvers_all($self) { 113sub pkgvers_all($self) {
114 map { $_->pkgvers_all } $self->pkgs; 114 map { $_->pkgvers_all } $self->pkgs;
115} 115}
116 116
117# All PkgVers of the given pkgbase, sorted by version in decreasing 117# All PkgVers of the given pkgbase, sorted by version in decreasing
118# alphabetical(!) order. 118# alphabetical(!) order.
119sub pkgvers_by_pkgbase($self, $pkgbase) { 119sub pkgvers_by_pkgbase($self, $pkgbase) {
120 my $pkgs = $self->{$pkgbase}; 120 my $pkgs = $self->{$pkgbase};
121 defined $pkgs ? $pkgs->pkgvers_all : (); 121 defined $pkgs ? $pkgs->pkgvers_all : ();
122} 122}
123 123
124sub pkgver($self, $pkgbase, $pkgversion) { 124sub pkgver($self, $pkgbase, $pkgversion) {
125 my $pkgs = $self->{$pkgbase}; 125 my $pkgs = $self->{$pkgbase};
126 defined $pkgs ? $pkgs->pkgver($pkgversion) : undef; 126 defined $pkgs ? $pkgs->pkgver($pkgversion) : undef;
127} 127}
128 128
129# pkgs() returns all Pkgs, sorted by pkgbase. 129# pkgs() returns all Pkgs, sorted by pkgbase.
130# 130#
131# pkgs($pkgbase) returns the Pkgs, or undef. 131# pkgs($pkgbase) returns the Pkgs, or undef.
132sub pkgs($self, $pkgbase = undef) { 132sub pkgs($self, $pkgbase = undef) {
133 defined $pkgbase 133 defined $pkgbase
134 ? $self->{$pkgbase} 134 ? $self->{$pkgbase}
135 : sort { $a->pkgbase cmp $b->pkgbase } values %$self; 135 : sort { $a->pkgbase cmp $b->pkgbase } values %$self;
136} 136}
137 137
138package main; 138package main;
139 139
140# Buildtime configuration 140# Buildtime configuration
141my $conf_make = '@MAKE@'; 141my $conf_make = '@MAKE@';
142my $conf_makeconf = '@MAKECONF@'; 142my $conf_makeconf = '@MAKECONF@';
143my $conf_pkg_info = '@PKG_INFO@'; 143my $conf_pkg_info = '@PKG_INFO@';
144my $conf_pkgsrcdir = '@PKGSRCDIR@'; 144my $conf_pkgsrcdir = '@PKGSRCDIR@';
145my $conf_prefix = '@PREFIX@'; 145my $conf_prefix = '@PREFIX@';
146my $conf_x11base = '@X11BASE@'; 146my $conf_x11base = '@X11BASE@';
147 147
148my ( 148my (
149 $pkgdb, # Database of pkgsrc packages 149 $pkgdb, # Database of pkgsrc packages
150 $default_vars, # Set for Makefiles, inc PACKAGES & PKGSRCDIR 150 $default_vars, # Set for Makefiles, inc PACKAGES & PKGSRCDIR
151 %opt, # Command line options 151 %opt, # Command line options
152 @matched_prebuiltpackages, # List of obsolete prebuilt package paths 152 @matched_prebuiltpackages, # List of obsolete prebuilt package paths
153 @prebuilt_pkgdirs, # Use to follow symlinks in prebuilt pkgdirs 153 @prebuilt_pkgdirs, # Use to follow symlinks in prebuilt pkgdirs
154 %prebuilt_pkgdir_cache, # To avoid symlink loops in prebuilt_pkgdirs 154 %prebuilt_pkgdir_cache, # To avoid symlink loops in prebuilt_pkgdirs
155); 155);
156 156
157# Horrible kludge to ensure we have a value for testing in conditionals, but 157# Horrible kludge to ensure we have a value for testing in conditionals, but
158# gets removed in the final evaluation 158# gets removed in the final evaluation
159my $magic_undefined = 'M_a_G_i_C_uNdEfInEd'; 159my $magic_undefined = 'M_a_G_i_C_uNdEfInEd';
160 160
161sub debug(@args) { 161sub debug(@args) {
162 $opt{D} and print STDERR 'DEBUG: ', @args, "\n"; 162 $opt{D} and print STDERR 'DEBUG: ', @args, "\n";
163} 163}
164 164
165sub verbose(@args) { 165sub verbose(@args) {
166 -t STDERR and print STDERR @args; 166 -t STDERR and print STDERR @args;
167} 167}
168 168
169sub fail($msg) { 169sub fail($msg) {
170 print STDERR $msg, "\n"; 170 print STDERR $msg, "\n";
171 exit(3); 171 exit(3);
172} 172}
173 173
174# List (recursive) non directory contents of specified directory 174# List (recursive) non directory contents of specified directory
175# 175#
176#TODO this entire sub should be replaced with direct calls to 176#TODO this entire sub should be replaced with direct calls to
177# File::Find 177# File::Find
178sub listdir($base, $dir = undef) { 178sub listdir($base, $dir = undef) {
179 my ($thisdir); 179 my ($thisdir);
180 my (@list, @thislist); 180 my (@list, @thislist);
181 181
182 $thisdir = $base; 182 $thisdir = $base;
183 if (defined $dir) { 183 if (defined $dir) {
184 $thisdir .= "/$dir"; 184 $thisdir .= "/$dir";
185 $dir .= '/'; 185 $dir .= '/';
186 } else { 186 } else {
187 $dir = ''; 187 $dir = '';
188 } 188 }
189 189
190 opendir(DIR, $thisdir) || fail("Unable to opendir($thisdir): $!"); 190 opendir(DIR, $thisdir) || fail("Unable to opendir($thisdir): $!");
191 @thislist = grep { substr($_, 0, 1) ne '.' && $_ ne 'CVS' } readdir(DIR); 191 @thislist = grep { substr($_, 0, 1) ne '.' && $_ ne 'CVS' } readdir(DIR);
192 closedir(DIR); 192 closedir(DIR);
193 foreach my $entry (@thislist) { 193 foreach my $entry (@thislist) {
194 if (-d "$thisdir/$entry") { 194 if (-d "$thisdir/$entry") {
195 push @list, listdir($base, "$dir$entry"); 195 push @list, listdir($base, "$dir$entry");
196 } else { 196 } else {
197 push @list, "$dir$entry"; 197 push @list, "$dir$entry";
198 } 198 }
199 } 199 }
200 @list; 200 @list;
201} 201}
202 202
203sub canonicalize_pkgname($pkgname) { 203sub canonicalize_pkgname($pkgname) {
204 $pkgname =~ s,^py\d+(?:pth|)-,py-,; 204 $pkgname =~ s,^py\d+(?:pth|)-,py-,;
205 $pkgname =~ s,^ruby\d+-,ruby-,; 205 $pkgname =~ s,^ruby\d+-,ruby-,;
206 $pkgname =~ s,^php\d+-,php-,; 206 $pkgname =~ s,^php\d+-,php-,;
207 return $pkgname; 207 return $pkgname;
208} 208}
209 209
210sub split_pkgversion($pkgversion) { 210sub split_pkgversion($pkgversion) {
211 $pkgversion = lc($pkgversion); 211 $pkgversion = lc($pkgversion);
212 212
213 # See pkgtools/pkg_install/files/lib/dewey.c. 213 # See pkgtools/pkg_install/files/lib/dewey.c.
214 my @temp = ($pkgversion =~ s/nb(\d+)//) ? +$1 : 0; 214 my @temp = ($pkgversion =~ s/nb(\d+)//) ? +$1 : 0;
215 foreach my $elem (split(/(pl|pre|rc|beta|alpha|\D)/, $pkgversion)) { 215 foreach my $elem (split(/(pl|pre|rc|beta|alpha|\D)/, $pkgversion)) {
216 if ($elem =~ /\d/) { 216 if ($elem =~ /\d/) {
217 push @temp, +$elem; 217 push @temp, +$elem;
218 } elsif ($elem eq 'pl' || $elem eq '.' || $elem eq '_') { 218 } elsif ($elem eq 'pl' || $elem eq '.' || $elem eq '_') {
219 push @temp, 0; 219 push @temp, 0;
220 } elsif ($elem eq 'pre' || $elem eq 'rc') { 220 } elsif ($elem eq 'pre' || $elem eq 'rc') {
221 push @temp, -1; 221 push @temp, -1;
222 } elsif ($elem eq 'beta') { 222 } elsif ($elem eq 'beta') {
223 push @temp, -2; 223 push @temp, -2;
224 } elsif ($elem eq 'alpha') { 224 } elsif ($elem eq 'alpha') {
225 push @temp, -3; 225 push @temp, -3;
226 } elsif ('a' le $elem && $elem le 'z') { 226 } elsif ('a' le $elem && $elem le 'z') {
227 push @temp, 0, ord($elem) - ord('a') + 1; 227 push @temp, 0, ord($elem) - ord('a') + 1;
228 } 228 }
229 } 229 }
230 @temp; 230 @temp;
231} 231}
232 232
233sub pkgversion_cmp($va, $op, $vb) { 233sub pkgversion_cmp($va, $op, $vb) {
234 my ($nb_a, @a) = split_pkgversion($va); 234 my ($nb_a, @a) = split_pkgversion($va);
235 my ($nb_b, @b) = split_pkgversion($vb); 235 my ($nb_b, @b) = split_pkgversion($vb);
236 236
237 my $cmp = 0; 237 my $cmp = 0;
238 while ($cmp == 0 && (@a || @b)) { 238 while ($cmp == 0 && (@a || @b)) {
239 $cmp = (shift @a || 0) <=> (shift @b || 0); 239 $cmp = (shift @a || 0) <=> (shift @b || 0);
240 } 240 }
241 $cmp ||= $nb_a <=> $nb_b; 241 $cmp ||= $nb_a <=> $nb_b;
242 242
243 $op eq '<' ? $cmp < 0 243 $op eq '<' ? $cmp < 0
244 : $op eq '<=' ? $cmp <= 0 244 : $op eq '<=' ? $cmp <= 0
245 : $op eq '>' ? $cmp > 0 245 : $op eq '>' ? $cmp > 0
246 : $cmp >= 0; 246 : $cmp >= 0;
247} 247}
248 248
249# Return a copy of $value in which trivial variable expressions are replaced 249# Return a copy of $value in which trivial variable expressions are replaced
250# with their variable values. 250# with their variable values.
251sub expand_exprs($value, $vars) { 251sub expand_exprs($value, $vars) {
252 while ($value =~ /\$\{([-\w.]+)\}/) { 252 while ($value =~ /\$\{([-\w.]+)\}/) {
253 $value = defined $vars->{$1} 253 $value = defined $vars->{$1}
254 ? "$`$vars->{$1}$'" 254 ? "$`$vars->{$1}$'"
255 : "$`$magic_undefined$'"; 255 : "$`$magic_undefined$'";
256 } 256 }
257 $value; 257 $value;
258} 258}
259 259
260sub eval_mk_cond_func($func, $arg, $vars) { 260sub eval_mk_cond_func($func, $arg, $vars) {
261 if ($func eq 'defined') { 261 if ($func eq 'defined') {
262 my $varname = expand_exprs($arg, $vars); 262 my $varname = expand_exprs($arg, $vars);
263 defined $vars->{$varname} ? 1 : 0; 263 defined $vars->{$varname} ? 1 : 0;
264 264
265 } elsif ($func eq 'empty') { 265 } elsif ($func eq 'empty') {
266 266
267 # Implement (some of) make's :M modifier 267 # Implement (some of) make's :M modifier
268 if ($arg =~ /^ ([^:]+) :M ([^:]+) $/x) { 268 if ($arg =~ /^ ([^:]+) :M ([^:]+) $/x) {
269 my ($varname, $pattern) = ($1, $2); 269 my ($varname, $pattern) = ($1, $2);
270 $varname = expand_exprs($varname, $vars); 270 $varname = expand_exprs($varname, $vars);
271 $pattern = expand_exprs($pattern, $vars); 271 $pattern = expand_exprs($pattern, $vars);
272 272
273 my $value = $vars->{$varname}; 273 my $value = $vars->{$varname};
274 return 1 unless defined $value; 274 return 1 unless defined $value;
275 275
276 $value = expand_exprs($value, $vars); 276 $value = expand_exprs($value, $vars);
277 277
278 $pattern =~ s/([{.+])/\\$1/g; 278 $pattern =~ s/([{.+])/\\$1/g;
279 $pattern =~ s/\*/.*/g; 279 $pattern =~ s/\*/.*/g;
280 $pattern =~ s/\?/./g; 280 $pattern =~ s/\?/./g;
281 $pattern = '^' . $pattern . '$'; 281 $pattern = '^' . $pattern . '$';
282 282
283 # XXX: Splitting by whitespace is not correct, but 283 # XXX: Splitting by whitespace is not correct, but
284 # it's good enough for lists with only unquoted 284 # it's good enough for lists with only unquoted
285 # words. See devel/bmake/files/str.c:brk_string. 285 # words. See devel/bmake/files/str.c:brk_string.
286 foreach my $word (split(/\s+/, $value)) { 286 foreach my $word (split(/\s+/, $value)) {
287 return 0 if $word =~ /$pattern/; 287 return 0 if $word =~ /$pattern/;
288 } 288 }
289 return 1; 289 return 1;
290 } elsif ($arg =~ /:M/) { 290 } elsif ($arg =~ /:M/) {
291 debug("Unsupported ':M' modifier in '$arg'"); 291 debug("Unsupported ':M' modifier in '$arg'");
292 } 292 }
293 293
294 my $value = expand_exprs("\${$arg}", $vars); 294 my $value = expand_exprs("\${$arg}", $vars);
295 defined $value && $value =~ /\S/ ? 0 : 1; 295 defined $value && $value =~ /\S/ ? 0 : 1;
296 296
297 } elsif ($func eq 'exists') { 297 } elsif ($func eq 'exists') {
298 my $fname = expand_exprs($arg, $vars); 298 my $fname = expand_exprs($arg, $vars);
299 -e $fname ? 1 : 0; 299 -e $fname ? 1 : 0;
300 300
301 } elsif ($func eq 'make') { 301 } elsif ($func eq 'make') {
302 0; 302 0;
303 303
304 } else { # $func eq 'target' 304 } else { # $func eq 'target'
305 0; 305 0;
306 } 306 }
307} 307}
308 308
309sub eval_mk_cond($line, $vars) { 309sub eval_mk_cond($line, $vars) {
310 my $cond = expand_exprs($line, $vars); 310 my $cond = expand_exprs($line, $vars);
311 311
312 # XXX This is _so_ wrong - need to parse this correctly 312 # XXX This is _so_ wrong - need to parse this correctly
313 $cond =~ s/""/\r/g; 313 $cond =~ s/""/\r/g;
314 $cond =~ s/"//g; 314 $cond =~ s/"//g;
315 $cond =~ s/\r/""/g; 315 $cond =~ s/\r/""/g;
316 316
317 debug("conditional: $cond"); 317 debug("conditional: $cond");
318 318
319 while ($cond =~ /(target|empty|make|defined|exists)\s*\(([^()]+)\)/) { 319 while ($cond =~ /(target|empty|make|defined|exists)\s*\(([^()]+)\)/) {
320 my ($func, $arg) = ($1, $2); 320 my ($func, $arg) = ($1, $2);
321 my $result = eval_mk_cond_func($func, $arg, $vars); 321 my $result = eval_mk_cond_func($func, $arg, $vars);
322 $cond =~ s/$func\s*\([^()]+\)/$result/; 322 $cond =~ s/$func\s*\([^()]+\)/$result/;
323 debug("conditional: update to $cond"); 323 debug("conditional: update to $cond");
324 } 324 }
325 325
326 while ($cond =~ /([^\s()\|\&]+) \s+ (!=|==) \s+ ([^\s()]+)/x) { 326 while ($cond =~ /([^\s()\|\&]+) \s+ (!=|==) \s+ ([^\s()]+)/x) {
327 my $result = 0 + (($2 eq '==') ? ($1 eq $3) : ($1 ne $3)); 327 my $result = 0 + (($2 eq '==') ? ($1 eq $3) : ($1 ne $3));
328 $cond =~ s/[^\s()\|\&]+ \s+ (!=|==) \s+ [^\s()]+/$result/x; 328 $cond =~ s/[^\s()\|\&]+ \s+ (!=|==) \s+ [^\s()]+/$result/x;
329 } 329 }
330 330
331 if ($cond =~ /^[ <> \d () \s & | . ! ]+$/xx) { 331 if ($cond =~ /^[ <> \d () \s & | . ! ]+$/xx) {
332 my $result = eval "($cond) ? 1 : 0"; 332 my $result = eval "($cond) ? 1 : 0";
333 defined $result or fail("Eval '$cond' failed in '$line': $@"); 333 defined $result or fail("Eval '$cond' failed in '$line': $@");
334 debug("conditional: evaluated to " . ($result ? 'true' : 'false')); 334 debug("conditional: evaluated to " . ($result ? 'true' : 'false'));
335 $result; 335 $result;
336 336
337 } else { 337 } else {
338 debug("conditional: defaulting '$cond' to true"); 338 debug("conditional: defaulting '$cond' to true");
339 1; 339 1;
340 } 340 }
341} 341}
342 342
343sub parse_makefile_line_include($file, $incfile, 343sub parse_makefile_line_include($file, $incfile,
344 $incdirs, $included, $lines, $vars) { 344 $incdirs, $included, $lines, $vars) {
345 345
346 # At this point just skip any includes which we were 346 # At this point just skip any includes which we were
347 # not able to fully expand. 347 # not able to fully expand.
348 if ($incfile =~ m#/mk/bsd# 348 if ($incfile =~ m#/mk/bsd#
349 || $incfile =~ /$magic_undefined/ 349 || $incfile =~ /$magic_undefined/
350 || $incfile =~ /\$\{/ 350 || $incfile =~ /\$\{/
351 || (!$opt{d} && $incfile =~ m#/(buildlink[^/]*\.mk)#)) { 351 || (!$opt{d} && $incfile =~ m#/(buildlink[^/]*\.mk)#)) {
352 debug("$file: .include \"$incfile\" skipped"); 352 debug("$file: .include \"$incfile\" skipped");
353 return; 353 return;
354 } 354 }
355 355
356 debug("$file: .include \"$incfile\""); 356 debug("$file: .include \"$incfile\"");
357 357
358 if (substr($incfile, 0, 1) ne '/') { 358 if (substr($incfile, 0, 1) ne '/') {
359 foreach my $dir (reverse @$incdirs) { 359 foreach my $dir (reverse @$incdirs) {
360 if (-f "$dir/$incfile") { 360 if (-f "$dir/$incfile") {
361 $incfile = "$dir/$incfile"; 361 $incfile = "$dir/$incfile";
362 last; 362 last;
363 } 363 }
364 } 364 }
365 } 365 }
366 366
367 # perl 5.6.1 realpath() cannot handle files, only directories. 367 # perl 5.6.1 realpath() cannot handle files, only directories.
368 # If the last component is a symlink, this will give a false 368 # If the last component is a symlink, this will give a false
369 # negative, but that is not a problem, as the duplicate check 369 # negative, but that is not a problem, as the duplicate check
370 # is for performance. 370 # is for performance.
371 $incfile =~ m#^(.+)(/[^/]+)$#; 371 $incfile =~ m#^(.+)(/[^/]+)$#;
372 372
373 if (!-f $incfile) { 373 if (!-f $incfile) {
374 $opt{L} or verbose("\n"); 374 $opt{L} or verbose("\n");
375 375
376 my $dirs = join(' ', @$incdirs); 376 my $dirs = join(' ', @$incdirs);
377 verbose("$file: Cannot locate $incfile in $dirs\n"); 377 verbose("$file: Cannot locate $incfile in $dirs\n");
378 return; 378 return;
379 } 379 }
380 380
381 $incfile = realpath($1) . $2; 381 $incfile = realpath($1) . $2;
382 return if $included->{$incfile}; 382 return if $included->{$incfile};
383 383
384 $opt{L} and print "inc $incfile\n"; 384 $opt{L} and print "inc $incfile\n";
385 $included->{$incfile} = 1; 385 $included->{$incfile} = 1;
386 386
387 if (!open(FILE, $incfile)) { 387 if (!open(FILE, $incfile)) {
388 verbose("Cannot open '$incfile' (from $file): $_ $!\n"); 388 verbose("Cannot open '$incfile' (from $file): $_ $!\n");
389 return; 389 return;
390 } 390 }
391 391
392 # FIXME: .CURDIR doesn't change, but .PARSEDIR does. 392 # FIXME: .CURDIR doesn't change, but .PARSEDIR does.
393 my $NEWCURDIR = $incfile; 393 my $NEWCURDIR = $incfile;
394 $NEWCURDIR =~ s#/[^/]*$##; 394 $NEWCURDIR =~ s#/[^/]*$##;
395 push @$incdirs, $NEWCURDIR 395 push @$incdirs, $NEWCURDIR
396 unless grep { $_ eq $NEWCURDIR } @$incdirs; 396 unless grep { $_ eq $NEWCURDIR } @$incdirs;
397 unshift @$lines, ".CURDIR=" . $vars->{'.CURDIR'}; 397 unshift @$lines, ".CURDIR=" . $vars->{'.CURDIR'};
398 chomp(my @inc_lines = <FILE>); 398 chomp(my @inc_lines = <FILE>);
399 unshift @$lines, @inc_lines; 399 unshift @$lines, @inc_lines;
400 unshift @$lines, ".CURDIR=$NEWCURDIR"; 400 unshift @$lines, ".CURDIR=$NEWCURDIR";
401 close(FILE); 401 close(FILE);
402} 402}
403 403
404sub parse_makefile_line_var($varname, $op, $value, $vars) { 404sub parse_makefile_line_var($varname, $op, $value, $vars) {
405 if ($op eq ':=') { 405 if ($op eq ':=') {
406 $vars->{$varname} = expand_exprs($value, $vars); 406 $vars->{$varname} = expand_exprs($value, $vars);
407 } elsif ($op eq '+=' && defined $vars->{$varname}) { 407 } elsif ($op eq '+=' && defined $vars->{$varname}) {
408 $vars->{$varname} .= " $value"; 408 $vars->{$varname} .= " $value";
409 } elsif ($op eq '?=' && defined $vars->{$varname}) { 409 } elsif ($op eq '?=' && defined $vars->{$varname}) {
410 # Do nothing. 410 # Do nothing.
411 } else { 411 } else {
412 $vars->{$varname} = $value; 412 $vars->{$varname} = $value;
413 } 413 }
414 debug($op eq '=' 414 debug($op eq '='
415 ? "assignment: $varname $op $value" 415 ? "assignment: $varname $op $value"
416 : "assignment: $varname $op $value => $vars->{$varname}"); 416 : "assignment: $varname $op $value => $vars->{$varname}");
417 417
418 # Give python a little hand (XXX - do we wanna consider actually 418 # Give python a little hand (XXX - do we wanna consider actually
419 # implementing make .for loops, etc? 419 # implementing make .for loops, etc?
420 # 420 #
421 if ($varname eq 'PYTHON_VERSIONS_ACCEPTED') { 421 if ($varname eq 'PYTHON_VERSIONS_ACCEPTED') {
422 foreach my $pv (split(/\s+/, $vars->{PYTHON_VERSIONS_ACCEPTED})) { 422 foreach my $pv (split(/\s+/, $vars->{PYTHON_VERSIONS_ACCEPTED})) {
423 $vars->{'_PYTHON_VERSION_FIRSTACCEPTED'} ||= $pv; 423 $vars->{'_PYTHON_VERSION_FIRSTACCEPTED'} ||= $pv;
424 $vars->{"_PYTHON_VERSION_${pv}_OK"} = 'yes'; 424 $vars->{"_PYTHON_VERSION_${pv}_OK"} = 'yes';
425 } 425 }
426 } 426 }
427} 427}
428 428
429sub expand_modifiers($file, $varname, $left, $subvar, $mods, $right, $vars) { 429sub expand_modifiers($file, $varname, $left, $subvar, $mods, $right, $vars) {
430 my @mods = split(':', $mods); 430 my @mods = split(':', $mods);
431 my $result = $vars->{$subvar}; 431 my $result = $vars->{$subvar};
432 $result = '' unless defined $result; 432 $result = '' unless defined $result;
433 433
434 # If the value of $subvar contains a '$', skip it on this pass. 434 # If the value of $subvar contains a '$', skip it on this pass.
435 # Hopefully it will get substituted and we can catch it 435 # Hopefully it will get substituted and we can catch it
436 # next time around. 436 # next time around.
437 return 0 if index($result, '${') != -1; 437 return 0 if index($result, '${') != -1;
438 438
439 debug("$file: substitutelist $varname ($result) $subvar (@mods)"); 439 debug("$file: substitutelist $varname ($result) $subvar (@mods)");
440 foreach (@mods) { 440 foreach (@mods) {
441 debug("expanding modifier '$_'"); 441 debug("expanding modifier '$_'");
442 442
443 if (m#^ (U) (.*) #x) { 443 if (m#^ (U) (.*) #x) {
444 $result = $2 unless defined $vars->{$subvar}; 444 $result = $2 unless defined $vars->{$subvar};
445 445
446 } elsif (m#^ ([CS]) (.) ([^/\@]+) \2 ([^/\@]*) \2 ([1g]*) #x) { 446 } elsif (m#^ ([CS]) (.) ([^/\@]+) \2 ([^/\@]*) \2 ([1g]*) #x) {
447 # TODO: Use non-greedy repetitions above. 447 # TODO: Use non-greedy repetitions above.
448 # TODO: Properly handle separators other than '/' and '@'. 448 # TODO: Properly handle separators other than '/' and '@'.
449 my ($how, $from, $to, $global) = ($1, $3, $4, $5); 449 my ($how, $from, $to, $global) = ($1, $3, $4, $5);
450 450
451 debug("$file: ':S' $subvar, $how, $from, $to, $global"); 451 debug("$file: ':S' $subvar, $how, $from, $to, $global");
452 if ($how eq 'S') { 452 if ($how eq 'S') {
453 # Limited substitution - keep ^ and $ 453 # Limited substitution - keep ^ and $
454 $from =~ s/([?.{}\]\[*+])/\\$1/g; 454 $from =~ s/([?.{}\]\[*+])/\\$1/g;
455 } 455 }
456 $to =~ s/\\(\d)/\$$1/g; # Change \1 etc to $1 456 $to =~ s/\\(\d)/\$$1/g; # Change \1 etc to $1
457 $to =~ s/\&/\$&/g; 457 $to =~ s/\&/\$&/g;
458 458
459 my ($notfirst); 459 my ($notfirst);
460 if ($global =~ s/1//) { 460 if ($global =~ s/1//) {
461 # FIXME: The modifier '1' applies to the first 461 # FIXME: The modifier '1' applies to the first
462 # occurrence in any word, it doesn't have to 462 # occurrence in any word, it doesn't have to
463 # be in the first word. 463 # be in the first word.
464 ($from, $notfirst) = split('\s', $from, 2); 464 ($from, $notfirst) = split('\s', $from, 2);
465 } 465 }
466 466
467 debug("$file: substituteperl $subvar, $how, $from, $to"); 467 debug("$file: substituteperl $subvar, $how, $from, $to");
468 debug("eval substitute <$from> <$to> <$global>"); 468 debug("eval substitute <$from> <$to> <$global>");
469 eval "\$result =~ s/$from/$to/$global"; 469 eval "\$result =~ s/$from/$to/$global";
470 if (defined $notfirst) { 470 if (defined $notfirst) {
471 $result .= " $notfirst"; 471 $result .= " $notfirst";
472 } 472 }
473 473
474 } else { 474 } else {
475 debug("$file: variable '$varname' has unknown modifier '$_'"); 475 debug("$file: variable '$varname' has unknown modifier '$_'");
476 } 476 }
477 } 477 }
478 478
479 $result = '' if !defined $result; 479 $result = '' if !defined $result;
480 $vars->{$varname} = "$left$result$right"; 480 $vars->{$varname} = "$left$result$right";
481 return 1; 481 return 1;
482} 482}
483 483
484# Extract variable assignments from Makefile 484# Extract variable assignments from Makefile
485# Much unpalatable magic to avoid having to use make (all for speed) 485# Much unpalatable magic to avoid having to use make (all for speed)
486# 486#
487sub parse_makefile_vars($file, $cwd = undef) { 487sub parse_makefile_vars($file, $cwd = undef) {
488 my %vars; 488 my %vars;
489 my %incfiles; # Cache of previously included files 489 my %incfiles; # Cache of previously included files
490 my @incdirs; # Directories in which to check for includes 490 my @incdirs; # Directories in which to check for includes
491 my @lines; 491 my @lines;
492 492
493 open(FILE, $file) or return undef; 493 open(FILE, $file) or return undef;
494 chomp(@lines = <FILE>); 494 chomp(@lines = <FILE>);
495 close(FILE); 495 close(FILE);
496 496
497 push @incdirs, '.'; 497 push @incdirs, '.';
498 push @incdirs, dirname($file); 498 push @incdirs, dirname($file);
499 499
500 # Some Makefiles depend on these being set 500 # Some Makefiles depend on these being set
501 if ($file eq $conf_makeconf) { 501 if ($file eq $conf_makeconf) {
502 $vars{LINTPKGSRC} = 'YES'; 502 $vars{LINTPKGSRC} = 'YES';
503 } else { 503 } else {
504 %vars = %{$default_vars}; 504 %vars = %{$default_vars};
505 } 505 }
506 $vars{BSD_PKG_MK} = 'YES'; 506 $vars{BSD_PKG_MK} = 'YES';
507 507
508 my $curdir = $cwd || ($file =~ m#(.*)/# ? $1 : getcwd); 508 my $curdir = $cwd || ($file =~ m#(.*)/# ? $1 : getcwd);
509 $vars{'.CURDIR'} = $curdir; 509 $vars{'.CURDIR'} = $curdir;
510 push @incdirs, $curdir; 510 push @incdirs, $curdir;
511 if ($opt{L}) { 511 if ($opt{L}) {
512 print "$file\n"; 512 print "$file\n";
513 } 513 }
514 514
515 my @if_state; # 'not_yet', 'active', 'done' 515 my @if_state; # 'not_yet', 'active', 'done'
516 while (defined($_ = shift @lines)) { 516 while (defined($_ = shift @lines)) {
517 s/(*negative_lookbehind:\\)#.*//; 517 s/(*negative_lookbehind:\\)#.*//;
518 s/\s+$//; 518 s/\s+$//;
519 519
520 # Join continuation lines. 520 # Join continuation lines.
521 # See devel/bmake/files/parse.c, 'replace following'. 521 # See devel/bmake/files/parse.c, 'replace following'.
522 while (substr($_, -1) eq "\\" && @lines > 0) { 522 while (substr($_, -1) eq "\\" && @lines > 0) {
523 my $cont = shift @lines; 523 my $cont = shift @lines;
524 $cont =~ s,^\s*, ,; 524 $cont =~ s,^\s*, ,;
525 $cont =~ s/(*negative_lookbehind:\\)#.*//; 525 $cont =~ s/(*negative_lookbehind:\\)#.*//;
526 $cont =~ s/\s+$//; 526 $cont =~ s/\s+$//;
527 substr($_, -1) = $cont; 527 substr($_, -1) = $cont;
528 } 528 }
529 529
530 # Conditionals 530 # Conditionals
531 # 531 #
532 if (m#^ \. \s* (if|ifdef|ifndef) \s+ (.*) #x) { 532 if (m#^ \. \s* (if|ifdef|ifndef) \s+ (.*) #x) {
533 my ($kind, $cond) = ($1, $2); 533 my ($kind, $cond) = ($1, $2);
534 534
535 if (@if_state > 0 && $if_state[-1] ne 'active') { 535 if (@if_state > 0 && $if_state[-1] ne 'active') {
536 push @if_state, 'done'; 536 push @if_state, 'done';
537 537
538 } elsif ($kind eq 'if') { 538 } elsif ($kind eq 'if') {
539 my $result = eval_mk_cond($cond, \%vars); 539 my $result = eval_mk_cond($cond, \%vars);
540 push @if_state, $result ? 'active' : 'not_yet'; 540 push @if_state, $result ? 'active' : 'not_yet';
541 541
542 } else { 542 } else {
543 my $varname = expand_exprs($cond, \%vars); 543 my $varname = expand_exprs($cond, \%vars);
544 544
545 # bmake also allows '.ifdef A && B'. 545 # bmake also allows '.ifdef A && B'.
546 debug("not implemented: .ifdef $varname") 546 debug("not implemented: .ifdef $varname")
547 if $cond =~ /\s/; 547 if $cond =~ /\s/;
548 548
549 my $result = $kind eq 'ifdef' 549 my $result = $kind eq 'ifdef'
550 ? defined($vars{$varname}) 550 ? defined($vars{$varname})
551 : !defined($vars{$varname}); 551 : !defined($vars{$varname});
552 push @if_state, $result ? 'active' : 'not_yet'; 552 push @if_state, $result ? 'active' : 'not_yet';
553 } 553 }
554 554
555 debug("$file: .$kind @if_state"); 555 debug("$file: .$kind @if_state");
556 556
557 } elsif ( # XXX: bmake also knows '.elifdef' and '.elifnmake'. 557 } elsif ( # XXX: bmake also knows '.elifdef' and '.elifnmake'.
558 m#^ \. \s* elif \s+ (.*)#x && @if_state > 0) { 558 m#^ \. \s* elif \s+ (.*)#x && @if_state > 0) {
559 my ($cond) = ($1); 559 my ($cond) = ($1);
560 560
561 if ($if_state[-1] eq 'active') { 561 if ($if_state[-1] eq 'active') {
562 $if_state[-1] = 'done'; 562 $if_state[-1] = 'done';
563 } elsif ($if_state[-1] eq 'not_yet' 563 } elsif ($if_state[-1] eq 'not_yet'
564 && eval_mk_cond($cond, \%vars)) { 564 && eval_mk_cond($cond, \%vars)) {
565 $if_state[-1] = 'active'; 565 $if_state[-1] = 'active';
566 } 566 }
567 debug("$file: .elif @if_state"); 567 debug("$file: .elif @if_state");
568 568
569 } elsif (m#^ \. \s* else \b #x && @if_state > 0) { 569 } elsif (m#^ \. \s* else \b #x && @if_state > 0) {
570 $if_state[-1] = 570 $if_state[-1] =
571 $if_state[-1] eq 'not_yet' ? 'active' : 'done'; 571 $if_state[-1] eq 'not_yet' ? 'active' : 'done';
572 debug("$file: .else @if_state"); 572 debug("$file: .else @if_state");
573 573
574 } elsif (m#^\. \s* endif \b #x) { 574 } elsif (m#^\. \s* endif \b #x) {
575 pop @if_state; 575 pop @if_state;
576 debug("$file: .endif @if_state"); 576 debug("$file: .endif @if_state");
577 577
578 } elsif (@if_state > 0 && $if_state[-1] ne 'active') { 578 } elsif (@if_state > 0 && $if_state[-1] ne 'active') {
579 # Skip branches whose condition evaluated to false. 579 # Skip branches whose condition evaluated to false.
580 580
581 } elsif (m#^\. \s* include \s+ "([^"]+)" #x) { 581 } elsif (m#^\. \s* include \s+ "([^"]+)" #x) {
582 my $incfile = expand_exprs($1, \%vars); 582 my $incfile = expand_exprs($1, \%vars);
583 583
584 parse_makefile_line_include($file, $incfile, 584 parse_makefile_line_include($file, $incfile,
585 \@incdirs, \%incfiles, \@lines, \%vars); 585 \@incdirs, \%incfiles, \@lines, \%vars);
586 586
587 } elsif (m#^[ ]* ([-\w\.]+) \s* ([:+?]?=) \s* (.*)#x) { 587 } elsif (m#^[ ]* ([-\w\.]+) \s* ([:+?]?=) \s* (.*)#x) {
588 parse_makefile_line_var($1, $2, $3, \%vars); 588 parse_makefile_line_var($1, $2, $3, \%vars);
589 589
590 } elsif ($_ eq '' || m#^\s*\## || m#^\t#) { 590 } elsif ($_ eq '' || m#^\s*\## || m#^\t#) {
591 # Skip comment lines and shell commands. 591 # Skip comment lines and shell commands.
592 592
593 } else { 593 } else {
594 debug("$file: unknown line '$_'"); 594 debug("$file: unknown line '$_'");
595 } 595 }
596 } 596 }
597 597
598 debug("$file: expand"); 598 debug("$file: expand");
599 599
600 # Handle variable substitutions FRED = a-${JIM:S/-/-b-/} 600 # Handle variable substitutions FRED = a-${JIM:S/-/-b-/}
601 601
602 for (my $loop = 1; $loop != 0;) { 602 for (my $loop = 1; $loop != 0;) {
603 $loop = 0; 603 $loop = 0;
604 foreach my $key (keys %vars) { 604 foreach my $key (keys %vars) {
605 next if index($vars{$key}, '$') == -1; 605 next if index($vars{$key}, '$') == -1;
606 606
607 $_ = expand_exprs($vars{$key}, \%vars); 607 $_ = expand_exprs($vars{$key}, \%vars);
608 if ($_ ne $vars{$key}) { 608 if ($_ ne $vars{$key}) {
609 $vars{$key} = $_; 609 $vars{$key} = $_;
610 $loop = 1; 610 $loop = 1;
611 611
612 } elsif ($vars{$key} =~ m#\$\{([\w.]+):([CS]([^{}])[^{}\3]+\3[^{}\3]*\3[g1]*(|:[^{}]+)|U[^{}]+)\}#) { 612 } elsif ($vars{$key} =~ m#\$\{([\w.]+):([CS]([^{}])[^{}\3]+\3[^{}\3]*\3[g1]*(|:[^{}]+)|U[^{}]+)\}#) {
613 $loop ||= expand_modifiers($file, $key, $`, $1, $2, $', \%vars); 613 $loop ||= expand_modifiers($file, $key, $`, $1, $2, $', \%vars);
614 } 614 }
615 } 615 }
616 } 616 }
617 617
618 foreach my $key (keys %vars) { 618 foreach my $key (keys %vars) {
619 $vars{$key} =~ s/$magic_undefined//; 619 $vars{$key} =~ s/$magic_undefined//;
620 } 620 }
621 \%vars; 621 \%vars;
622} 622}
623 623
624sub get_default_makefile_vars() { 624sub get_default_makefile_vars() {
625 625
626 chomp($_ = `uname -srm`); 626 chomp($_ = `uname -srm`);
627 ( 627 (
628 $default_vars->{OPSYS}, 628 $default_vars->{OPSYS},
629 $default_vars->{OS_VERSION}, 629 $default_vars->{OS_VERSION},
630 $default_vars->{MACHINE} 630 $default_vars->{MACHINE}
631 ) = split; 631 ) = split;
632 $default_vars->{MACHINE} 632 $default_vars->{MACHINE}
633 or die('Unable to extract machine from uname'); 633 or die('Unable to extract machine from uname');
634 634
635 # Handle systems without uname -p (NetBSD pre 1.4) 635 # Handle systems without uname -p (NetBSD pre 1.4)
636 chomp($default_vars->{MACHINE_ARCH} = `uname -p 2>/dev/null`); 636 chomp($default_vars->{MACHINE_ARCH} = `uname -p 2>/dev/null`);
637 637
638 if (!$default_vars->{MACHINE_ARCH} 638 if (!$default_vars->{MACHINE_ARCH}
639 && $default_vars->{OS_VERSION} eq 'NetBSD') { 639 && $default_vars->{OS_VERSION} eq 'NetBSD') {
640 chomp($default_vars->{MACHINE_ARCH} = `sysctl -n hw.machine_arch`); 640 chomp($default_vars->{MACHINE_ARCH} = `sysctl -n hw.machine_arch`);
641 } 641 }
642 642
643 $default_vars->{MACHINE_ARCH} ||= $default_vars->{MACHINE}; 643 $default_vars->{MACHINE_ARCH} ||= $default_vars->{MACHINE};
644 644
645 $default_vars->{OBJECT_FMT} = 'x'; 645 $default_vars->{OBJECT_FMT} = 'x';
646 $default_vars->{LOWER_OPSYS} = lc($default_vars->{OPSYS}); 646 $default_vars->{LOWER_OPSYS} = lc($default_vars->{OPSYS});
647 647
648 $default_vars->{PKGSRCDIR} = $opt{P} 648 $default_vars->{PKGSRCDIR} = $opt{P}
649 ? realpath($opt{P}) 649 ? realpath($opt{P})
650 : $conf_pkgsrcdir; 650 : $conf_pkgsrcdir;
651 651
652 $default_vars->{DESTDIR} = ''; 652 $default_vars->{DESTDIR} = '';
653 $default_vars->{LOCALBASE} = $conf_pkgsrcdir; 653 $default_vars->{LOCALBASE} = $conf_pkgsrcdir;
654 $default_vars->{X11BASE} = $conf_x11base; 654 $default_vars->{X11BASE} = $conf_x11base;
655 655
656 my ($vars); 656 my ($vars);
657 if (-f $conf_makeconf && 657 if (-f $conf_makeconf &&
658 ($vars = parse_makefile_vars($conf_makeconf, undef))) { 658 ($vars = parse_makefile_vars($conf_makeconf, undef))) {
659 foreach my $var (keys %{$vars}) { 659 foreach my $var (keys %{$vars}) {
660 $default_vars->{$var} = $vars->{$var}; 660 $default_vars->{$var} = $vars->{$var};
661 } 661 }
662 } 662 }
663 663
664 # XXX: repeated from above? 664 # XXX: repeated from above?
665 if ($opt{P}) { 665 if ($opt{P}) {
666 $default_vars->{PKGSRCDIR} = realpath($opt{P}); 666 $default_vars->{PKGSRCDIR} = realpath($opt{P});
667 } 667 }
668 668
669 if ($opt{M}) { 669 if ($opt{M}) {
670 $default_vars->{DISTDIR} = realpath($opt{M}); 670 $default_vars->{DISTDIR} = realpath($opt{M});
671 } else { 671 } else {
672 $default_vars->{DISTDIR} ||= $default_vars->{PKGSRCDIR} . '/distfiles'; 672 $default_vars->{DISTDIR} ||= $default_vars->{PKGSRCDIR} . '/distfiles';
673 } 673 }
674 674
675 if ($opt{K}) { 675 if ($opt{K}) {
676 $default_vars->{PACKAGES} = realpath($opt{K}); 676 $default_vars->{PACKAGES} = realpath($opt{K});
677 } 677 }
678 678
679 # Extract some variables from bsd.pkg.mk 679 # Extract some variables from bsd.pkg.mk
680 my ($mkvars); 680 my ($mkvars);
681 $mkvars = parse_makefile_vars( 681 $mkvars = parse_makefile_vars(
682 "$default_vars->{PKGSRCDIR}/mk/bsd.pkg.mk", 682 "$default_vars->{PKGSRCDIR}/mk/bsd.pkg.mk",
683 "$default_vars->{PKGSRCDIR}/mk/scripts" 683 "$default_vars->{PKGSRCDIR}/mk/scripts"
684 ); 684 );
685 foreach my $varname (keys %{$mkvars}) { 685 foreach my $varname (keys %{$mkvars}) {
686 if ($varname =~ /_REQD$/ || $varname eq 'EXTRACT_SUFX') { 686 if ($varname =~ /_REQD$/ || $varname eq 'EXTRACT_SUFX') {
687 $default_vars->{$varname} = $mkvars->{$varname}; 687 $default_vars->{$varname} = $mkvars->{$varname};
688 } 688 }
689 } 689 }
690 690
691 $default_vars->{PACKAGES} ||= $default_vars->{PKGSRCDIR} . '/packages'; 691 $default_vars->{PACKAGES} ||= $default_vars->{PKGSRCDIR} . '/packages';
692} 692}
693 693
694# Determine if a package version is current. If not, report correct version 694# Determine if a package version is current. If not, report correct version
695# if found 695# if found
696# 696#
697sub invalid_version($pkgmatch) { 697sub invalid_version($pkgmatch) {
698 my ($fail, $ok); 698 my ($fail, $ok);
699 my (@pkgmatches); 699 my (@pkgmatches);
700 700
701 my @todo = ($pkgmatch); 701 my @todo = ($pkgmatch);
702 702
703 # We handle {} here, everything else in package_globmatch 703 # We handle {} here, everything else in package_globmatch
704 while ($pkgmatch = shift @todo) { 704 while ($pkgmatch = shift @todo) {
705 if ($pkgmatch =~ /(.*)\{([^{}]+)}(.*)/) { 705 if ($pkgmatch =~ /(.*)\{([^{}]+)}(.*)/) {
706 foreach (split(',', $2)) { 706 foreach (split(',', $2)) {
707 push @todo, "$1$_$3"; 707 push @todo, "$1$_$3";
708 } 708 }
709 } else { 709 } else {
710 push @pkgmatches, $pkgmatch; 710 push @pkgmatches, $pkgmatch;
711 } 711 }
712 } 712 }
713 713
714 foreach $pkgmatch (@pkgmatches) { 714 foreach $pkgmatch (@pkgmatches) {
715 my ($pkg, $badver) = package_globmatch($pkgmatch); 715 my ($pkg, $badver) = package_globmatch($pkgmatch);
716 716
717 if (defined $badver) { 717 if (defined $badver) {
718 if (my $pkgs = $pkgdb->pkgs($pkg)) { 718 if (my $pkgs = $pkgdb->pkgs($pkg)) {
719 $fail .= 719 $fail .=
720 "Version mismatch: '$pkg' $badver vs " 720 "Version mismatch: '$pkg' $badver vs "
721 . join(',', $pkgs->versions) . "\n"; 721 . join(',', $pkgs->versions) . "\n";
722 } else { 722 } else {
723 $fail .= "Unknown package: '$pkg' version $badver\n"; 723 $fail .= "Unknown package: '$pkg' version $badver\n";
724 } 724 }
725 } else { 725 } else {
726 # If we find one match, don't bitch about others 726 # If we find one match, don't bitch about others
727 $ok = 1; 727 $ok = 1;
728 } 728 }
729 } 729 }
730 $ok ? undef : $fail; 730 $ok ? undef : $fail;
731} 731}
732 732
733sub list_installed_packages() { 733sub list_installed_packages() {
734 open(PKG_INFO, "$conf_pkg_info -e '*' |") 734 open(PKG_INFO, "$conf_pkg_info -e '*' |")
735 or fail("Unable to run $conf_pkg_info: $!"); 735 or fail("Unable to run $conf_pkg_info: $!");
736 chomp(my @pkgs = <PKG_INFO>); 736 chomp(my @pkgs = <PKG_INFO>);
737 close(PKG_INFO); 737 close(PKG_INFO);
738 map { $_ = canonicalize_pkgname($_) } @pkgs; 738 map { $_ = canonicalize_pkgname($_) } @pkgs;
739} 739}
740 740
741# List top level pkgsrc categories 741# List top level pkgsrc categories
742# 742#
743sub list_pkgsrc_categories($pkgsrcdir) { 743sub list_pkgsrc_categories($pkgsrcdir) {
744 my (@categories); 744 my (@categories);
745 745
746 opendir(BASE, $pkgsrcdir) || die("Unable to opendir($pkgsrcdir): $!"); 746 opendir(BASE, $pkgsrcdir) || die("Unable to opendir($pkgsrcdir): $!");
747 @categories = 747 @categories =
748 grep(substr($_, 0, 1) ne '.' 748 grep(substr($_, 0, 1) ne '.'
749 && $_ ne 'CVS' 749 && $_ ne 'CVS'
750 && -f "$pkgsrcdir/$_/Makefile", 750 && -f "$pkgsrcdir/$_/Makefile",
751 readdir(BASE)); 751 readdir(BASE));
752 closedir(BASE); 752 closedir(BASE);
753 @categories; 753 @categories;
754} 754}
755 755
756# For a given category, list potentially valid pkgdirs 756# For a given category, list potentially valid pkgdirs
757# 757#
758sub list_pkgsrc_pkgdirs($pkgsrcdir, $cat) { 758sub list_pkgsrc_pkgdirs($pkgsrcdir, $cat) {
759 opendir(CAT, "$pkgsrcdir/$cat") 759 opendir(CAT, "$pkgsrcdir/$cat")
760 or die("Unable to opendir($pkgsrcdir/$cat): $!"); 760 or die("Unable to opendir($pkgsrcdir/$cat): $!");
761 my @pkgdirs = sort grep { 761 my @pkgdirs = sort grep {
762 $_ ne 'Makefile' 762 $_ ne 'Makefile'
763 && $_ ne 'pkg' 763 && $_ ne 'pkg'
764 && $_ ne 'CVS' 764 && $_ ne 'CVS'
765 && substr($_, 0, 1) ne '.' 765 && substr($_, 0, 1) ne '.'
766 } readdir(CAT); 766 } readdir(CAT);
767 closedir(CAT) or die; 767 closedir(CAT) or die;
768 @pkgdirs; 768 @pkgdirs;
769} 769}
770 770
771# Convert the glob pattern to a regular expression. 771# Convert the glob pattern to a regular expression.
772# Return '' if the regular expression equals the glob expression. 772# Return '' if the regular expression equals the glob expression.
773# Return undef on error. 773# Return undef on error.
774sub glob2regex($glob) { 774sub glob2regex($glob) {
775 my @chars = split(//, $glob); 775 my @chars = split(//, $glob);
776 my $alternative_depth = 0; 776 my $alternative_depth = 0;
777 my $regex = ''; 777 my $regex = '';
778 while (defined($_ = shift @chars)) { 778 while (defined($_ = shift @chars)) {
779 if ($_ eq '*') { 779 if ($_ eq '*') {
780 $regex .= '.*'; 780 $regex .= '.*';
781 } elsif ($_ eq '?') { 781 } elsif ($_ eq '?') {
782 $regex .= '.'; 782 $regex .= '.';
783 } elsif ($_ eq '+') { 783 } elsif ($_ eq '+') {
784 $regex .= '\\+'; 784 $regex .= '\\+';
785 } elsif ($_ eq '\\' && @chars > 0) { 785 } elsif ($_ eq '\\' && @chars > 0) {
786 my $next = shift @chars; 786 my $next = shift @chars;
787 $regex .= $next =~ /\w/ ? $next : "\\$next"; 787 $regex .= $next =~ /\w/ ? $next : "\\$next";
788 } elsif ($_ eq '.' || $_ eq '|') { 788 } elsif ($_ eq '.' || $_ eq '|') {
789 $regex .= "\\$_"; 789 $regex .= "\\$_";
790 } elsif ($_ eq '{') { 790 } elsif ($_ eq '{') {
791 $regex .= '('; 791 $regex .= '(';
792 ++$alternative_depth; 792 ++$alternative_depth;
793 } elsif ($_ eq '}') { 793 } elsif ($_ eq '}') {
794 return undef if $alternative_depth == 0; 794 return undef if $alternative_depth == 0;
795 $regex .= ')'; 795 $regex .= ')';
796 --$alternative_depth; 796 --$alternative_depth;
797 } elsif ($_ eq ',' && $alternative_depth > 0) { 797 } elsif ($_ eq ',' && $alternative_depth > 0) {
798 $regex .= '|'; 798 $regex .= '|';
799 } elsif ($_ eq '[') { 799 } elsif ($_ eq '[') {
800 $regex .= '['; 800 $regex .= '[';
801 while (defined($_ = shift @chars)) { 801 while (defined($_ = shift @chars)) {
802 $regex .= $_; 802 $regex .= $_;
803 if ($_ eq ']') { 803 if ($_ eq ']') {
804 last; 804 last;
805 } elsif ($_ eq '\\' && @chars > 0) { 805 } elsif ($_ eq '\\' && @chars > 0) {
806 $regex .= shift @chars; 806 $regex .= shift @chars;
807 } 807 }
808 } 808 }
809 return undef if $_ ne ']'; 809 return undef if $_ ne ']';
810 } else { 810 } else {
811 $regex .= $_; 811 $regex .= $_;
812 } 812 }
813 } 813 }
814 814
815 return undef if $alternative_depth > 0; 815 return undef if $alternative_depth > 0;
816 return '' if $regex eq $glob; # XXX: why? 816 return '' if $regex eq $glob; # XXX: why?
817 817
818 $opt{D} and print "glob2regex: $glob -> $regex\n"; 818 $opt{D} and print "glob2regex: $glob -> $regex\n";
819 '^' . $regex . '$'; 819 '^' . $regex . '$';
820} 820}
821 821
822# Perform some (reasonable) subset of 'pkg_info -e' / glob(3) 822# Perform some (reasonable) subset of 'pkg_info -e' / glob(3)
823# Returns (sometimes best guess at) package name, 823# Returns (sometimes best guess at) package name,
824# and either 'problem version' or undef if all OK 824# and either 'problem version' or undef if all OK
825# 825#
826sub package_globmatch($pkgmatch) { 826sub package_globmatch($pkgmatch) {
827 my ($matchpkgname, $matchver, $regex); 827 my ($matchpkgname, $matchver, $regex);
828 828
829 if ($pkgmatch =~ /^([^*?[]+)(<|>|<=|>=|-)(\d[^*?[{]*)$/) { 829 if ($pkgmatch =~ /^([^*?[]+)(<|>|<=|>=|-)(\d[^*?[{]*)$/) {
830 830
831 # (package)(cmp)(pkgversion) 831 # (package)(cmp)(pkgversion)
832 my ($test, @pkgvers); 832 my ($test, @pkgvers);
833 833
834 # TODO: rename $matchpkgname to be more accurate. 834 # TODO: rename $matchpkgname to be more accurate.
835 ($matchpkgname, $test, $matchver) = ($1, $2, $3); 835 ($matchpkgname, $test, $matchver) = ($1, $2, $3);
836 if (@pkgvers = $pkgdb->pkgvers_by_pkgbase($matchpkgname)) { 836 if (@pkgvers = $pkgdb->pkgvers_by_pkgbase($matchpkgname)) {
837 foreach my $pkgver (@pkgvers) { 837 foreach my $pkgver (@pkgvers) {
838 if ($test eq '-') { 838 if ($test eq '-') {
839 if ($pkgver->pkgversion eq $matchver) { 839 if ($pkgver->pkgversion eq $matchver) {
840 $matchver = undef; 840 $matchver = undef;
841 last; 841 last;
842 } 842 }
843 } else { 843 } else {
844 if (pkgversion_cmp($pkgver->pkgversion, $test, $matchver)) { 844 if (pkgversion_cmp($pkgver->pkgversion, $test, $matchver)) {
845 $matchver = undef; 845 $matchver = undef;
846 last; 846 last;
847 } 847 }
848 } 848 }
849 } 849 }
850 850
851 if ($matchver && $test ne '-') { 851 if ($matchver && $test ne '-') {
852 $matchver = "$test$matchver"; 852 $matchver = "$test$matchver";
853 } 853 }
854 } 854 }
855 855
856 } elsif ($pkgmatch =~ /^([^[]+)-([\d*?{[].*)$/) { 856 } elsif ($pkgmatch =~ /^([^[]+)-([\d*?{[].*)$/) {
857 857
858 # (package)-(globver) 858 # (package)-(globver)
859 my (@pkgnames); 859 my (@pkgnames);
860 860
861 ($matchpkgname, $matchver) = ($1, $2); 861 ($matchpkgname, $matchver) = ($1, $2);
862 862
863 if (defined $pkgdb->pkgs($matchpkgname)) { 863 if (defined $pkgdb->pkgs($matchpkgname)) {
864 push @pkgnames, $matchpkgname; 864 push @pkgnames, $matchpkgname;
865 865
866 } elsif ($regex = glob2regex($matchpkgname)) { 866 } elsif ($regex = glob2regex($matchpkgname)) {
867 foreach my $pkg ($pkgdb->pkgs) { 867 foreach my $pkg ($pkgdb->pkgs) {
868 if ($pkg->pkgbase =~ /$regex/) { 868 if ($pkg->pkgbase =~ /$regex/) {
869 push @pkgnames, $pkg->pkgbase; 869 push @pkgnames, $pkg->pkgbase;
870 } 870 }
871 } 871 }
872 } 872 }
873 873
874 # Try to convert $matchver into regex version 874 # Try to convert $matchver into regex version
875 # 875 #
876 $regex = glob2regex($matchver); 876 $regex = glob2regex($matchver);
877 877
878 foreach my $pkg (@pkgnames) { 878 foreach my $pkg (@pkgnames) {
879 if (defined $pkgdb->pkgver($pkg, $matchver)) { 879 if (defined $pkgdb->pkgver($pkg, $matchver)) {
880 return ($matchver); 880 return ($matchver);
881 } 881 }
882 882
883 if ($regex) { 883 if ($regex) {
884 foreach my $ver ($pkgdb->pkgs($pkg)->versions) { 884 foreach my $ver ($pkgdb->pkgs($pkg)->versions) {
885 if ($ver =~ /$regex/) { 885 if ($ver =~ /$regex/) {
886 $matchver = undef; 886 $matchver = undef;
887 last; 887 last;
888 } 888 }
889 } 889 }
890 } 890 }
891 891
892 $matchver || last; 892 $matchver || last;
893 } 893 }
894 894
895 # last ditch attempt to handle the whole DEPENDS as a glob 895 # last ditch attempt to handle the whole DEPENDS as a glob
896 # 896 #
897 if ($matchver && ($regex = glob2regex($pkgmatch))) { 897 if ($matchver && ($regex = glob2regex($pkgmatch))) {
898 898
899 # (large-glob) 899 # (large-glob)
900 foreach my $pkgver ($pkgdb->pkgvers_all) { 900 foreach my $pkgver ($pkgdb->pkgvers_all) {
901 if ($pkgver->pkgname =~ /$regex/) { 901 if ($pkgver->pkgname =~ /$regex/) {
902 $matchver = undef; 902 $matchver = undef;
903 last; 903 last;
904 } 904 }
905 } 905 }
906 } 906 }
907 907
908 } else { 908 } else {
909 ($matchpkgname, $matchver) = ($pkgmatch, 'missing'); 909 ($matchpkgname, $matchver) = ($pkgmatch, 'missing');
910 } 910 }
911 911
912 ($matchpkgname, $matchver); 912 ($matchpkgname, $matchver);
913} 913}
914 914
915# Parse a pkgsrc package makefile and return the pkgname and set variables 915# Parse a pkgsrc package makefile and return the pkgname and set variables
916# 916#
917sub parse_makefile_pkgsrc($file) { 917sub parse_makefile_pkgsrc($file) {
918 my ($pkgname, $vars); 918 my ($pkgname, $vars);
919 919
920 $vars = parse_makefile_vars($file, undef); 920 $vars = parse_makefile_vars($file, undef);
921 921
922 if (!$vars) { 922 if (!$vars) {
923 923
924 # Missing Makefile 924 # Missing Makefile
925 return undef; 925 return undef;
926 } 926 }
927 927
928 if (defined $vars->{PKGNAME}) { 928 if (defined $vars->{PKGNAME}) {
929 $pkgname = $vars->{PKGNAME}; 929 $pkgname = $vars->{PKGNAME};
930 } elsif (defined $vars->{DISTNAME}) { 930 } elsif (defined $vars->{DISTNAME}) {
931 $pkgname = $vars->{DISTNAME}; 931 $pkgname = $vars->{DISTNAME};
932 } 932 }
933 933
934 if (defined $vars->{PKGNAME}) { 934 if (defined $vars->{PKGNAME}) {
935 debug("$file: PKGNAME=$vars->{PKGNAME}"); 935 debug("$file: PKGNAME=$vars->{PKGNAME}");
936 } 936 }
937 if (defined $vars->{DISTNAME}) { 937 if (defined $vars->{DISTNAME}) {
938 debug("$file: DISTNAME=$vars->{DISTNAME}"); 938 debug("$file: DISTNAME=$vars->{DISTNAME}");
939 } 939 }
940 940
941 if (!defined $pkgname || $pkgname =~ /\$/ || $pkgname !~ /(.*)-(\d.*)/) { 941 if (!defined $pkgname || $pkgname =~ /\$/ || $pkgname !~ /(.*)-(\d.*)/) {
942 942
943 # invoke make here as a last resort 943 # invoke make here as a last resort
944 my $pkgdir = dirname $file; 944 my $pkgdir = dirname $file;
945 debug("Running '$conf_make' in '$pkgdir'"); 945 debug("Running '$conf_make' in '$pkgdir'");
946 my $pid = open3(\*WTR, \*RDR, \*ERR, 946 my $pid = open3(\*WTR, \*RDR, \*ERR,
947 "cd $pkgdir || exit 1; $conf_make show-vars VARNAMES=PKGNAME"); 947 "cd $pkgdir || exit 1; $conf_make show-vars VARNAMES=PKGNAME");
948 if (!$pid) { 948 if (!$pid) {
949 warn "$file: Unable to run make: $!"; 949 warn "$file: Unable to run make: $!";
950 } else { 950 } else {
951 close(WTR); 951 close(WTR);
952 my @errors = <ERR>; 952 my @errors = <ERR>;
953 close(ERR); 953 close(ERR);
954 my ($makepkgname) = <RDR>; 954 my ($makepkgname) = <RDR>;
955 close(RDR); 955 close(RDR);
956 wait; 956 wait;
957 chomp @errors; 957 chomp @errors;
958 if (@errors) { warn "\n$file: @errors\n"; } 958 if (@errors) { warn "\n$file: @errors\n"; }
959 959
960 if ($makepkgname =~ /(.*)-(\d.*)/) { 960 if ($makepkgname =~ /(.*)-(\d.*)/) {
961 $pkgname = $makepkgname; 961 $pkgname = $makepkgname;
962 } 962 }
963 } 963 }
964 } 964 }
965 965
966 if (defined $pkgname) { 966 if (defined $pkgname) {
967 $pkgname = canonicalize_pkgname($pkgname); 967 $pkgname = canonicalize_pkgname($pkgname);
968 968
969 if (defined $vars->{PKGREVISION} 969 if (defined $vars->{PKGREVISION}
970 and not $vars->{PKGREVISION} =~ /^\s*$/) { 970 and not $vars->{PKGREVISION} =~ /^\s*$/) {
971 if ($vars->{PKGREVISION} =~ /^\$\{(_(CVS|GIT|HG|SVN)_PKGVERSION):.*\}$/) { 971 if ($vars->{PKGREVISION} =~ /^\$\{(_(CVS|GIT|HG|SVN)_PKGVERSION):.*\}$/) {
972 # See wip/mk/*-package.mk. 972 # See wip/mk/*-package.mk.
973 } elsif ($vars->{PKGREVISION} =~ /\D/) { 973 } elsif ($vars->{PKGREVISION} =~ /\D/) {
974 print "\nBogus: PKGREVISION $vars->{PKGREVISION} (from $file)\n"; 974 print "\nBogus: PKGREVISION $vars->{PKGREVISION} (from $file)\n";
975 975
976 } elsif ($vars->{PKGREVISION}) { 976 } elsif ($vars->{PKGREVISION}) {
977 $pkgname .= 'nb'; 977 $pkgname .= 'nb';
978 $pkgname .= $vars->{PKGREVISION}; 978 $pkgname .= $vars->{PKGREVISION};
979 } 979 }
980 } 980 }
981 981
982 if ($pkgname =~ /\$/) { 982 if ($pkgname =~ /\$/) {
983 print "\nBogus: $pkgname (from $file)\n"; 983 print "\nBogus: $pkgname (from $file)\n";
984 984
985 } elsif ($pkgname =~ /(.*)-(\d.*)/) { 985 } elsif ($pkgname =~ /(.*)-(\d.*)/) {
986 if ($pkgdb) { 986 if ($pkgdb) {
987 my ($pkgver) = $pkgdb->add($1, $2); 987 my ($pkgver) = $pkgdb->add($1, $2);
988 988
989 debug("add $1 $2"); 989 debug("add $1 $2");
990 990
991 foreach my $var (qw(DEPENDS RESTRICTED OSVERSION_SPECIFIC BROKEN)) { 991 foreach my $var (qw(DEPENDS RESTRICTED OSVERSION_SPECIFIC BROKEN)) {
992 $pkgver->var($var, $vars->{$var}); 992 $pkgver->var($var, $vars->{$var});
993 } 993 }
994 994
995 if (defined $vars->{NO_BIN_ON_FTP}) { 995 if (defined $vars->{NO_BIN_ON_FTP}) {
996 $pkgver->var('RESTRICTED', 'NO_BIN_ON_FTP'); 996 $pkgver->var('RESTRICTED', 'NO_BIN_ON_FTP');
997 } 997 }
998 998
999 if ($file =~ m:([^/]+/[^/]+)/Makefile$:) { 999 if ($file =~ m:([^/]+/[^/]+)/Makefile$:) {
1000 $pkgver->var('dir', $1); 1000 $pkgver->var('dir', $1);
1001 } else { 1001 } else {
1002 $pkgver->var('dir', 'unknown'); 1002 $pkgver->var('dir', 'unknown');
1003 } 1003 }
1004 } 1004 }
1005 } else { 1005 } else {
1006 print "Cannot extract $pkgname version ($file)\n"; 1006 print "Cannot extract $pkgname version ($file)\n";
1007 } 1007 }
1008 1008
1009 return ($pkgname, $vars); 1009 return ($pkgname, $vars);
1010 1010
1011 } else { 1011 } else {
1012 return (undef); 1012 return (undef);
1013 } 1013 }
1014} 1014}
1015 1015
1016 1016
1017sub chdir_or_fail($dir) { 1017sub chdir_or_fail($dir) {
1018 debug("chdir: $dir"); 1018 debug("chdir: $dir");
1019 chdir($dir) or fail("Cannot chdir($dir): $!\n"); 1019 chdir($dir) or fail("Cannot chdir($dir): $!\n");
1020} 1020}
1021 1021
1022sub load_pkgdb_from_cache($fname) { 1022sub load_pkgdb_from_cache($fname) {
1023 open(STORE, '<', $fname) 1023 open(STORE, '<', $fname)
1024 or die("Cannot read pkgsrc store from $fname: $!\n"); 1024 or die("Cannot read pkgsrc store from $fname: $!\n");
1025 my ($pkgver); 1025 my ($pkgver);
1026 $pkgdb = PkgDb->new(); 1026 $pkgdb = PkgDb->new();
1027 while (defined(my $line = <STORE>)) { 1027 while (defined(my $line = <STORE>)) {
1028 chomp($line); 1028 chomp($line);
1029 if ($line =~ qr"^package\t([^\t]+)\t([^\t]+$)$") { 1029 if ($line =~ qr"^package\t([^\t]+)\t([^\t]+$)$") {
1030 $pkgver = $pkgdb->add($1, $2); 1030 $pkgver = $pkgdb->add($1, $2);
1031 } elsif ($line =~ qr"^var\t([^\t]+)\t(.*)$") { 1031 } elsif ($line =~ qr"^var\t([^\t]+)\t(.*)$") {
1032 $pkgver->var($1, $2); 1032 $pkgver->var($1, $2);
1033 } elsif ($line =~ qr"^sub ") { 1033 } elsif ($line =~ qr"^sub ") {
1034 die "Outdated cache format in '$fname'\n"; 1034 die "Outdated cache format in '$fname'\n";
1035 } else { 1035 } else {
1036 die "Invalid line '$line' in cache '$fname'\n"; 1036 die "Invalid line '$line' in cache '$fname'\n";
1037 } 1037 }
1038 } 1038 }
1039 close(STORE); 1039 close(STORE);
1040} 1040}
1041 1041
1042# Generate pkgname->category/pkg mapping, optionally check DEPENDS 1042# Generate pkgname->category/pkg mapping, optionally check DEPENDS
1043# 1043#
1044sub scan_pkgsrc_makefiles($pkgsrcdir) { 1044sub scan_pkgsrc_makefiles($pkgsrcdir) {
1045 1045
1046 return if defined $pkgdb; # Already done. 1046 return if defined $pkgdb; # Already done.
1047 1047
1048 if ($opt{I}) { 1048 if ($opt{I}) {
1049 load_pkgdb_from_cache($opt{I}); 1049 load_pkgdb_from_cache($opt{I});
1050 return; 1050 return;
1051 } 1051 }
1052 1052
1053 $pkgdb = PkgDb->new(); 1053 $pkgdb = PkgDb->new();
1054 my @categories = list_pkgsrc_categories($pkgsrcdir); 1054 my @categories = list_pkgsrc_categories($pkgsrcdir);
1055 verbose('Scan Makefiles: '); 1055 verbose('Scan Makefiles: ');
1056 1056
1057 if (!$opt{L}) { 1057 if (!$opt{L}) {
1058 verbose('_' x @categories . "\b" x @categories); 1058 verbose('_' x @categories . "\b" x @categories);
1059 } else { 1059 } else {
1060 verbose("\n"); 1060 verbose("\n");
1061 } 1061 }
1062 1062
1063 foreach my $cat (sort @categories) { 1063 foreach my $cat (sort @categories) {
1064 foreach my $pkgdir (list_pkgsrc_pkgdirs($pkgsrcdir, $cat)) { 1064 foreach my $pkgdir (list_pkgsrc_pkgdirs($pkgsrcdir, $cat)) {
1065 my ($pkg, $vars) = parse_makefile_pkgsrc("$pkgsrcdir/$cat/$pkgdir/Makefile"); 1065 my ($pkg, $vars) = parse_makefile_pkgsrc("$pkgsrcdir/$cat/$pkgdir/Makefile");
1066 } 1066 }
1067 1067
1068 if (!$opt{L}) { 1068 if (!$opt{L}) {
1069 verbose('.'); 1069 verbose('.');
1070 } 1070 }
1071 } 1071 }
1072 1072
1073 if (!$opt{L}) { 1073 if (!$opt{L}) {
1074 my ($len); 1074 my ($len);
1075 1075
1076 $_ = scalar $pkgdb->pkgvers_all . ' packages'; 1076 $_ = scalar $pkgdb->pkgvers_all . ' packages';
1077 $len = @categories - length($_); 1077 $len = @categories - length($_);
1078 verbose("\b" x @categories, $_, ' ' x $len, "\b" x $len, "\n"); 1078 verbose("\b" x @categories, $_, ' ' x $len, "\b" x $len, "\n");
1079 } 1079 }
1080} 1080}
1081 1081
1082# Cross reference all depends 1082# Cross reference all depends
1083# 1083#
1084sub pkgsrc_check_depends() { 1084sub pkgsrc_check_depends() {
1085 foreach my $pkgver ($pkgdb->pkgvers_all) { 1085 foreach my $pkgver ($pkgdb->pkgvers_all) {
1086 my ($err, $msg); 1086 my ($err, $msg);
1087 1087
1088 defined $pkgver->var('DEPENDS') || next; 1088 defined $pkgver->var('DEPENDS') || next;
1089 foreach my $depend (split(' ', $pkgver->var('DEPENDS'))) { 1089 foreach my $depend (split(' ', $pkgver->var('DEPENDS'))) {
1090 1090
1091 $depend =~ s/:.*// || next; 1091 $depend =~ s/:.*// || next;
1092 1092
1093 $depend = canonicalize_pkgname($depend); 1093 $depend = canonicalize_pkgname($depend);
1094 if (($msg = invalid_version($depend))) { 1094 if (($msg = invalid_version($depend))) {
1095 if (!defined $err) { 1095 if (!defined $err) {
1096 print $pkgver->pkgname . " DEPENDS errors:\n"; 1096 print $pkgver->pkgname . " DEPENDS errors:\n";
1097 } 1097 }
1098 $err = 1; 1098 $err = 1;
1099 $msg =~ s/(\n)(.)/$1\t$2/g; 1099 $msg =~ s/(\n)(.)/$1\t$2/g;
1100 print "\t$msg"; 1100 print "\t$msg";
1101 } 1101 }
1102 } 1102 }
1103 } 1103 }
1104} 1104}
1105 1105
1106sub load_distinfo($pkgsrcdir, $cat, $pkgdir, $distfiles, $warnings) { 1106sub load_distinfo($pkgsrcdir, $cat, $pkgdir, $distfiles, $warnings) {
1107 my $fname = "$pkgsrcdir/$cat/$pkgdir/distinfo"; 1107 my $fname = "$pkgsrcdir/$cat/$pkgdir/distinfo";
1108 open(DISTINFO, '<', $fname) or return; 1108 open(DISTINFO, '<', $fname) or return;
1109 1109
1110 while (defined(my $line = <DISTINFO>)) { 1110 while (defined(my $line = <DISTINFO>)) {
1111 chomp($line); 1111 chomp($line);
1112 1112
1113 next if $line eq '' || $line =~ /^\$NetBSD/; 1113 next if $line eq '' || $line =~ /^\$NetBSD/;
1114 1114
1115 if ($line !~ m/^ (\w+) \s \( ([^)]+) \) \s=\s (\S+)/x) { 1115 if ($line !~ m/^ (\w+) \s \( ([^)]+) \) \s=\s (\S+)/x) {
1116 warn "Invalid line in $fname:$.: $line\n"; 1116 warn "Invalid line in $fname:$.: $line\n";
1117 next; 1117 next;
1118 } 1118 }
1119 my ($alg, $distfile, $hash) = ($1, $2, $3); 1119 my ($alg, $distfile, $hash) = ($1, $2, $3);
1120 1120
1121 next if $distfile =~ /^patch-[\w.+\-]+$/; 1121 next if $distfile =~ /^patch-[\w.+\-]+$/;
1122 1122
1123 # Only store and check the first algorithm listed in distinfo. 1123 # Only store and check the first algorithm listed in distinfo.
1124 if (!defined $distfiles->{$distfile}) { 1124 if (!defined $distfiles->{$distfile}) {
1125 $distfiles->{$distfile} = { 1125 $distfiles->{$distfile} = {
1126 sumtype => $alg, 1126 sumtype => $alg,
1127 sum => $hash, 1127 sum => $hash,
1128 path => "$cat/$pkgdir", 1128 path => "$cat/$pkgdir",
1129 }; 1129 };
1130 1130
1131 } elsif ($distfiles->{$distfile}->{sumtype} eq $alg 1131 } elsif ($distfiles->{$distfile}->{sumtype} eq $alg
1132 && $distfiles->{$distfile}->{sum} ne $hash) { 1132 && $distfiles->{$distfile}->{sum} ne $hash) {
1133 push @$warnings, 1133 push @$warnings,
1134 "checksum mismatch between '$alg' for '$distfile' " 1134 "checksum mismatch between '$alg' for '$distfile' "
1135 . "in $cat/$pkgdir " 1135 . "in $cat/$pkgdir "
1136 . "and $distfiles->{$distfile}{path}\n"; 1136 . "and $distfiles->{$distfile}{path}\n";
1137 } 1137 }
1138 } 1138 }
1139 close(DISTINFO) or die; 1139 close(DISTINFO) or die;
1140} 1140}
1141 1141
1142# Extract all distinfo entries, then verify contents of distfiles 1142# Extract all distinfo entries, then verify contents of distfiles
1143# 1143#
1144sub scan_pkgsrc_distfiles_vs_distinfo($pkgsrcdir, $pkgdistdir, $check_unref, 1144sub scan_pkgsrc_distfiles_vs_distinfo($pkgsrcdir, $pkgdistdir, $check_unref,
1145 $check_distinfo) { 1145 $check_distinfo) {
1146 my (@categories); 1146 my (@categories);
1147 my (%distfiles, %sumfiles, @distwarn, $numpkg); 1147 my (%distfiles, %sumfiles, @distwarn, $numpkg);
1148 my (%unref_distfiles); 1148 my (%unref_distfiles);
1149 1149
1150 @categories = list_pkgsrc_categories($pkgsrcdir); 1150 @categories = list_pkgsrc_categories($pkgsrcdir);
1151 1151
1152 verbose('Scan distinfo: ' . '_' x @categories . "\b" x @categories); 1152 verbose('Scan distinfo: ' . '_' x @categories . "\b" x @categories);
1153 $numpkg = 0; 1153 $numpkg = 0;
1154 foreach my $cat (sort @categories) { 1154 foreach my $cat (sort @categories) {
1155 foreach my $pkgdir (list_pkgsrc_pkgdirs($pkgsrcdir, $cat)) { 1155 foreach my $pkgdir (list_pkgsrc_pkgdirs($pkgsrcdir, $cat)) {
1156 ++$numpkg; 1156 ++$numpkg;
1157 load_distinfo($pkgsrcdir, $cat, $pkgdir, 1157 load_distinfo($pkgsrcdir, $cat, $pkgdir,
1158 \%distfiles, \@distwarn); 1158 \%distfiles, \@distwarn);
1159 } 1159 }
1160 verbose('.'); 1160 verbose('.');
1161 } 1161 }
1162 verbose(" ($numpkg packages)\n"); 1162 verbose(" ($numpkg packages)\n");
1163 1163
1164 # check each file in $pkgdistdir 1164 # check each file in $pkgdistdir
1165 find({ wanted => sub { 1165 find({ wanted => sub {
1166 my ($dist); 1166 my ($dist);
1167 if (-f $File::Find::name) { 1167 if (-f $File::Find::name) {
1168 my $distn = $File::Find::name; 1168 my $distn = $File::Find::name;
1169 $distn =~ s/$pkgdistdir\/?//g; 1169 $distn =~ s/$pkgdistdir\/?//g;
1170 #pkg/47516 ignore cvs dirs 1170 #pkg/47516 ignore cvs dirs
1171 return if $distn =~ m/^\.cvsignore/; 1171 return if $distn =~ m/^\.cvsignore/;
1172 return if $distn =~ m/^CVS\//; 1172 return if $distn =~ m/^CVS\//;
1173 if (!defined($dist = $distfiles{$distn})) { 1173 if (!defined($dist = $distfiles{$distn})) {
1174 $unref_distfiles{$distn} = 1; 1174 $unref_distfiles{$distn} = 1;
1175 } elsif ($dist->{sum} ne 'IGNORE') { 1175 } elsif ($dist->{sum} ne 'IGNORE') {
1176 push @{$sumfiles{ $dist->{sumtype} }}, $distn; 1176 push @{$sumfiles{ $dist->{sumtype} }}, $distn;
1177 } 1177 }
1178 } 1178 }
1179 } }, 1179 } },
1180 ($pkgdistdir)); 1180 ($pkgdistdir));
1181 1181
1182 if ($check_unref && %unref_distfiles) { 1182 if ($check_unref && %unref_distfiles) {
1183 verbose(scalar(keys %unref_distfiles), 1183 verbose(scalar(keys %unref_distfiles),
1184 " unreferenced file(s) in '$pkgdistdir':\n"); 1184 " unreferenced file(s) in '$pkgdistdir':\n");
1185 print join("\n", sort keys %unref_distfiles), "\n"; 1185 print join("\n", sort keys %unref_distfiles), "\n";
1186 } 1186 }
1187 1187
1188 if ($check_distinfo) { 1188 if ($check_distinfo) {
1189 if (@distwarn) { 1189 if (@distwarn) {
1190 verbose(@distwarn); 1190 verbose(@distwarn);
1191 } 1191 }
1192 1192
1193 verbose("checksum mismatches\n"); 1193 verbose("checksum mismatches\n");
 1194 my $prev_cwd = getcwd() or die;
1194 chdir_or_fail($pkgdistdir); 1195 chdir_or_fail($pkgdistdir);
1195 foreach my $sum (keys %sumfiles) { 1196 foreach my $sum (keys %sumfiles) {
1196 if ($sum eq 'Size') { 1197 if ($sum eq 'Size') {
1197 foreach my $file (@{$sumfiles{$sum}}) { 1198 foreach my $file (@{$sumfiles{$sum}}) {
1198 if (!-f $file || -S $file != $distfiles{$file}{sum}) { 1199 if (!-f $file || -S $file != $distfiles{$file}{sum}) {
1199 print $file, " (Size)\n"; 1200 print $file, " (Size)\n";
1200 $unref_distfiles{$file} = 1; 1201 $unref_distfiles{$file} = 1;
1201 } 1202 }
1202 } 1203 }
1203 next; 1204 next;
1204 } 1205 }
1205 1206
1206 my $pid = open3(my $in, my $out, undef, 'xargs', 'digest', $sum); 1207 my $pid = open3(my $in, my $out, undef, 'xargs', 'digest', $sum);
1207 defined $pid || fail 'fork'; 1208 defined $pid || fail 'fork';
1208 my $pid2 = fork(); 1209 my $pid2 = fork();
1209 defined $pid2 || fail 'fork'; 1210 defined $pid2 || fail 'fork';
1210 if ($pid2) { 1211 if ($pid2) {
1211 close($in); 1212 close($in);
1212 } else { 1213 } else {
1213 print $in "@{$sumfiles{$sum}}"; 1214 print $in "@{$sumfiles{$sum}}";
1214 exit 0; 1215 exit 0;
1215 } 1216 }
1216 while (<$out>) { 1217 while (<$out>) {
1217 if (m/^$sum ?\(([^\)]+)\) = (\S+)/) { 1218 if (m/^$sum ?\(([^\)]+)\) = (\S+)/) {
1218 if ($distfiles{$1}{sum} ne $2) { 1219 if ($distfiles{$1}{sum} ne $2) {
1219 print $1, " ($sum)\n"; 1220 print $1, " ($sum)\n";
1220 $unref_distfiles{$1} = 1; 1221 $unref_distfiles{$1} = 1;
1221 } 1222 }
1222 } 1223 }
1223 } 1224 }
1224 close($out); 1225 close($out);
1225 waitpid($pid, 0) || fail "xargs digest $sum"; 1226 waitpid($pid, 0) || fail "xargs digest $sum";
1226 waitpid($pid2, 0) || fail 'pipe write to xargs'; 1227 waitpid($pid2, 0) || fail 'pipe write to xargs';
1227 } 1228 }
1228 chdir_or_fail('/'); # Do not want to stay in $pkgdistdir 1229 chdir_or_fail($prev_cwd);
1229 } 1230 }
1230 1231
1231 sort keys %unref_distfiles; 1232 sort keys %unref_distfiles;
1232} 1233}
1233 1234
1234sub store_pkgdb_in_cache($db, $fname) { 1235sub store_pkgdb_in_cache($db, $fname) {
1235 open(STORE, '>', $fname) 1236 open(STORE, '>', $fname)
1236 or die("Cannot save package data to $fname: $!\n"); 1237 or die("Cannot save package data to $fname: $!\n");
1237 foreach my $pkgver ($db->pkgvers_all) { 1238 foreach my $pkgver ($db->pkgvers_all) {
1238 my $pkgbase = $pkgver->pkgbase; 1239 my $pkgbase = $pkgver->pkgbase;
1239 my $pkgversion = $pkgver->pkgversion; 1240 my $pkgversion = $pkgver->pkgversion;
1240 1241
1241 $pkgbase =~ /^\S+$/ 1242 $pkgbase =~ /^\S+$/
1242 or die "cannot store package name '$pkgbase'\n"; 1243 or die "cannot store package name '$pkgbase'\n";
1243 $pkgversion =~ /^\S+$/ 1244 $pkgversion =~ /^\S+$/
1244 or die "cannot store package version '$pkgversion'\n"; 1245 or die "cannot store package version '$pkgversion'\n";
1245 print STORE "package\t$pkgbase\t$pkgversion\n"; 1246 print STORE "package\t$pkgbase\t$pkgversion\n";
1246 1247
1247 foreach my $varname (sort $pkgver->vars) { 1248 foreach my $varname (sort $pkgver->vars) {
1248 my $value = $pkgver->var($varname); 1249 my $value = $pkgver->var($varname);
1249 $varname =~ /^\S+$/ 1250 $varname =~ /^\S+$/
1250 or die "cannot store variable name '$varname'\n"; 1251 or die "cannot store variable name '$varname'\n";
1251 $value =~ /^.*$/ 1252 $value =~ /^.*$/
1252 or die "cannot store variable value '$value'\n"; 1253 or die "cannot store variable value '$value'\n";
1253 print STORE "var\t$varname\t$value\n"; 1254 print STORE "var\t$varname\t$value\n";
1254 } 1255 }
1255 } 1256 }
1256 close(STORE) 1257 close(STORE)
1257 or die("Cannot save package data to $fname: $!\n"); 1258 or die("Cannot save package data to $fname: $!\n");
1258} 1259}
1259 1260
1260# Remember to update manual page when modifying option list 1261# Remember to update manual page when modifying option list
1261# 1262#
1262sub usage_and_exit($status) { 1263sub usage_and_exit($status) {
1263 print "Usage: lintpkgsrc [opts] [makefiles] 1264 print "Usage: lintpkgsrc [opts] [makefiles]
1264opts: 1265opts:
1265 -h : This help. [see lintpkgsrc(1) for more information] 1266 -h : This help. [see lintpkgsrc(1) for more information]
1266 1267
1267Installed package options: Distfile options: 1268Installed package options: Distfile options:
1268 -i : Check version against pkgsrc -m : List distinfo mismatches 1269 -i : Check version against pkgsrc -m : List distinfo mismatches
1269 -u : As -i + fetch dist (may change) -o : List obsolete (no distinfo) 1270 -u : As -i + fetch dist (may change) -o : List obsolete (no distinfo)
1270 -y : Remove orphan distfiles 1271 -y : Remove orphan distfiles
1271 -z : Remove installed distfiles 1272 -z : Remove installed distfiles
1272 1273
1273Prebuilt package options: Makefile options: 1274Prebuilt package options: Makefile options:
1274 -p : List old/obsolete -B : List packages marked as 'BROKEN' 1275 -p : List old/obsolete -B : List packages marked as 'BROKEN'
1275 -O : List OSVERSION_SPECIFIC -d : Check 'DEPENDS' up to date 1276 -O : List OSVERSION_SPECIFIC -d : Check 'DEPENDS' up to date
1276 -R : List NO_BIN_ON_FTP/RESTRICTED -S : List packages not in 'SUBDIRS' 1277 -R : List NO_BIN_ON_FTP/RESTRICTED -S : List packages not in 'SUBDIRS'
1277 1278
1278Misc: 1279Misc:
1279 -E file : Export the internal pkgsrc database to file 1280 -E file : Export the internal pkgsrc database to file
1280 -I file : Import the internal pkgsrc database to file (for use with -i) 1281 -I file : Import the internal pkgsrc database to file (for use with -i)
1281 -g file : Generate 'pkgname pkgdir pkgver' map in file 1282 -g file : Generate 'pkgname pkgdir pkgver' map in file
1282 -r : Remove bad files (Without -m -o -p or -V implies all, can use -R) 1283 -r : Remove bad files (Without -m -o -p or -V implies all, can use -R)
1283 1284
1284Modifiers: 1285Modifiers:
1285 -K path : Set PACKAGES basedir (default PKGSRCDIR/packages) 1286 -K path : Set PACKAGES basedir (default PKGSRCDIR/packages)
1286 -M path : Set DISTDIR (default PKGSRCDIR/distfiles) 1287 -M path : Set DISTDIR (default PKGSRCDIR/distfiles)
1287 -P path : Set PKGSRCDIR (default $conf_pkgsrcdir) 1288 -P path : Set PKGSRCDIR (default $conf_pkgsrcdir)
1288 -D : Debug makefile and glob parsing 1289 -D : Debug makefile and glob parsing
1289 -L : List each Makefile when scanned 1290 -L : List each Makefile when scanned
1290"; 1291";
1291 exit $status; 1292 exit $status;
1292} 1293}
1293 1294
1294# Could speed up by building a cache of package names to paths, then processing 1295# Could speed up by building a cache of package names to paths, then processing
1295# each package name once against the tests. 1296# each package name once against the tests.
1296sub check_prebuilt_packages() { 1297sub check_prebuilt_packages() {
1297 1298
1298 if ($_ eq 'distfiles' || $_ eq 'pkgsrc') { 1299 if ($_ eq 'distfiles' || $_ eq 'pkgsrc') {
1299 # Skip these subdirs if present 1300 # Skip these subdirs if present
1300 $File::Find::prune = 1; 1301 $File::Find::prune = 1;
1301 1302
1302 } elsif (/(.+)-(\d.*)\.t[bg]z$/) { 1303 } elsif (/(.+)-(\d.*)\.t[bg]z$/) {
1303 my ($pkg, $ver) = ($1, $2); 1304 my ($pkg, $ver) = ($1, $2);
1304 1305
1305 $pkg = canonicalize_pkgname($pkg); 1306 $pkg = canonicalize_pkgname($pkg);
1306 1307
1307 my ($pkgs); 1308 my ($pkgs);
1308 if ($pkgs = $pkgdb->pkgs($pkg)) { 1309 if ($pkgs = $pkgdb->pkgs($pkg)) {
1309 my ($pkgver) = $pkgs->pkgver($ver); 1310 my ($pkgver) = $pkgs->pkgver($ver);
1310 1311
1311 if (!defined $pkgver) { 1312 if (!defined $pkgver) {
1312 if ($opt{p}) { 1313 if ($opt{p}) {
1313 print "$File::Find::dir/$_\n"; 1314 print "$File::Find::dir/$_\n";
1314 push @matched_prebuiltpackages, "$File::Find::dir/$_"; 1315 push @matched_prebuiltpackages, "$File::Find::dir/$_";
1315 } 1316 }
1316 1317
1317 # Pick probably the last version 1318 # Pick probably the last version
1318 $pkgver = ($pkgs->pkgvers_all)[0]; 1319 $pkgver = ($pkgs->pkgvers_all)[0];
1319 } 1320 }
1320 1321
1321 if ($opt{R} && $pkgver->var('RESTRICTED')) { 1322 if ($opt{R} && $pkgver->var('RESTRICTED')) {
1322 print "$File::Find::dir/$_\n"; 1323 print "$File::Find::dir/$_\n";
1323 push @matched_prebuiltpackages, "$File::Find::dir/$_"; 1324 push @matched_prebuiltpackages, "$File::Find::dir/$_";
1324 } 1325 }
1325 1326
1326 if ($opt{O} && $pkgver->var('OSVERSION_SPECIFIC')) { 1327 if ($opt{O} && $pkgver->var('OSVERSION_SPECIFIC')) {
1327 print "$File::Find::dir/$_\n"; 1328 print "$File::Find::dir/$_\n";
1328 push @matched_prebuiltpackages, "$File::Find::dir/$_"; 1329 push @matched_prebuiltpackages, "$File::Find::dir/$_";
1329 } 1330 }
1330 } 1331 }
1331 1332
1332 } elsif (-d $_) { 1333 } elsif (-d $_) {
1333 if ($prebuilt_pkgdir_cache{"$File::Find::dir/$_"}) { 1334 if ($prebuilt_pkgdir_cache{"$File::Find::dir/$_"}) {
1334 $File::Find::prune = 1; 1335 $File::Find::prune = 1;
1335 return; 1336 return;
1336 } 1337 }
1337 1338
1338 $prebuilt_pkgdir_cache{"$File::Find::dir/$_"} = 1; 1339 $prebuilt_pkgdir_cache{"$File::Find::dir/$_"} = 1;
1339 if (-l $_) { 1340 if (-l $_) {
1340 my ($dest) = readlink($_); 1341 my ($dest) = readlink($_);
1341 1342
1342 if (substr($dest, 0, 1) ne '/') { 1343 if (substr($dest, 0, 1) ne '/') {
1343 $dest = "$File::Find::dir/$dest"; 1344 $dest = "$File::Find::dir/$dest";
1344 } 1345 }
1345 if (!$prebuilt_pkgdir_cache{$dest}) { 1346 if (!$prebuilt_pkgdir_cache{$dest}) {
1346 push @prebuilt_pkgdirs, $dest; 1347 push @prebuilt_pkgdirs, $dest;
1347 } 1348 }
1348 } 1349 }
1349 } 1350 }
1350} 1351}
1351 1352
1352sub debug_parse_makefiles(@args) { 1353sub debug_parse_makefiles(@args) {
1353 foreach my $file (@args) { 1354 foreach my $file (@args) {
1354 -d $file and $file .= '/Makefile'; 1355 -d $file and $file .= '/Makefile';
1355 -f $file or fail("No such file: $file"); 1356 -f $file or fail("No such file: $file");
1356 1357
1357 my ($pkgname, $vars) = parse_makefile_pkgsrc($file); 1358 my ($pkgname, $vars) = parse_makefile_pkgsrc($file);
1358 $pkgname ||= 'uNDEFINEd'; 1359 $pkgname ||= 'uNDEFINEd';
1359 1360
1360 print "$file -> $pkgname\n"; 1361 print "$file -> $pkgname\n";
1361 foreach my $varname (sort keys %$vars) { 1362 foreach my $varname (sort keys %$vars) {
1362 print "\t$varname = $vars->{$varname}\n"; 1363 print "\t$varname = $vars->{$varname}\n";
1363 } 1364 }
1364 1365
1365 #if ($opt{d}) { 1366 #if ($opt{d}) {
1366 # pkgsrc_check_depends(); 1367 # pkgsrc_check_depends();
1367 #} 1368 #}
1368 } 1369 }
1369} 1370}
1370 1371
1371sub check_distfiles($pkgsrcdir, $pkgdistdir) { 1372sub check_distfiles($pkgsrcdir, $pkgdistdir) {
1372 my @unref_distfiles = scan_pkgsrc_distfiles_vs_distinfo( 1373 my @unref_distfiles = scan_pkgsrc_distfiles_vs_distinfo(
1373 $pkgsrcdir, $pkgdistdir, $opt{o}, $opt{m}); 1374 $pkgsrcdir, $pkgdistdir, $opt{o}, $opt{m});
1374 1375
1375 return unless $opt{r}; 1376 return unless $opt{r};
1376 verbose("Unlinking unreferenced distfiles\n"); 1377 verbose("Unlinking unreferenced distfiles\n");
1377 foreach my $distfile (@unref_distfiles) { 1378 foreach my $distfile (@unref_distfiles) {
1378 unlink("$pkgdistdir/$distfile"); 1379 unlink("$pkgdistdir/$distfile");
1379 } 1380 }
1380} 1381}
1381 1382
1382sub remove_distfiles($pkgsrcdir, $pkgdistdir) { 1383sub remove_distfiles($pkgsrcdir, $pkgdistdir) {
1383 my @pkgs = list_installed_packages(); 1384 my @pkgs = list_installed_packages();
1384 scan_pkgsrc_makefiles($pkgsrcdir); 1385 scan_pkgsrc_makefiles($pkgsrcdir);
1385 1386
1386 # list the installed packages and the directory they live in 1387 # list the installed packages and the directory they live in
1387 my @installed; 1388 my @installed;
1388 foreach my $pkgname (sort @pkgs) { 1389 foreach my $pkgname (sort @pkgs) {
1389 if ($pkgname =~ /^([^*?[]+)-([\d*?[].*)/) { 1390 if ($pkgname =~ /^([^*?[]+)-([\d*?[].*)/) {
1390 foreach my $pkgver ($pkgdb->pkgvers_by_pkgbase($1)) { 1391 foreach my $pkgver ($pkgdb->pkgvers_by_pkgbase($1)) {
1391 next if $pkgver->var('dir') =~ /-current/; 1392 next if $pkgver->var('dir') =~ /-current/;
1392 push @installed, $pkgver; 1393 push @installed, $pkgver;
1393 last; 1394 last;
1394 } 1395 }
1395 } 1396 }
1396 } 1397 }
1397 1398
1398 # distfiles belonging to the currently installed packages 1399 # distfiles belonging to the currently installed packages
1399 my (%distfiles, @pkgdistfiles); 1400 my (%distfiles, @pkgdistfiles);
1400 foreach my $pkgver (sort @installed) { 1401 foreach my $pkgver (sort @installed) {
1401 my $pkgpath = $pkgver->var('dir'); 1402 my $pkgpath = $pkgver->var('dir');
1402 next unless open(DISTINFO, "$pkgsrcdir/$pkgpath/distinfo"); 1403 next unless open(DISTINFO, "$pkgsrcdir/$pkgpath/distinfo");
1403 while (<DISTINFO>) { 1404 while (<DISTINFO>) {
1404 next unless m/^(\w+) ?\(([^\)]+)\) = (\S+)/; 1405 next unless m/^(\w+) ?\(([^\)]+)\) = (\S+)/;
1405 my $dn = $2; 1406 my $dn = $2;
1406 next if $dn =~ /^patch-[\w.+\-]+$/; 1407 next if $dn =~ /^patch-[\w.+\-]+$/;
1407 # Strip leading ./ which sometimes gets added 1408 # Strip leading ./ which sometimes gets added
1408 # because of DISTSUBDIR=. 1409 # because of DISTSUBDIR=.
1409 $dn =~ s/^(\.\/)*//; 1410 $dn =~ s/^(\.\/)*//;
1410 if (!defined $distfiles{$dn}) { 1411 if (!defined $distfiles{$dn}) {
1411 $distfiles{$dn}{name} = $dn; 1412 $distfiles{$dn}{name} = $dn;
1412 push @pkgdistfiles, $dn; 1413 push @pkgdistfiles, $dn;
1413 } 1414 }
1414 } 1415 }
1415 close(DISTINFO); 1416 close(DISTINFO);
1416 } 1417 }
1417 1418
1418 # distfiles downloaded on the current system 1419 # distfiles downloaded on the current system
1419 my @tmpdistfiles = listdir("$pkgdistdir", undef); 1420 my @tmpdistfiles = listdir("$pkgdistdir", undef);
1420 my @dldistfiles = grep { $_ ne 'pkg-vulnerabilities' } @tmpdistfiles; 1421 my @dldistfiles = grep { $_ ne 'pkg-vulnerabilities' } @tmpdistfiles;
1421 1422
1422 # sort the two arrays to make searching a bit faster 1423 # sort the two arrays to make searching a bit faster
1423 @dldistfiles = sort { $a cmp $b } @dldistfiles; 1424 @dldistfiles = sort { $a cmp $b } @dldistfiles;
1424 @pkgdistfiles = sort { $a cmp $b } @pkgdistfiles; 1425 @pkgdistfiles = sort { $a cmp $b } @pkgdistfiles;
1425 1426
1426 if ($opt{y}) { 1427 if ($opt{y}) {
1427 # looking for files that are downloaded on the current system 1428 # looking for files that are downloaded on the current system
1428 # but do not belong to any currently installed package i.e. orphaned 1429 # but do not belong to any currently installed package i.e. orphaned
1429 my $found = 0; 1430 my $found = 0;
1430 my @orphan; 1431 my @orphan;
1431 foreach my $dldf (@dldistfiles) { 1432 foreach my $dldf (@dldistfiles) {
1432 foreach my $pkgdf (@pkgdistfiles) { 1433 foreach my $pkgdf (@pkgdistfiles) {
1433 if ($dldf eq $pkgdf) { 1434 if ($dldf eq $pkgdf) {
1434 $found = 1; 1435 $found = 1;
1435 } 1436 }
1436 } 1437 }
1437 if ($found != 1) { 1438 if ($found != 1) {
1438 push @orphan, $dldf; 1439 push @orphan, $dldf;
1439 print "Orphaned file: $dldf\n"; 1440 print "Orphaned file: $dldf\n";
1440 } 1441 }
1441 $found = 0; 1442 $found = 0;
1442 } 1443 }
1443 1444
1444 if ($opt{r}) { 1445 if ($opt{r}) {
1445 chdir_or_fail("$pkgdistdir"); 1446 chdir_or_fail("$pkgdistdir");
1446 verbose("Unlinking 'orphaned' distfiles\n"); 1447 verbose("Unlinking 'orphaned' distfiles\n");
1447 foreach my $distfile (@orphan) { 1448 foreach my $distfile (@orphan) {
1448 unlink($distfile) 1449 unlink($distfile)
1449 } 1450 }
1450 } 1451 }
1451 } 1452 }
1452 1453
1453 if ($opt{z}) { 1454 if ($opt{z}) {
1454 # looking for files that are downloaded on the current system 1455 # looking for files that are downloaded on the current system
1455 # but belong to a currently installed package i.e. parented 1456 # but belong to a currently installed package i.e. parented
1456 my $found = 0; 1457 my $found = 0;
1457 my @parent; 1458 my @parent;
1458 foreach my $pkgdf (@pkgdistfiles) { 1459 foreach my $pkgdf (@pkgdistfiles) {
1459 foreach my $dldf (@dldistfiles) { 1460 foreach my $dldf (@dldistfiles) {
1460 if ($pkgdf eq $dldf) { 1461 if ($pkgdf eq $dldf) {
1461 $found = 1; 1462 $found = 1;
1462 } 1463 }
1463 } 1464 }
1464 if ($found == 1) { 1465 if ($found == 1) {
1465 push @parent, $pkgdf; 1466 push @parent, $pkgdf;
1466 print "Parented file: $pkgdf\n"; 1467 print "Parented file: $pkgdf\n";
1467 } 1468 }
1468 $found = 0; 1469 $found = 0;
1469 } 1470 }
1470 1471
1471 if ($opt{r}) { 1472 if ($opt{r}) {
1472 chdir_or_fail("$pkgdistdir"); 1473 chdir_or_fail("$pkgdistdir");
1473 verbose("Unlinking 'parented' distfiles\n"); 1474 verbose("Unlinking 'parented' distfiles\n");
1474 foreach my $distfile (@parent) { 1475 foreach my $distfile (@parent) {
1475 unlink($distfile); 1476 unlink($distfile);
1476 } 1477 }
1477 } 1478 }
1478 } 1479 }
1479} 1480}
1480 1481
1481sub list_broken_packages($pkgsrcdir) { 1482sub list_broken_packages($pkgsrcdir) {
1482 scan_pkgsrc_makefiles($pkgsrcdir); 1483 scan_pkgsrc_makefiles($pkgsrcdir);
1483 foreach my $pkgver ($pkgdb->pkgvers_all) { 1484 foreach my $pkgver ($pkgdb->pkgvers_all) {
1484 my $broken = $pkgver->var('BROKEN'); 1485 my $broken = $pkgver->var('BROKEN');
1485 next unless $broken; 1486 next unless $broken;
1486 print $pkgver->pkgname . ": $broken\n"; 1487 print $pkgver->pkgname . ": $broken\n";
1487 } 1488 }
1488} 1489}
1489 1490
1490# List obsolete or NO_BIN_ON_FTP/RESTRICTED prebuilt packages 1491# List obsolete or NO_BIN_ON_FTP/RESTRICTED prebuilt packages
1491# 1492#
1492sub list_prebuilt_packages($pkgsrcdir) { 1493sub list_prebuilt_packages($pkgsrcdir) {
1493 scan_pkgsrc_makefiles($pkgsrcdir); 1494 scan_pkgsrc_makefiles($pkgsrcdir);
1494 1495
1495 @prebuilt_pkgdirs = ($default_vars->{PACKAGES}); 1496 @prebuilt_pkgdirs = ($default_vars->{PACKAGES});
1496 %prebuilt_pkgdir_cache = (); 1497 %prebuilt_pkgdir_cache = ();
1497 1498
1498 while (@prebuilt_pkgdirs) { 1499 while (@prebuilt_pkgdirs) {
1499 find(\&check_prebuilt_packages, shift @prebuilt_pkgdirs); 1500 find(\&check_prebuilt_packages, shift @prebuilt_pkgdirs);
1500 } 1501 }
1501 1502
1502 if ($opt{r}) { 1503 if ($opt{r}) {
1503 verbose("Unlinking listed prebuilt packages\n"); 1504 verbose("Unlinking listed prebuilt packages\n");
1504 foreach my $pkgfile (@matched_prebuiltpackages) { 1505 foreach my $pkgfile (@matched_prebuiltpackages) {
1505 unlink($pkgfile); 1506 unlink($pkgfile);
1506 } 1507 }
1507 } 1508 }
1508} 1509}
1509 1510
1510sub list_packages_not_in_SUBDIR($pkgsrcdir) { 1511sub list_packages_not_in_SUBDIR($pkgsrcdir) {
1511 my (%in_subdir); 1512 my (%in_subdir);
1512 foreach my $cat (list_pkgsrc_categories($pkgsrcdir)) { 1513 foreach my $cat (list_pkgsrc_categories($pkgsrcdir)) {
1513 my $vars = parse_makefile_vars("$pkgsrcdir/$cat/Makefile", undef); 1514 my $vars = parse_makefile_vars("$pkgsrcdir/$cat/Makefile", undef);
1514 1515
1515 if (!$vars->{SUBDIR}) { 1516 if (!$vars->{SUBDIR}) {
1516 print "Warning - no SUBDIR for $cat\n"; 1517 print "Warning - no SUBDIR for $cat\n";
1517 next; 1518 next;
1518 } 1519 }
1519 foreach my $pkgdir (split(/\s+/, $vars->{SUBDIR})) { 1520 foreach my $pkgdir (split(/\s+/, $vars->{SUBDIR})) {
1520 $in_subdir{"$cat/$pkgdir"} = 1; 1521 $in_subdir{"$cat/$pkgdir"} = 1;
1521 } 1522 }
1522 } 1523 }
1523 1524
1524 scan_pkgsrc_makefiles($pkgsrcdir); 1525 scan_pkgsrc_makefiles($pkgsrcdir);
1525 foreach my $pkgver ($pkgdb->pkgvers_all) { 1526 foreach my $pkgver ($pkgdb->pkgvers_all) {
1526 my $pkgpath = $pkgver->var('dir'); 1527 my $pkgpath = $pkgver->var('dir');
1527 if (!defined $in_subdir{$pkgpath}) { 1528 if (!defined $in_subdir{$pkgpath}) {
1528 print "$pkgpath: Not in SUBDIR\n"; 1529 print "$pkgpath: Not in SUBDIR\n";
1529 } 1530 }
1530 } 1531 }
1531} 1532}
1532 1533
1533sub generate_map_file($pkgsrcdir, $fname) { 1534sub generate_map_file($pkgsrcdir, $fname) {
1534 my $tmpfile = "$fname.tmp.$$"; 1535 my $tmpfile = "$fname.tmp.$$";
1535 1536
1536 scan_pkgsrc_makefiles($pkgsrcdir); 1537 scan_pkgsrc_makefiles($pkgsrcdir);
1537 open(TABLE, '>', $tmpfile) or fail("Cannot write '$tmpfile': $!"); 1538 open(TABLE, '>', $tmpfile) or fail("Cannot write '$tmpfile': $!");
1538 foreach my $pkgver ($pkgdb->pkgvers_all) { 1539 foreach my $pkgver ($pkgdb->pkgvers_all) {
1539 print TABLE $pkgver->pkgbase . "\t" 1540 print TABLE $pkgver->pkgbase . "\t"
1540 . $pkgver->var('dir') . "\t" 1541 . $pkgver->var('dir') . "\t"
1541 . $pkgver->pkgversion . "\n"; 1542 . $pkgver->pkgversion . "\n";
1542 } 1543 }
1543 close(TABLE) or fail("close('$tmpfile'): $!"); 1544 close(TABLE) or fail("close('$tmpfile'): $!");
1544 rename($tmpfile, $fname) 1545 rename($tmpfile, $fname)
1545 or fail("rename('$tmpfile', '$fname'): $!"); 1546 or fail("rename('$tmpfile', '$fname'): $!");
1546} 1547}
1547 1548
1548sub check_outdated_installed_packages($pkgsrcdir) { 1549sub check_outdated_installed_packages($pkgsrcdir) {
1549 my @pkgs = list_installed_packages(); 1550 my @pkgs = list_installed_packages();
1550 scan_pkgsrc_makefiles($pkgsrcdir); 1551 scan_pkgsrc_makefiles($pkgsrcdir);
1551 1552
1552 my @update; 1553 my @update;
1553 foreach my $pkgname (sort @pkgs) { 1554 foreach my $pkgname (sort @pkgs) {
1554 next unless $_ = invalid_version($pkgname); 1555 next unless $_ = invalid_version($pkgname);
1555 1556
1556 print $_; 1557 print $_;
1557 next unless $pkgname =~ /^([^*?[]+)-([\d*?[].*)/; 1558 next unless $pkgname =~ /^([^*?[]+)-([\d*?[].*)/;
1558 1559
1559 foreach my $pkgver ($pkgdb->pkgvers_by_pkgbase($1)) { 1560 foreach my $pkgver ($pkgdb->pkgvers_by_pkgbase($1)) {
1560 next if $pkgver->var('dir') =~ /-current/; 1561 next if $pkgver->var('dir') =~ /-current/;
1561 push @update, $pkgver; 1562 push @update, $pkgver;
1562 last; 1563 last;
1563 } 1564 }
1564 } 1565 }
1565 1566
1566 return unless $opt{u}; 1567 return unless $opt{u};
1567 1568
1568 print "\nREQUIRED details for packages that could be updated:\n"; 1569 print "\nREQUIRED details for packages that could be updated:\n";
1569 1570
1570 foreach my $pkgver (@update) { 1571 foreach my $pkgver (@update) {
1571 my $pkgbase = $pkgver->pkgbase; 1572 my $pkgbase = $pkgver->pkgbase;
1572 print "$pkgbase:"; 1573 print "$pkgbase:";
1573 if (open(PKGINFO, "$conf_pkg_info -R $pkgbase |")) { 1574 if (open(PKGINFO, "$conf_pkg_info -R $pkgbase |")) {
1574 my ($list); 1575 my ($list);
1575 1576
1576 while (<PKGINFO>) { 1577 while (<PKGINFO>) {
1577 if (/Required by:/) { 1578 if (/Required by:/) {
1578 $list = 1; 1579 $list = 1;
1579 } elsif ($list) { 1580 } elsif ($list) {
1580 chomp; 1581 chomp;
1581 s/-\d.*//; 1582 s/-\d.*//;
1582 print " $_"; 1583 print " $_";
1583 } 1584 }
1584 } 1585 }
1585 close(PKGINFO); 1586 close(PKGINFO);
1586 } 1587 }
1587 print "\n"; 1588 print "\n";
1588 } 1589 }
1589 1590
1590 print "\nRunning '$conf_make fetch-list | sh' for each package:\n"; 1591 print "\nRunning '$conf_make fetch-list | sh' for each package:\n";
1591 foreach my $pkgver (@update) { 1592 foreach my $pkgver (@update) {
1592 my $pkgpath = $pkgver->var('dir'); 1593 my $pkgpath = $pkgver->var('dir');
1593 defined $pkgpath 1594 defined $pkgpath
1594 or fail('Cannot determine ' . $pkgver->pkgbase . ' directory'); 1595 or fail('Cannot determine ' . $pkgver->pkgbase . ' directory');
1595 1596
1596 print "$pkgsrcdir/$pkgpath\n"; 1597 print "$pkgsrcdir/$pkgpath\n";
1597 chdir_or_fail("$pkgsrcdir/$pkgpath"); 1598 chdir_or_fail("$pkgsrcdir/$pkgpath");
1598 system("$conf_make fetch-list | sh"); 1599 system("$conf_make fetch-list | sh");
1599 } 1600 }
1600} 1601}
1601 1602
1602sub main() { 1603sub main() {
1603 1604
1604 $ENV{PATH} .= 1605 $ENV{PATH} .=
1605 ":/bin:/usr/bin:/sbin:/usr/sbin:$conf_prefix/sbin:$conf_prefix/bin"; 1606 ":/bin:/usr/bin:/sbin:/usr/sbin:$conf_prefix/sbin:$conf_prefix/bin";
1606 1607
1607 if ( 1608 if (
1608 !getopts('-BDE:I:K:LM:OP:RSVdg:himopruyz', \%opt) 1609 !getopts('-BDE:I:K:LM:OP:RSVdg:himopruyz', \%opt)
1609 || $opt{h} 1610 || $opt{h}
1610 || !grep(/[BDEORSdgimopruyz]/, keys %opt)) { 1611 || !grep(/[BDEORSdgimopruyz]/, keys %opt)) {
1611 usage_and_exit($opt{h} ? 0 : 1); 1612 usage_and_exit($opt{h} ? 0 : 1);
1612 } 1613 }
1613 $| = 1; 1614 $| = 1;
1614 1615
1615 get_default_makefile_vars(); # $default_vars 1616 get_default_makefile_vars(); # $default_vars
1616 1617
1617 if ($opt{D} && @ARGV) { 1618 if ($opt{D} && @ARGV) {
1618 debug_parse_makefiles(@ARGV); 1619 debug_parse_makefiles(@ARGV);
1619 exit; 1620 exit;
1620 } 1621 }
1621 1622
1622 my $pkgsrcdir = $default_vars->{PKGSRCDIR}; 1623 my $pkgsrcdir = $default_vars->{PKGSRCDIR};
1623 my $pkgdistdir = $default_vars->{DISTDIR}; 1624 my $pkgdistdir = $default_vars->{DISTDIR};
1624 1625
1625 if ($opt{r} && !$opt{o} && !$opt{m} && !$opt{p}) { 1626 if ($opt{r} && !$opt{o} && !$opt{m} && !$opt{p}) {
1626 $opt{o} = $opt{m} = $opt{p} = 1; 1627 $opt{o} = $opt{m} = $opt{p} = 1;
1627 } 1628 }
1628 if ($opt{o} || $opt{m}) { 1629 if ($opt{o} || $opt{m}) {
1629 check_distfiles($pkgsrcdir, $pkgdistdir); 1630 check_distfiles($pkgsrcdir, $pkgdistdir);
1630 } 1631 }
1631 1632
1632 # Remove all distfiles that are / are not part of an installed package 1633 # Remove all distfiles that are / are not part of an installed package
1633 if ($opt{y} || $opt{z}) { 1634 if ($opt{y} || $opt{z}) {
1634 remove_distfiles($pkgsrcdir, $pkgdistdir); 1635 remove_distfiles($pkgsrcdir, $pkgdistdir);
1635 } 1636 }
1636 1637
1637 if ($opt{B}) { 1638 if ($opt{B}) {
1638 list_broken_packages($pkgsrcdir); 1639 list_broken_packages($pkgsrcdir);
1639 } 1640 }
1640 1641
1641 if ($opt{p} || $opt{O} || $opt{R}) { 1642 if ($opt{p} || $opt{O} || $opt{R}) {
1642 list_prebuilt_packages($pkgsrcdir); 1643 list_prebuilt_packages($pkgsrcdir);
1643 } 1644 }
1644 1645
1645 if ($opt{S}) { 1646 if ($opt{S}) {
1646 list_packages_not_in_SUBDIR($pkgsrcdir); 1647 list_packages_not_in_SUBDIR($pkgsrcdir);
1647 } 1648 }
1648 1649
1649 if ($opt{g}) { 1650 if ($opt{g}) {
1650 generate_map_file($pkgsrcdir, $opt{g}); 1651 generate_map_file($pkgsrcdir, $opt{g});
1651 } 1652 }
1652 1653
1653 if ($opt{d}) { 1654 if ($opt{d}) {
1654 scan_pkgsrc_makefiles($pkgsrcdir); 1655 scan_pkgsrc_makefiles($pkgsrcdir);
1655 pkgsrc_check_depends(); 1656 pkgsrc_check_depends();
1656 } 1657 }
1657 1658
1658 if ($opt{i} || $opt{u}) { 1659 if ($opt{i} || $opt{u}) {
1659 check_outdated_installed_packages($pkgsrcdir); 1660 check_outdated_installed_packages($pkgsrcdir);
1660 } 1661 }
1661 1662
1662 if ($opt{E}) { 1663 if ($opt{E}) {
1663 scan_pkgsrc_makefiles($pkgsrcdir); 1664 scan_pkgsrc_makefiles($pkgsrcdir);
1664 store_pkgdb_in_cache($pkgdb, $opt{E}); 1665 store_pkgdb_in_cache($pkgdb, $opt{E});
1665 } 1666 }
1666} 1667}
1667 1668
1668sub export_for_test() { 1669sub export_for_test() {
1669 ({ 1670 ({
1670 'opt' => \%opt, 1671 'opt' => \%opt,
1671 'default_vars' => $default_vars, 1672 'default_vars' => $default_vars,
1672 'pkgdb' => $pkgdb, 1673 'pkgdb' => $pkgdb,
1673 }); 1674 });
1674} 1675}
1675 1676
1676if (caller()) { 1677if (caller()) {
1677 # To allow easy testing of the code. 1678 # To allow easy testing of the code.
1678 # TODO: reduce the use of global variables, or make them accessible 1679 # TODO: reduce the use of global variables, or make them accessible
1679 # to the tests. 1680 # to the tests.
1680 $default_vars = {}; 1681 $default_vars = {};
1681} 1682}
1682 1683
1683main() unless caller(); 1684main() unless caller();