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