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