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