xref: /openbsd-src/usr.sbin/pkg_add/OpenBSD/Dependencies.pm (revision 039cbdaaca23c9e872a2bab23f91224c76c0f23b)
1# ex:ts=8 sw=4:
2# $OpenBSD: Dependencies.pm,v 1.175 2023/06/13 09:07:17 espie Exp $
3#
4# Copyright (c) 2005-2010 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
17use v5.36;
18
19use OpenBSD::Dependencies::SolverBase;
20
21package _cache;
22
23sub new($class, $v)
24{
25	bless \$v, $class;
26}
27
28sub pretty($self)
29{
30	return ref($self)."(".$$self.")";
31}
32
33package _cache::self;
34our @ISA=(qw(_cache));
35sub do($v, $solver, $state, $dep, $package)
36{
37	push(@{$package->{before}}, $$v);
38	return $$v;
39}
40
41package _cache::installed;
42our @ISA=(qw(_cache));
43sub do($v, $solver, $state, $dep, $package)
44{
45	return $$v;
46}
47
48package _cache::bad;
49our @ISA=(qw(_cache));
50sub do($v, $solver, $state, $dep, $package)
51{
52	return $$v;
53}
54
55package _cache::to_install;
56our @ISA=(qw(_cache));
57sub do($v, $solver, $state, $dep, $package)
58{
59	if ($state->tracker->{uptodate}{$$v}) {
60		bless $v, "_cache::installed";
61		$solver->set_global($dep, $v);
62		return $$v;
63	}
64	if ($state->tracker->{cant_install}{$$v}) {
65		bless $v, "_cache::bad";
66		$solver->set_global($dep, $v);
67		return $$v;
68	}
69	if ($state->tracker->{to_install}{$$v}) {
70		my $set = $state->tracker->{to_install}{$$v};
71		if ($set->real_set eq $solver->{set}) {
72			bless $v, "_cache::self";
73			return $v->do($solver, $state, $dep, $package);
74		} else {
75			$solver->add_dep($set);
76			return $$v;
77		}
78	}
79	return;
80}
81
82package _cache::to_update;
83our @ISA=(qw(_cache));
84sub do($v, $solver, $state, $dep, $package)
85{
86	my $alt = $solver->find_dep_in_self($state, $dep);
87	if ($alt) {
88		$solver->set_cache($dep, _cache::self->new($alt));
89		push(@{$package->{before}}, $alt);
90		return $alt;
91	}
92
93	if ($state->tracker->{to_update}{$$v}) {
94		$solver->add_dep($state->tracker->{to_update}{$$v});
95		return $$v;
96	}
97	if ($state->tracker->{uptodate}{$$v}) {
98		bless $v, "_cache::installed";
99		$solver->set_global($dep, $v);
100		return $$v;
101	}
102	if ($state->tracker->{cant_update}{$$v}) {
103		bless $v, "_cache::bad";
104		$solver->set_global($dep, $v);
105		return $$v;
106	}
107	my @candidates = $dep->spec->filter(keys %{$state->tracker->{installed}});
108	if (@candidates > 0) {
109		$solver->set_global($dep, _cache::installed->new($candidates[0]));
110		return $candidates[0];
111	}
112	return;
113}
114
115package OpenBSD::Dependencies::Solver;
116our @ISA = qw(OpenBSD::Dependencies::SolverBase);
117
118use OpenBSD::PackageInfo;
119
120sub merge($solver, @extra)
121{
122	$solver->clone('cache', @extra);
123}
124
125sub new($class, $set)
126{
127	bless { set => $set, bad => [] }, $class;
128}
129
130sub check_for_loops($self, $state)
131{
132	my $initial = $self->{set};
133
134	my @todo = ();
135	my @to_merge = ();
136	push(@todo, $initial);
137	my $done = {};
138
139	while (my $set = shift @todo) {
140		next unless defined $set->{solver};
141		for my $l (values %{$set->solver->{deplist}}) {
142			if ($l eq $initial) {
143				push(@to_merge, $set);
144			}
145			next if $done->{$l};
146			next if $done->{$l->real_set};
147			push(@todo, $l);
148			$done->{$l} = $set;
149		}
150	}
151	if (@to_merge > 0) {
152		my $merged = {};
153		my @real = ();
154		$state->say("Detected loop, merging sets #1", $state->ntogo);
155		$state->say("| #1", $initial->print);
156		for my $set (@to_merge) {
157			my $k = $set;
158			while ($k ne $initial && !$merged->{$k}) {
159				unless ($k->{finished}) {
160					$state->say("| #1", $k->print);
161					delete $k->solver->{deplist};
162					delete $k->solver->{to_register};
163					push(@real, $k);
164				}
165				$merged->{$k} = 1;
166				$k = $done->{$k};
167			}
168		}
169		delete $initial->solver->{deplist};
170		delete $initial->solver->{to_register};
171		$initial->merge($state->tracker, @real);
172	}
173}
174
175sub find_dep_in_repositories($self, $state, $dep)
176{
177	return unless $dep->spec->is_valid;
178
179	my $default = $dep->{def};
180
181	my $candidates = $self->{set}->match_locations($dep->spec);
182	if (!$state->defines('allversions')) {
183		require OpenBSD::Search;
184		$candidates = OpenBSD::Search::FilterLocation->
185		    keep_most_recent->filter_locations($candidates);
186	}
187	# XXX not really efficient, but hey
188	my %c = map {($_->name, $_)} @$candidates;
189
190	my @pkgs = keys %c;
191	if (@pkgs == 1) {
192		return $candidates->[0];
193	} elsif (@pkgs > 1) {
194		# unless -ii, we return the def if available
195		if ($state->is_interactive < 2) {
196			if (defined(my $d = $c{$default})) {
197				return $d;
198			}
199		}
200		# put default first if available
201		@pkgs = ((grep {$_ eq $default} @pkgs),
202		    (sort (grep {$_ ne $default} @pkgs)));
203		my $good = $state->ask_list(
204		    'Ambiguous: choose dependency for '.$self->{set}->print.': ',
205		    @pkgs);
206		return $c{$good};
207	} else {
208		return;
209	}
210}
211
212sub find_dep_in_stuff_to_install($self, $state, $dep)
213{
214	my $v = $self->find_candidate($dep,
215	    keys %{$state->tracker->{uptodate}});
216	if ($v) {
217		$self->set_global($dep, _cache::installed->new($v));
218		return $v;
219	}
220	# this is tricky, we don't always know what we're going to actually
221	# install yet.
222	my @candidates = $dep->spec->filter(keys %{$state->tracker->{to_update}});
223	if (@candidates > 0) {
224		for my $k (@candidates) {
225			my $set = $state->tracker->{to_update}{$k};
226			$self->add_dep($set);
227		}
228		if (@candidates == 1) {
229			$self->set_cache($dep,
230			    _cache::to_update->new($candidates[0]));
231		}
232		return $candidates[0];
233	}
234
235	$v = $self->find_candidate($dep, keys %{$state->tracker->{to_install}});
236	if ($v) {
237		$self->set_cache($dep, _cache::to_install->new($v));
238		$self->add_dep($state->tracker->{to_install}{$v});
239	}
240	return $v;
241}
242
243sub really_solve_dependency($self, $state, $dep, $package)
244{
245	my $v;
246
247	if ($state->{allow_replacing}) {
248
249		$v = $self->find_dep_in_self($state, $dep);
250		if ($v) {
251			$self->set_cache($dep, _cache::self->new($v));
252			push(@{$package->{before}}, $v);
253			return $v;
254		}
255		$v = $self->find_candidate($dep, $self->{set}->older_names);
256		if ($v) {
257			push(@{$self->{bad}}, $dep->{pattern});
258			return $v;
259		}
260		$v = $self->find_dep_in_stuff_to_install($state, $dep);
261		return $v if $v;
262	}
263
264	$v = $self->find_dep_in_installed($state, $dep);
265	if ($v) {
266		if ($state->{newupdates}) {
267			if ($state->tracker->is_known($v)) {
268				return $v;
269			}
270			my $set = $state->updateset->add_older(OpenBSD::Handle->create_old($v, $state));
271			$set->merge_paths($self->{set});
272			$self->add_dep($set);
273			$self->set_cache($dep, _cache::to_update->new($v));
274			$state->tracker->todo($set);
275		}
276		return $v;
277	}
278	if (!$state->{allow_replacing}) {
279		$v = $self->find_dep_in_stuff_to_install($state, $dep);
280		return $v if $v;
281	}
282
283	$v = $self->find_dep_in_repositories($state, $dep);
284
285	my $s;
286	if ($v) {
287		$s = $state->updateset_from_location($v);
288		$v = $v->name;
289	} else {
290		# resort to default if nothing else
291		$v = $dep->{def};
292		$s = $state->updateset_with_new($v);
293	}
294
295	$s->merge_paths($self->{set});
296	$state->tracker->todo($s);
297	$self->add_dep($s);
298	$self->set_cache($dep, _cache::to_install->new($v));
299	return $v;
300}
301
302sub check_depends($self)
303{
304	for my $dep ($self->dependencies) {
305		push(@{$self->{bad}}, $dep)
306		    unless is_installed($dep) or
307		    	defined $self->{set}{newer}{$dep};
308	}
309	return $self->{bad};
310}
311
312sub register_dependencies($self, $state)
313{
314	require OpenBSD::RequiredBy;
315	for my $pkg ($self->{set}->newer) {
316		my $pkgname = $pkg->pkgname;
317		my @l = keys %{$self->{to_register}{$pkg}};
318
319		OpenBSD::Requiring->new($pkgname)->add(@l);
320		for my $dep (@l) {
321			OpenBSD::RequiredBy->new($dep)->add($pkgname);
322		}
323	}
324}
325
326sub repair_dependencies($self, $state)
327{
328	for my $p ($self->{set}->newer) {
329		my $pkgname = $p->pkgname;
330		for my $pkg (installed_packages(1)) {
331			my $plist = OpenBSD::PackingList->from_installation(
332			    $pkg, \&OpenBSD::PackingList::DependOnly);
333			$plist->repair_dependency($pkg, $pkgname);
334		}
335	}
336}
337
338sub find_old_lib($self, $state, $base, $pattern, $lib)
339{
340
341	require OpenBSD::Search;
342
343	my $r = $state->repo->installed->match_locations(OpenBSD::Search::PkgSpec->new(".libs-".$pattern));
344	for my $try (map {$_->name} @$r) {
345		$state->shlibs->add_libs_from_installed_package($try);
346		if ($self->check_lib_spec($state, $base, $lib, {$try => 1})) {
347			return $try;
348		}
349	}
350	return undef;
351}
352
353sub errsay_library($solver, $state, $h)
354{
355	$state->errsay("Can't install #1 because of libraries", $h->pkgname);
356}
357
358sub solve_old_depends($self, $state)
359{
360	$self->{old_dependencies} = {};
361	for my $package ($self->{set}->older) {
362		for my $dep (@{$package->dependency_info->{depend}}) {
363			my $v = $self->solve_dependency($state, $dep, $package);
364			# XXX
365			next if !defined $v;
366			$self->{old_dependencies}{$v} = $dep;
367		}
368	}
369}
370
371sub solve_handle_tags($solver, $h, $state)
372{
373	my $plist = $h->plist;
374	return 1 if !defined $plist->{tags};
375	my $okay = 1;
376	$solver->{tag_finder} //= OpenBSD::lookup::tag->new($solver, $state);
377	for my $tag (@{$plist->{tags}}) {
378		$solver->find_in_self($plist, $state, $tag) ||
379		$solver->{tag_finder}->lookup($solver,
380		    $solver->{to_register}{$h}, $state, $tag);
381		if (!$solver->verify_tag($tag, $state, $plist, $h->{is_old})) {
382			$okay = 0;
383		}
384	}
385	return $okay;
386}
387
388sub solve_tags($solver, $state)
389{
390	my $okay = 1;
391	for my $h ($solver->{set}->changed_handles) {
392		if (!$solver->solve_handle_tags($h, $state)) {
393			$okay = 0;
394		}
395	}
396	if (!$okay) {
397		$solver->dump($state);
398		$solver->{tag_finder}->dump($state);
399	}
400	return $okay;
401}
402
403package OpenBSD::PackingElement;
404sub repair_dependency($, $, $)
405{
406}
407
408package OpenBSD::PackingElement::Dependency;
409sub repair_dependency($self, $requiring, $required)
410{
411	if ($self->spec->filter($required) == 1) {
412		require OpenBSD::RequiredBy;
413		OpenBSD::RequiredBy->new($required)->add($requiring);
414		OpenBSD::Requiring->new($requiring)->add($required);
415	}
416}
417
4181;
419