1# ex:ts=8 sw=4: 2# $OpenBSD: PkgSpec.pm,v 1.51 2023/06/13 09:07:17 espie Exp $ 3# 4# Copyright (c) 2003-2007 Marc Espie <espie@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18use v5.36; 19 20package OpenBSD::PkgSpec::flavorspec; 21sub new($class, $spec) 22{ 23 bless \$spec, $class; 24} 25 26sub check_1flavor($f, $spec) 27{ 28 for my $flavor (split /\-/o, $spec) { 29 # must not be here 30 if ($flavor =~ m/^\!(.*)$/o) { 31 return 0 if $f->{$1}; 32 # must be here 33 } else { 34 return 0 unless $f->{$flavor}; 35 } 36 } 37 return 1; 38} 39 40sub match($self, $h) 41{ 42 # check each flavor constraint 43 for my $c (split /\,/o, $$self) { 44 if (check_1flavor($h->{flavors}, $c)) { 45 return 1; 46 } 47 } 48 return 0; 49} 50 51package OpenBSD::PkgSpec::exactflavor; 52our @ISA = qw(OpenBSD::PkgSpec::flavorspec); 53sub new($class, $value) 54{ 55 bless {map{($_, 1)} split(/\-/, $value)}, $class; 56} 57 58sub flavor_string($self) 59{ 60 return join('-', sort keys %$self); 61} 62 63sub match($self, $h) 64{ 65 if ($self->flavor_string eq $h->flavor_string) { 66 return 1; 67 } else { 68 return 0; 69 } 70} 71 72package OpenBSD::PkgSpec::versionspec; 73our @ISA = qw(OpenBSD::PackageName::version); 74my $ops = { 75 '<' => 'lt', 76 '<=' => 'le', 77 '>' => 'gt', 78 '>=' => 'ge', 79 '=' => 'eq' 80}; 81 82sub new($class, $s) 83{ 84 my ($op, $version) = ('=', $s); 85 if ($s =~ m/^(\>\=|\>|\<\=|\<|\=)(.*)$/) { 86 ($op, $version) = ($1, $2); 87 } 88 return "OpenBSD::PkgSpec::version::$ops->{$op}"->from_string($version); 89} 90 91sub pnum_compare($self, $b) 92{ 93 if (!defined $self->{p}) { 94 return 0; 95 } else { 96 return $self->SUPER::pnum_compare($b); 97 } 98} 99 100sub is_exact($) 101{ 102 return 0; 103} 104 105package OpenBSD::PkgSpec::version::lt; 106our @ISA = qw(OpenBSD::PkgSpec::versionspec); 107sub match($self, $b) 108{ 109 -$self->compare($b->{version}) < 0 ? 1 : 0; 110} 111 112package OpenBSD::PkgSpec::version::le; 113our @ISA = qw(OpenBSD::PkgSpec::versionspec); 114sub match($self, $b) 115{ 116 -$self->compare($b->{version}) <= 0 ? 1 : 0; 117} 118 119package OpenBSD::PkgSpec::version::gt; 120our @ISA = qw(OpenBSD::PkgSpec::versionspec); 121sub match($self, $b) 122{ 123 -$self->compare($b->{version}) > 0 ? 1 : 0; 124} 125 126package OpenBSD::PkgSpec::version::ge; 127our @ISA = qw(OpenBSD::PkgSpec::versionspec); 128sub match($self, $b) 129{ 130 -$self->compare($b->{version}) >= 0 ? 1 : 0; 131} 132 133package OpenBSD::PkgSpec::version::eq; 134our @ISA = qw(OpenBSD::PkgSpec::versionspec); 135sub match($self, $b) 136{ 137 -$self->compare($b->{version}) == 0 ? 1 : 0; 138} 139 140sub is_exact($) 141{ 142 return 1; 143} 144 145package OpenBSD::PkgSpec::badspec; 146sub new($class) 147{ 148 bless {}, $class; 149} 150 151# $self->match*($list) 152sub match_ref($, $) 153{ 154 return (); 155} 156 157sub match_libs_ref($, $) 158{ 159 return (); 160} 161 162sub match_locations($, $) 163{ 164 return []; 165} 166 167sub is_valid($) 168{ 169 return 0; 170} 171 172package OpenBSD::PkgSpec::SubPattern; 173use OpenBSD::PackageName; 174 175sub parse($class, $p) 176{ 177 my $r = {}; 178 179 # let's try really hard to find the stem and the flavors 180 unless ($p =~ m/^ 181 ([^%]+?) # stem part 182 \- 183 ( 184 (?:\>|\>\=|\<\=|\<|\=)?\d[^-%]* # optional op + version 185 |\* # or any version 186 ) 187 (?:\-([^%]*))? # optional flavor part 188 $/x) { 189 return undef; 190 } 191 ($r->{stemspec}, $r->{vspec}, $r->{flavorspec}) = ($1, $2, $3); 192 193 $r->{flavorspec} //= ''; 194 $r->{stemspec} =~ s/\./\\\./go; 195 $r->{stemspec} =~ s/\+/\\\+/go; 196 $r->{stemspec} =~ s/\*/\.\*/go; 197 $r->{stemspec} =~ s/\?/\./go; 198 $r->{stemspec} =~ s/^(\\\.libs)\-/$1\\d*\-/go; 199 return $r; 200} 201 202sub add_version_constraints($class, $constraints, $vspec) 203{ 204 # turn the vspec into a list of constraints. 205 if ($vspec eq '*') { 206 # non constraint 207 } else { 208 for my $c (split /\,/, $vspec) { 209 push(@$constraints, 210 OpenBSD::PkgSpec::versionspec->new($c)); 211 } 212 } 213} 214 215sub add_flavor_constraints($class, $constraints, $flavorspec) 216{ 217 # and likewise for flavors 218 if ($flavorspec eq '') { 219 # non constraint 220 } else { 221 push(@$constraints, 222 OpenBSD::PkgSpec::flavorspec->new($flavorspec)); 223 } 224} 225 226sub new($class, $p, $with_partial) 227{ 228 my $r = $class->parse($p); 229 if (defined $r) { 230 my $stemspec = $r->{stemspec}; 231 my $constraints = []; 232 $class->add_version_constraints($constraints, $r->{vspec}); 233 $class->add_flavor_constraints($constraints, $r->{flavorspec}); 234 235 my $o = bless { 236 libstem => qr{^\.libs\d*\-$stemspec\-\d.*$}, 237 }, $class; 238 239 if ($with_partial) { 240 $o->{fuzzystem} = qr{^(?:partial\-)*$stemspec\-\d.*$}; 241 } else { 242 $o->{fuzzystem} = qr{^$stemspec\-\d.*$}; 243 } 244 if (@$constraints != 0) { 245 $o->{constraints} = $constraints; 246 } 247 if (defined $r->{e}) { 248 $o->{e} = 1; 249 } 250 return $o; 251 } else { 252 return OpenBSD::PkgSpec::badspec->new; 253 } 254} 255 256sub match_ref($o, $list) 257{ 258 my @result = (); 259 # Now, have to extract the version number, and the flavor... 260LOOP1: 261 for my $s (grep(/$o->{fuzzystem}/, @$list)) { 262 my $name = OpenBSD::PackageName->from_string($s); 263 if (defined $o->{constraints}) { 264 for my $c (@{$o->{constraints}}) { 265 next LOOP1 unless $c->match($name); 266 } 267 } 268 if (wantarray) { 269 push(@result, $s); 270 } else { 271 return 1; 272 } 273 } 274 275 if (wantarray) { 276 return @result; 277 } else { 278 return 0; 279 } 280} 281 282sub match_libs_ref($o, $list) 283{ 284 return grep(/$o->{libstem}/, @$list); 285} 286 287 288sub match_locations($o, $list) 289{ 290 my $result = []; 291 # Now, have to extract the version number, and the flavor... 292LOOP2: 293 for my $s (grep { $_->name =~ m/$o->{fuzzystem}/} @$list) { 294 my $name = $s->pkgname; 295 if (defined $o->{constraints}) { 296 for my $c (@{$o->{constraints}}) { 297 next LOOP2 unless $c->match($name); 298 } 299 } 300 push(@$result, $s); 301 } 302 303 return $result; 304} 305 306sub is_valid($) 307{ 308 return 1; 309} 310 311package OpenBSD::PkgSpec; 312sub subpattern_class($) 313{ "OpenBSD::PkgSpec::SubPattern" } 314sub new($class, $pattern, $with_partial = 0) 315{ 316 my @l = map { $class->subpattern_class->new($_, $with_partial) } 317 (split /\|/o, $pattern); 318 if (@l == 1) { 319 return $l[0]; 320 } else { 321 return bless \@l, $class; 322 } 323} 324 325sub match_ref($self, $r) 326{ 327 if (wantarray) { 328 my @l = (); 329 for my $subpattern (@$self) { 330 push(@l, $subpattern->match_ref($r)); 331 } 332 return @l; 333 } else { 334 for my $subpattern (@$self) { 335 if ($subpattern->match_ref($r)) { 336 return 1; 337 } 338 } 339 return 0; 340 } 341} 342 343sub match_libs_ref($self, $r) 344{ 345 if (wantarray) { 346 my @l = (); 347 for my $subpattern (@$self) { 348 push(@l, $subpattern->match_libs_ref($r)); 349 } 350 return @l; 351 } else { 352 for my $subpattern (@$self) { 353 if ($subpattern->match_libs_ref($r)) { 354 return 1; 355 } 356 } 357 return 0; 358 } 359} 360 361sub match_locations($self, $r) 362{ 363 my $l = []; 364 for my $subpattern (@$self) { 365 push(@$l, @{$subpattern->match_locations($r)}); 366 } 367 return $l; 368} 369 370sub is_valid($self) 371{ 372 for my $subpattern (@$self) { 373 return 0 unless $subpattern->is_valid; 374 } 375 return 1; 376} 377 378package OpenBSD::PkgSpec::SubPattern::Exact; 379our @ISA = qw(OpenBSD::PkgSpec::SubPattern); 380 381sub add_version_constraints($class, $constraints, $vspec) 382{ 383 return if $vspec eq '*'; # XXX 384 my $v = OpenBSD::PkgSpec::versionspec->new($vspec); 385 die "not a good exact spec" if !$v->is_exact; 386 delete $v->{p}; 387 push(@$constraints, $v); 388} 389 390sub add_flavor_constraints($class, $constraints, $flavorspec) 391{ 392 push(@$constraints, OpenBSD::PkgSpec::exactflavor->new($flavorspec)); 393} 394 395package OpenBSD::PkgSpec::Exact; 396our @ISA = qw(OpenBSD::PkgSpec); 397 398sub subpattern_class($) 399{ "OpenBSD::PkgSpec::SubPattern::Exact" } 400 4011; 402