xref: /openbsd/usr.sbin/pkg_add/OpenBSD/PkgSpec.pm (revision cca36db2)
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