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