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