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