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