1# ex:ts=8 sw=4:
2# $OpenBSD: Dependencies.pm,v 1.173 2019/10/13 16:22:58 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 $default = $dep->{def};
196
197	my $candidates = $self->{set}->match_locations($dep->spec);
198	if (!$state->defines('allversions')) {
199		require OpenBSD::Search;
200		$candidates = OpenBSD::Search::FilterLocation->
201		    keep_most_recent->filter_locations($candidates);
202	}
203	# XXX not really efficient, but hey
204	my %c = map {($_->name, $_)} @$candidates;
205
206	my @pkgs = keys %c;
207	if (@pkgs == 1) {
208		return $candidates->[0];
209	} elsif (@pkgs > 1) {
210		# unless -ii, we return the def if available
211		if ($state->is_interactive < 2) {
212			if (defined(my $d = $c{$default})) {
213				return $d;
214			}
215		}
216		# put default first if available
217		@pkgs = ((grep {$_ eq $default} @pkgs),
218		    (sort (grep {$_ ne $default} @pkgs)));
219		my $good = $state->ask_list(
220		    'Ambiguous: choose dependency for '.$self->{set}->print.': ',
221		    @pkgs);
222		return $c{$good};
223	} else {
224		return;
225	}
226}
227
228sub find_dep_in_stuff_to_install
229{
230	my ($self, $state, $dep) = @_;
231
232	my $v = $self->find_candidate($dep,
233	    keys %{$state->tracker->{uptodate}});
234	if ($v) {
235		$self->set_global($dep, _cache::installed->new($v));
236		return $v;
237	}
238	# this is tricky, we don't always know what we're going to actually
239	# install yet.
240	my @candidates = $dep->spec->filter(keys %{$state->tracker->{to_update}});
241	if (@candidates > 0) {
242		for my $k (@candidates) {
243			my $set = $state->tracker->{to_update}{$k};
244			$self->add_dep($set);
245		}
246		if (@candidates == 1) {
247			$self->set_cache($dep,
248			    _cache::to_update->new($candidates[0]));
249		}
250		return $candidates[0];
251	}
252
253	$v = $self->find_candidate($dep, keys %{$state->tracker->{to_install}});
254	if ($v) {
255		$self->set_cache($dep, _cache::to_install->new($v));
256		$self->add_dep($state->tracker->{to_install}{$v});
257	}
258	return $v;
259}
260
261sub really_solve_dependency
262{
263	my ($self, $state, $dep, $package) = @_;
264
265	my $v;
266
267	if ($state->{allow_replacing}) {
268
269		$v = $self->find_dep_in_self($state, $dep);
270		if ($v) {
271			$self->set_cache($dep, _cache::self->new($v));
272			push(@{$package->{before}}, $v);
273			return $v;
274		}
275		$v = $self->find_candidate($dep, $self->{set}->older_names);
276		if ($v) {
277			push(@{$self->{bad}}, $dep->{pattern});
278			return $v;
279		}
280		$v = $self->find_dep_in_stuff_to_install($state, $dep);
281		return $v if $v;
282	}
283
284	$v = $self->find_dep_in_installed($state, $dep);
285	if ($v) {
286		if ($state->{newupdates}) {
287			if ($state->tracker->is_known($v)) {
288				return $v;
289			}
290			my $set = $state->updateset->add_older(OpenBSD::Handle->create_old($v, $state));
291			$set->merge_paths($self->{set});
292			$self->add_dep($set);
293			$self->set_cache($dep, _cache::to_update->new($v));
294			$state->tracker->todo($set);
295		}
296		return $v;
297	}
298	if (!$state->{allow_replacing}) {
299		$v = $self->find_dep_in_stuff_to_install($state, $dep);
300		return $v if $v;
301	}
302
303	$v = $self->find_dep_in_repositories($state, $dep);
304
305	my $s;
306	if ($v) {
307		$s = $state->updateset_from_location($v);
308		$v = $v->name;
309	} else {
310		# resort to default if nothing else
311		$v = $dep->{def};
312		$s = $state->updateset_with_new($v);
313	}
314
315	$s->merge_paths($self->{set});
316	$state->tracker->todo($s);
317	$self->add_dep($s);
318	$self->set_cache($dep, _cache::to_install->new($v));
319	return $v;
320}
321
322sub check_depends
323{
324	my $self = shift;
325
326	for my $dep ($self->dependencies) {
327		push(@{$self->{bad}}, $dep)
328		    unless is_installed($dep) or
329		    	defined $self->{set}{newer}{$dep};
330	}
331	return $self->{bad};
332}
333
334sub register_dependencies
335{
336	my ($self, $state) = @_;
337
338	require OpenBSD::RequiredBy;
339	for my $pkg ($self->{set}->newer) {
340		my $pkgname = $pkg->pkgname;
341		my @l = keys %{$self->{to_register}{$pkg}};
342
343		OpenBSD::Requiring->new($pkgname)->add(@l);
344		for my $dep (@l) {
345			OpenBSD::RequiredBy->new($dep)->add($pkgname);
346		}
347	}
348}
349
350sub repair_dependencies
351{
352	my ($self, $state) = @_;
353	for my $p ($self->{set}->newer) {
354		my $pkgname = $p->pkgname;
355		for my $pkg (installed_packages(1)) {
356			my $plist = OpenBSD::PackingList->from_installation(
357			    $pkg, \&OpenBSD::PackingList::DependOnly);
358			$plist->repair_dependency($pkg, $pkgname);
359		}
360	}
361}
362
363sub find_old_lib
364{
365	my ($self, $state, $base, $pattern, $lib) = @_;
366
367	require OpenBSD::Search;
368
369	my $r = $state->repo->installed->match_locations(OpenBSD::Search::PkgSpec->new(".libs-".$pattern));
370	for my $try (map {$_->name} @$r) {
371		OpenBSD::SharedLibs::add_libs_from_installed_package($try, $state);
372		if ($self->check_lib_spec($base, $lib, {$try => 1})) {
373			return $try;
374		}
375	}
376	return undef;
377}
378
379sub errsay_library
380{
381	my ($solver, $state, $h) = @_;
382
383	$state->errsay("Can't install #1 because of libraries", $h->pkgname);
384}
385
386sub solve_old_depends
387{
388	my ($self, $state) = @_;
389
390	$self->{old_dependencies} = {};
391	for my $package ($self->{set}->older) {
392		for my $dep (@{$package->dependency_info->{depend}}) {
393			my $v = $self->solve_dependency($state, $dep, $package);
394			# XXX
395			next if !defined $v;
396			$self->{old_dependencies}{$v} = $dep;
397		}
398	}
399}
400
401sub solve_handle_tags
402{
403	my ($solver, $h, $state) = @_;
404	my $plist = $h->plist;
405	return 1 if !defined $plist->{tags};
406	my $okay = 1;
407	$solver->{tag_finder} //= OpenBSD::lookup::tag->new($solver, $state);
408	for my $tag (@{$plist->{tags}}) {
409		$solver->find_in_self($plist, $state, $tag) ||
410		$solver->{tag_finder}->lookup($solver,
411		    $solver->{to_register}{$h}, $state, $tag);
412		if (!$solver->verify_tag($tag, $state, $plist, $h->{is_old})) {
413			$okay = 0;
414		}
415	}
416	return $okay;
417}
418
419sub solve_tags
420{
421	my ($solver, $state) = @_;
422
423	my $okay = 1;
424	for my $h ($solver->{set}->changed_handles) {
425		if (!$solver->solve_handle_tags($h, $state)) {
426			$okay = 0;
427		}
428	}
429	if (!$okay) {
430		$solver->dump($state);
431		$solver->{tag_finder}->dump($state);
432	}
433	return $okay;
434}
435
436package OpenBSD::PackingElement;
437sub repair_dependency
438{
439}
440
441package OpenBSD::PackingElement::Dependency;
442sub repair_dependency
443{
444	my ($self, $requiring, $required) = @_;
445	if ($self->spec->filter($required) == 1) {
446		require OpenBSD::RequiredBy;
447		OpenBSD::RequiredBy->new($required)->add($requiring);
448		OpenBSD::Requiring->new($requiring)->add($required);
449	}
450}
451
4521;
453