1# ex:ts=8 sw=4: 2# $OpenBSD: PkgSpec.pm,v 1.39 2011/08/31 22:50:21 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 $_ (split /\-/o, $spec) { 34 # must not be here 35 if (m/^\!(.*)$/o) { 36 return 0 if $f->{$1}; 37 # must be here 38 } else { 39 return 0 unless $f->{$_}; 40 } 41 } 42 return 1; 43} 44 45sub match 46{ 47 my ($self, $h) = @_; 48 49 # check each flavor constraint 50 for my $_ (split /\,/o, $$self) { 51 if (check_1flavor($h->{flavors}, $_)) { 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 192my $exception = { 193 "db-3.*" => "db->=3,<4", 194 "db-4.*" => "db->=4,<5", 195 "db-java-4.*" => "db-java->=4,<5", 196 "emacs-21.*" => "emacs->=21,<22", 197 "emacs-21.4*" => "emacs->=21.4,<21.5", 198 "emacs-22.2*" => "emacs->=22.2,<22.3", 199 "enlightenment-0.16*" => "enlightenment->=0.16,<0.17", 200 "gimp-2.*" => "gimp->=2,<3", 201 "gnupg->=1.4.*" => "gnupg->=1.4", 202 "gstreamer-0.10.*" => "gstreamer->=0.10,<0.11", 203 "gtksourceview-2.*" => "gtksourceview->=2,<3", 204 "hydra-5.4*" => "hydra->=5.4,<5.5", 205 "jdk->=1.5.0.*" => "jdk->=1.5.0", 206 "jdk->=1.6.0.*" => "jdk->=1.6.0", 207 "jre->=1.5.0.*" => "jre->=1.5.0", 208 "libggi->=0.9*" => "libggi->=0.9", 209 "libnet-1.0*" => "libnet->=1.0,<1.1", 210 "libnet-1.0.*" => "libnet->=1.0,<1.1", 211 "libnet-1.1*" => "libnet->=1.1,<1.2", 212 "libsigc++-1.*" => "libsigc++->=1,<2", 213 "libsigc++-2.*" => "libsigc++->=2,<3", 214 "mysql-client-5.0.*" => "mysql-client->=5.0,<5.1", 215 "ocaml-3.09.3*" => "ocaml->=3.09.3,<3.09.4", 216 "openldap-client-2.*" => "openldap-client->=2,<3", 217 "pgp-5.*" => "pgp->=5,<6", 218 "postgresql-client-8.3.*" => "postgresql-client->=8.3,<8.4", 219 "python-2.4*" => "python->=2.4,<2.5", 220 "python-2.4.*" => "python->=2.4,<2.5", 221 "python-2.5*" => "python->=2.5,<2.6", 222 "python-2.5.*" => "python->=2.5,<2.6", 223 "python-2.6.*" => "python->=2.6,<2.7", 224 "python-bsddb-2.5*" => "python-bsddb->=2.5,<2.6", 225 "python-tkinter-2.4*" => "python-tkinter->=2.4,<2.5", 226 "python-tkinter-2.5*" => "python-tkinter->=2.5,<2.6", 227 "rrdtool-1.2.*" => "rrdtool->=1.2,<1.3", 228 "swt-3.2.2*" => "swt->=3.2.2,<3.2.3", 229 "swt-browser-3.2.2*" => "swt-browser->=3.2.2,<3.2.3", 230 "tcl-8.4.*" => "tcl->=8.4,<8.5", 231 "tcl-8.5.*" => "tcl->=8.5,<8.6", 232 "tk-8.4*" => "tk->=8.4,<8.5", 233 "tk-8.4.*" => "tk->=8.4,<8.5", 234 "tk-8.5*" => "tk->=8.5,<8.6", 235 "tomcat-4.*" => "tomcat->=4,<5", 236 "tomcat-5.*" => "tomcat->=5,<6", 237 "tomcat-6.*" => "tomcat->=6,<7", 238 "tomcat-admin-4.*" => "tomcat-admin->=4,<5", 239 "tomcat-admin-5.*" => "tomcat-admin->=5,<6", 240 "xmms-1.2.11*" => "xmms->=1.2.11,<1.2.12" 241}; 242 243sub parse 244{ 245 my ($class, $p) = @_; 246 247 my $r = {}; 248 249 if (defined $exception->{$p}) { 250 $p = $exception->{$p}; 251 $r->{e} = 1; 252 } 253 254 # let's try really hard to find the stem and the flavors 255 unless ($p =~ m/^(.*?)\-((?:(?:\>|\>\=|\<\=|\<|\=)?\d|\*)[^-]*)(.*)$/) { 256 return undef; 257 } 258 ($r->{stemspec}, $r->{vspec}, $r->{flavorspec}) = ($1, $2, $3); 259 $r->{flavorspec} =~ s/^\-//; 260 261 $r->{stemspec} =~ s/\./\\\./go; 262 $r->{stemspec} =~ s/\+/\\\+/go; 263 $r->{stemspec} =~ s/\*/\.\*/go; 264 $r->{stemspec} =~ s/\?/\./go; 265 $r->{stemspec} =~ s/^(\\\.libs)\-/$1\\d*\-/go; 266 return $r; 267} 268 269sub add_version_constraints 270{ 271 my ($class, $constraints, $vspec) = @_; 272 273 # turn the vspec into a list of constraints. 274 if ($vspec eq '*') { 275 # non constraint 276 } else { 277 for my $c (split /\,/, $vspec) { 278 push(@$constraints, 279 OpenBSD::PkgSpec::versionspec->new($c)); 280 } 281 } 282} 283 284sub add_flavor_constraints 285{ 286 my ($class, $constraints, $flavorspec) = @_; 287 # and likewise for flavors 288 if ($flavorspec eq '') { 289 # non constraint 290 } else { 291 push(@$constraints, 292 OpenBSD::PkgSpec::flavorspec->new($flavorspec)); 293 } 294} 295 296sub new 297{ 298 my ($class, $p) = @_; 299 300 my $r = $class->parse($p); 301 if (defined $r) { 302 my $stemspec = $r->{stemspec}; 303 my $constraints = []; 304 $class->add_version_constraints($constraints, $r->{vspec}); 305 $class->add_flavor_constraints($constraints, $r->{flavorspec}); 306 307 my $o = bless { 308 exactstem => qr{^$stemspec$}, 309 fuzzystem => qr{^$stemspec\-\d.*$}, 310 libstem => qr{^\.libs\d*\-$stemspec\-\d.*$}, 311 constraints => $constraints, 312 }, $class; 313 if (defined $r->{e}) { 314 $o->{e} = 1; 315 } 316 return $o; 317 } else { 318 return OpenBSD::PkgSpec::badspec->new; 319 } 320} 321 322sub match_ref 323{ 324 my ($o, $list) = @_; 325 my @result = (); 326 # Now, have to extract the version number, and the flavor... 327LOOP1: 328 for my $s (grep(/$o->{fuzzystem}/, @$list)) { 329 my $name = OpenBSD::PackageName->from_string($s); 330 next unless $name->{stem} =~ m/^$o->{exactstem}$/; 331 for my $c (@{$o->{constraints}}) { 332 next LOOP1 unless $c->match($name); 333 } 334 if (wantarray) { 335 push(@result, $s); 336 } else { 337 return 1; 338 } 339 } 340 341 return @result; 342} 343 344sub match_libs_ref 345{ 346 my ($o, $list) = @_; 347 return grep(/$o->{libstem}/, @$list); 348} 349 350 351sub match_locations 352{ 353 my ($o, $list) = @_; 354 my $result = []; 355 # Now, have to extract the version number, and the flavor... 356LOOP2: 357 for my $s (grep { $_->name =~ m/$o->{fuzzystem}/} @$list) { 358 my $name = $s->pkgname; 359 next unless $name->{stem} =~ m/^$o->{exactstem}$/; 360 for my $c (@{$o->{constraints}}) { 361 next LOOP2 unless $c->match($name); 362 } 363 push(@$result, $s); 364 } 365 366 return $result; 367} 368 369sub is_valid 370{ 371 return !defined shift->{e}; 372} 373 374package OpenBSD::PkgSpec; 375sub subpattern_class 376{ "OpenBSD::PkgSpec::SubPattern" } 377sub new 378{ 379 my ($class, $pattern) = @_; 380 my @l = map { $class->subpattern_class->new($_) } 381 (split /\|/o, $pattern); 382 if (@l == 1) { 383 return $l[0]; 384 } else { 385 return bless \@l, $class; 386 } 387} 388 389sub match_ref 390{ 391 my ($self, $r) = @_; 392 if (wantarray) { 393 my @l = (); 394 for my $subpattern (@$self) { 395 push(@l, $subpattern->match_ref($r)); 396 } 397 return @l; 398 } else { 399 for my $subpattern (@$self) { 400 if ($subpattern->match_ref($r)) { 401 return 1; 402 } 403 } 404 return 0; 405 } 406} 407 408sub match_libs_ref 409{ 410 my ($self, $r) = @_; 411 if (wantarray) { 412 my @l = (); 413 for my $subpattern (@$self) { 414 push(@l, $subpattern->match_libs_ref($r)); 415 } 416 return @l; 417 } else { 418 for my $subpattern (@$self) { 419 if ($subpattern->match_libs_ref($r)) { 420 return 1; 421 } 422 } 423 return 0; 424 } 425} 426 427sub match_locations 428{ 429 my ($self, $r) = @_; 430 my $l = []; 431 for my $subpattern (@$self) { 432 push(@$l, @{$subpattern->match_locations($r)}); 433 } 434 return $l; 435} 436 437sub is_valid 438{ 439 my $self = shift; 440 for my $subpattern (@$self) { 441 return 0 unless $subpattern->is_valid; 442 } 443 return 1; 444} 445 446package OpenBSD::PkgSpec::SubPattern::Exact; 447our @ISA = qw(OpenBSD::PkgSpec::SubPattern); 448 449sub add_version_constraints 450{ 451 my ($class, $constraints, $vspec) = @_; 452 return if $vspec eq '*'; # XXX 453 my $v = OpenBSD::PkgSpec::versionspec->new($vspec); 454 die "not a good exact spec" if !$v->is_exact; 455 delete $v->{p}; 456 push(@$constraints, $v); 457} 458 459sub add_flavor_constraints 460{ 461 my ($class, $constraints, $flavorspec) = @_; 462 push(@$constraints, OpenBSD::PkgSpec::exactflavor->new($flavorspec)); 463} 464 465package OpenBSD::PkgSpec::Exact; 466our @ISA = qw(OpenBSD::PkgSpec); 467 468sub subpattern_class 469{ "OpenBSD::PkgSpec::SubPattern::Exact" } 470 4711; 472