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