From 070a91ce7b7730e86e9964f42185fa810bb89ed3 Mon Sep 17 00:00:00 2001 From: John McQuah <jquah@mx.sdf.org> Date: Tue, 14 Mar 2023 12:18:28 -0400 Subject: [PATCH] prt-auf: refine the arg parser, speed up the dependency resolver --- scripts/prt-auf | 438 +++++++++++++++++++++++++++--------------------- 1 file changed, 244 insertions(+), 194 deletions(-) diff --git a/scripts/prt-auf b/scripts/prt-auf index 68fadfd..b46a68c 100755 --- a/scripts/prt-auf +++ b/scripts/prt-auf @@ -11,17 +11,17 @@ use warnings; use strict; ################### Initialize global variables ####################### -my $title="prt-auf"; my $version=0.5; +my $title="prt-auf"; my $version=0.51; my $cache_ver="V5.1"; my $CONFDIR = "/var/lib/pkg"; my $prtalias="/etc/prt-get.aliases"; my $pkgdb="$CONFDIR/db"; my $prtlocker="$CONFDIR/prt-get.locker"; -my $prtcache="$CONFDIR/prt-auf.cache"; my @LOCKED; my %ALIASES; my %DEPENDS; +my $prtcache="$CONFDIR/prt-get.cache"; my @LOCKED; my %ALIASES; my %DEPENDS; my @allports; my %V_REPO; my %V_INST; my %DESC; my %SOFTDEPS; my @results; my $strf; my $ind; my $hh; my $portpath; my $built_pkg; my %osearch = ( cache => 0, regex => 0, path => 0, exact => 0, verbose => 0 ); my %odepends = ( soft => 0, tree => 0, recursive => 0, all => 0 ); my %opkg = ( margs => "", aargs => "", rargs => "", runscripts => "yes", makecommand => "/usr/bin/pkgmk", addcommand => "/usr/bin/pkgadd", - removecommand => "/usr/bin/pkgrm", test => "no" ); + removecommand => "/usr/bin/pkgrm", test => "no", group => "no" ); my %olog = ( write => "disabled", mode => "overwrite", rm_on_success => "yes", file => "/var/log/pkgbuild/%n.log" ); my $prtconf = "/etc/prt-get.conf"; @@ -66,15 +66,16 @@ if ($action eq "path") { @results = find_port_by_name($query[0],1,1,0); } elsif ($action eq "unlock") { port_unlock(@query); } elsif ($action eq "ls") { port_ls(@query); } elsif ($action =~ /^(cat|edit|readme)$/) { port_edit($1,@query); -} elsif ($action =~ /^(depends|deptree|quickdep)$/) { @results=deporder($1,@query); -} elsif ($action eq "dependent") { @results=list_ports("dependent",@query); -} elsif ($action eq "sysup") { @results = sysup(); -} elsif ($action =~ /^(install|update|depinst|grpinst)$/) { - @results = up_inst($1,@query); +} elsif ($action =~ /^(depends|quick)dep$/) { @results=deporder($1,@query); +} elsif ($action =~ /^dep(endent|tree)$/) { @results=list_ports($action,@query); +} elsif ($action eq "sysup") { my @outdated = grep { s/ .+// } port_diff("quick"); + @results = up_inst("sysup", @outdated); +} elsif ($action =~ /^(install|update)$/) { @results = up_inst($action,@query); +} elsif ($action eq "depinst") { @results = up_inst("depinst",@query); } elsif ($action eq "dup") { $ind=find_dups(@query); } elsif ($action eq "remove") { $ind=uninstall(@query); -} elsif ($action =~ /^(isinst|current)$/) { $ind = port_diff($1,@query); -} elsif ($action =~ /(.*)diff$/) { $ind = port_diff($1); +} elsif ($action =~ /^(isinst|current)$/) { @results = port_diff($1,@query); +} elsif ($action =~ /(.*)diff$/) { @results = port_diff($1); } elsif ($action =~ /^list(.*)/) { @results = list_ports($1); } elsif ($action eq "help") { print_help(); } elsif ($action eq "version") { print "$title $version\n"; @@ -88,7 +89,7 @@ if ($action =~ /^(listinst|listorphans)/) { $result .= " $V_INST{$result}\n$DESC{$result}\n" if $osearch{verbose}>1; printf $strf, $result; } -} elsif ($action =~ /^(list|search|dsearch|path|dependent)/) { +} elsif ($action =~ /^(list|search|dsearch|path)/) { foreach my $result (@results) { next if ((! $result) or ($result =~ /^\s*$/)); $result =~ s/.*\/(.*)$/$1/ if (($action ne "path") and ($osearch{path}==0)); @@ -104,27 +105,34 @@ if ($action =~ /^(listinst|listorphans)/) { @fmatch = split /\s/, $hits{$fh}; foreach my $fileN (@fmatch) { printf $strf, $fh, $fileN; } } -} elsif ($action =~ /^(diff|quickdiff|current|isinst|dup)$/) { - exit $ind; -} elsif ($action =~ /^(depends|deptree|quickdep)$/) { - print "-- dependencies ([i] = installed, [a] = alias installed)\n" if ($action =~ /^dep/); - my $indent=($action eq "deptree") ? " " : ""; - my @installed=keys %V_INST unless ($action eq "quickdep"); - my %seen; my $strf="%3s %s\n"; my $depline; my $dep; my $missing=0; - foreach $depline (@results) { - if ($depline =~ /MISSING/) { $missing=1; print "-- missing packages\n"; next; } - my $cleandep = $depline; - $cleandep =~ s/ .satisfies dependency .*// if ($action eq "deptree"); - $dep = (split / /, $cleandep)[-1]; - next if ((! $dep) or (($seen{$dep}) and ($odepends{all}==0))); - $seen{$dep}=1; +} elsif ($action =~ /^(current|isinst|dup|diff|quickdiff)$/) { + my $format = "%20s %15s %20s\n"; my $ind = shift(@results); + if ($action eq "diff") { printf $format, "Port", "Installed", "Available in Repo"; } + foreach my $diffline (@results) { + if ($action =~ /^(current|isinst)$/) { print $diffline; next; } + my ($diffN, $diffI, $diffR) = split(/ /, $diffline); + next if (($osearch{filter}) and ($diffN !~ /$osearch{filter}/)); + next if ((grep {$_ eq $diffN} @LOCKED) and ($odepends{all}==0)); + printf "$format", $diffN, $diffI, $diffR if ($action eq "diff"); + printf "%s ", $diffN if ($action eq "quickdiff" and $diffR ne "MISSING!"); + } + print "\n" if ($action eq "quickdiff"); + exit $ind; +} elsif ($action =~ /^(depends|quickdep)$/) { + print "-- dependency list ([i] = installed, [a] = alias installed)\n" if ($action =~ /^dep/); + my $strf="%3s %s\n"; my $dep; my $missing=0; + foreach $dep (@results) { + if ($dep =~ /MISSING/) { $missing=1; print "-- missing packages\n"; next; } + next if (! $dep); my $alias; if ($action ne "quickdep") { - $ind = (grep { $_ eq $dep } @installed) ? "[i]" : "[ ]"; - $ind = ($depline =~ / .satisfies dependency /) ? "[a]" : $ind; + $ind = (grep { $_ eq $dep } keys %V_INST) ? "[i]" : "[ ]"; + $alias = who_aliased_to($dep); + $ind = ((! $V_INST{$dep}) and ($alias)) ? "[a]" : $ind; + $dep .= " (provided by $alias)" if ($ind eq "[a]"); + $dep .= " $V_REPO{$dep}" if (($osearch{verbose}==1) and ($ind ne "[a]")); + $dep .= " $V_REPO{$dep}\n$DESC{$dep}" if (($osearch{verbose}>1) and ($ind ne "[a]")); } - $depline .= " $V_REPO{$dep}" if $osearch{verbose}==1; - $depline .= " $V_REPO{$dep}\n$DESC{$dep}" if $osearch{verbose}>1; - printf $strf, $ind, $depline unless ($action eq "quickdep"); + printf $strf, $ind, $dep unless ($action eq "quickdep"); printf "%s ", $dep if ($action eq "quickdep"); } print "\n" if ($action eq "quickdep"); @@ -132,20 +140,20 @@ if ($action =~ /^(listinst|listorphans)/) { $strf = "%14s: %-s\n"; exit 1 if ($#results < 0); my @fields = ("Name", "Repository", "Version", "Release", "Description", - "Dependencies", "Optional Deps", "URL", "Packager", "Maintainer", + "Dependencies", "URL", "Optional Deps", "Maintainer", "Readme", "PreInstall", "PostInstall"); - for (my $i=0; $i<6; $i++) { printf $strf, $fields[$i], $results[$i]; } - printf $strf, $fields[7], $results[7]; - printf $strf, $fields[9], $results[9]; + for (my $i=0; $i<9; $i++) { printf $strf, $fields[$i], $results[$i]; } } elsif ($action eq "remove") { my %removed = %$ind; my @successes = grep { $removed{$_}==1 } keys %removed; my @failures = grep { $removed{$_}==0 } keys %removed; print "Ports removed:\n" if (@successes); foreach my $p (@successes) { print "$p\n"; } -} elsif ($action =~ /^(install|update|depinst|grpinst|sysup)$/) { +} elsif ($action =~ /^(install|update|depinst|sysup)$/) { my @ok = @{$results[0]}; my @ok_pre = @{$results[1]}; my @ok_post = @{$results[2]}; my @ok_readme = @{$results[3]}; my @not_ok = @{$results[4]}; my $note; + if ($opkg{test} eq "yes") { print "\n$action successful.\n"; + print "*** prt-auf: test mode end\n\n"; } if (($opkg{test} eq "no") and (@ok)) { print "Successful ports:\n"; foreach my $k (@ok) { @@ -156,12 +164,12 @@ if ($action =~ /^(listinst|listorphans)/) { } print "\n"; } - if (($opkg{test} eq "no") and (@ok_readme)) { - print "Successful ports with README files:\n"; + if (@ok_readme) { + print "Ports with README files:\n"; foreach (@ok_readme) { print " $_\n"; } print "\n"; } - if (($opkg{test} eq "no") and (@not_ok)) { + if (@not_ok) { print "Ports with pkgmk/pkgadd failures:\n"; foreach (@not_ok) { print " $_\n"; } print "\n"; @@ -175,10 +183,13 @@ sub parse_args { my @query; while (my $arg = shift) { if ($arg =~ /^(search|dsearch|fsearch|path|info|list|remove)$/) { $action = $1; - } elsif ($arg =~ /^(install|update|depinst|grpinst|sysup)$/) { $action = $1; + } elsif ($arg =~ /^(install|update|depinst|sysup)$/) { $action = $1; + } elsif ($arg eq "grpinst") { $action = "install"; $opkg{group} = "yes"; + print "Warning: grpinst is obsolescent, using install --group\n"; } elsif ($arg =~ /^(lock|unlock|listlocked|current|isinst)$/) { $action = $1; } elsif ($arg =~ /^(diff|quickdiff|printf|listinst|listorphans)$/) { $action = $1; - } elsif ($arg =~ /^(depends|deptree|quickdep|dependent|dup)$/) { $action = $1; + } elsif ($arg =~ /^(depends|quickdep|dup|dependent)$/) { $action = $1; + } elsif ($arg eq "deptree") { $action = $arg; $odepends{tree} = 1; } elsif ($arg =~ /^(readme|cat|edit|ls|help|version|cache)$/) { $action = $1; } elsif ($arg eq "--tree") { $odepends{tree} = 1; } elsif ($arg eq "--all") { $odepends{all} = 1; @@ -192,6 +203,7 @@ sub parse_args { } elsif ($arg eq "-v") { $osearch{verbose} += 1; } elsif ($arg eq "-vv") { $osearch{verbose} += 2; } elsif ($arg eq "--test") { $opkg{test} = "yes"; + } elsif ($arg eq "--group") { $opkg{group} = "yes"; } elsif ($arg eq "-fr") { $opkg{margs} .= " -f"; } elsif ($arg =~ /^(-uf|-if|-us|-is|-ns|-kw)$/) { $opkg{margs} .= " $1"; } elsif ($arg =~ /^--margs=(.+)/) { $opkg{margs} .= $1; @@ -219,7 +231,7 @@ sub parse_args { print "$1 requires exactly one argument.\n"; exit 1; } if (($#query < 0) and - ($action =~ /^(install|update|depinst|grpinst|remove)$/)) { + ($action =~ /^(install|update|depinst|remove)$/)) { print "$1 requires at least one argument.\n"; exit 1; } return $action, @query; @@ -312,8 +324,8 @@ sub who_aliased_to { sub printf_ports { my $FS; my @pstats; my $p; my $inputf=shift; my @targets=@_; my @pos; my @outfields; my $outputf; my %FS = ( "t"=>"\t", "n"=>"\n" ); - my %subscripts = ( "n"=>0, "p"=>1, "v"=>2, "r"=>3, "d"=>4, "o"=>5, "e"=>6, - "u"=>7, "P"=>8, "M"=>9, "R"=>10, "E"=>11, "O"=>12, "l"=>13, "i"=>14); + my %subscripts = ( "n"=>0, "p"=>1, "v"=>2, "r"=>3, "d"=>4, "u"=>5, + "P"=>7, "M"=>8, "R"=>9, "E"=>10, "O"=>11, "l"=>12, "i"=>13); if ($inputf eq "CACHE") { open (CACHE,'>',$prtcache) or die "cannot create a new cache file"; print CACHE "V5.1\n"; @@ -331,19 +343,19 @@ sub printf_ports { foreach (@outfields) { if (m/\\(t|n)/) { $outputf .= $FS{$1}; next; } $strf = $_; - s/%(p|n|v|r|d|o|e|u|P|M|R|E|O|l|i)/_Z_$subscripts{$1}/g; + s/%(p|n|v|r|d|u|P|M|R|E|O|l|i)/_Z_$subscripts{$1}/g; push @pos, grep { s/([0-9]+)(.*)/$1/ } (split /_Z_/, $_); - $strf =~ s/%(p|n|v|r|d|o|e|u|P|M|R|E|O|l|i)/%s/g; + $strf =~ s/%(p|n|v|r|d|u|P|M|R|E|O|l|i)/%s/g; $outputf .= $strf; } foreach my $pp (@targets) { $p = (split /\//, $pp)[-1]; @pstats = get_pkgfile_fields($pp,"all"); - $pstats[13] = (grep /^$p$/, @LOCKED) ? "yes" : "no"; - $pstats[14] = (grep /^$p$/, keys %V_INST) ? "yes" : "no"; - if (($pstats[14] eq "yes") and ($V_INST{$p} ne $V_REPO{$p})) { - $pstats[14] = "diff" } + $pstats[12] = (grep /^$p$/, @LOCKED) ? "yes" : "no"; + $pstats[13] = (grep /^$p$/, keys %V_INST) ? "yes" : "no"; + if (($pstats[13] eq "yes") and ($V_INST{$p} ne $V_REPO{$p})) { + $pstats[13] = "diff" } printf STDOUT $outputf, @pstats[@pos]; } } @@ -352,7 +364,10 @@ sub printf_ports { sub fill_hashes_from_cache { open (my $cf,$prtcache) or die "cannot use $prtcache as a cache!\n"; my $p; my $parent; my $deps; my $softDeps; - my $ignored=<$cf>; # first line only contains the cache format version + my $ignored=<$cf>; chomp($ignored); + if ($ignored ne "$cache_ver") { + die "incompatible cache format; regenerate by running $0 cache"; + } while (1) { $p = <$cf>; last unless defined $p; @@ -360,13 +375,12 @@ sub fill_hashes_from_cache { push @allports, "$parent/$p"; $V_REPO{$p} = <$cf>; chomp($V_REPO{$p}); $V_REPO{$p} .= "-".<$cf>; $DESC{$p} = <$cf>; - $deps = <$cf>; $softDeps = <$cf>; + $deps = <$cf>; $ignored=<$cf>; $softDeps = <$cf>; chomp($deps, $softDeps, $DESC{$p}, $V_REPO{$p}); - $DEPENDS{$p} = $deps; - $SOFTDEPS{$p} = $softDeps; + $DEPENDS{$p} = $deps; $SOFTDEPS{$p} = $softDeps; $DEPENDS{$p} =~ s/, / /g; $DEPENDS{$p} =~ s/,/ /g; $SOFTDEPS{$p} =~ s/, / /g; $SOFTDEPS{$p} =~ s/,/ /g; - for (my $i=7; $i<14; $i++) { $ignored = <$cf>; } + for (my $i=8; $i<13; $i++) { $ignored = <$cf>; } } close ($cf); } @@ -389,7 +403,7 @@ sub fill_hashes_from_pkgfiles { } sub get_pkgfile_fields { - my ($descrip, $url, $maintainer, $packager, $Version, $Release)=('','','','','',0,0); + my ($descrip, $url, $maintainer, $Version, $Release)=('','','',0,0); my ($readme, $preInstall, $postInstall, $Dependencies, $SoftDeps)=("no","no","no",'',''); my $portpath = shift; my $Name = (split /\//, $portpath)[-1]; my $pkgfile = "$portpath/Pkgfile"; @@ -408,7 +422,6 @@ sub get_pkgfile_fields { elsif (s/^release=(.*)/$1/) { $Release = $_; } elsif (s/^# Depends on:\s*(.*)/$1/) { $Dependencies = $_; } elsif (s/^# (Optional|Nice to have):\s*(.*)/$2/) { $SoftDeps = $_; } - elsif (s/^# Packager:\s*(.*)/$1/) { $packager = $_; } elsif (s/^# Maintainer:\s*(.*)/$1/) { $maintainer = $_; } else {} } close(PF); @@ -417,7 +430,7 @@ sub get_pkgfile_fields { $SoftDeps =~ s/, / /g; $SoftDeps =~ s/,/ /g; if (shift) { return $Name, $portpath, $Version, $Release, $descrip, $Dependencies, - $SoftDeps, $url, $packager, $maintainer, $readme, $preInstall, $postInstall; + $url, $SoftDeps, $maintainer, $readme, $preInstall, $postInstall; } else { return $Version, $Release, $descrip, $Dependencies, $SoftDeps; } } @@ -500,7 +513,6 @@ sub port_unlock { sub list_ports { my @found; my $subset = shift; - our $indent="0 "; our $height=0; our @descendants=(); our @outfile; if (! $subset) { # empty arg: list all valid ports foreach my $collection (@basedirs) { @@ -515,13 +527,13 @@ sub list_ports { } } elsif ($subset eq "inst") { @found = keys %V_INST; } elsif ($subset eq "locked") { @found=@LOCKED; - } elsif ($subset =~ /^(orphans|dependent)$/) { + } elsif ($subset =~ /^(orphans|dependent|deptree)$/) { my $seed; my $sseed; - our @searchspace=(($subset eq "orphans") or ($odepends{all}==0)) ? - keys %V_INST : keys %DEPENDS; + our @searchspace=(($subset ne "orphans") and ($odepends{all} != 0)) ? + keys %V_REPO : keys %V_INST; @searchspace = grep { defined $DEPENDS{$_} } @searchspace; - if ($subset eq "dependent") { + if ($subset =~ /^dep/) { $seed=shift; $sseed = $seed; if (! find_port_by_name($seed,1,1,0)) { print "$seed not found in the ports tree.\n"; return; @@ -531,10 +543,12 @@ sub list_ports { } if ($subset eq "orphans") { - my %not_orphans = map { $_ => 0 } keys %V_INST; my @ndd; + my %not_orphans = map { $_ => 0 } keys %V_INST; foreach my $port (@searchspace) { - @ndd=split(/ /, $DEPENDS{$port}); - map { $not_orphans{$_} = 1 } @ndd; + map { $not_orphans{$_} = 1 } split(/ /, $DEPENDS{$port}); + if ($odepends{soft} == 1) { + map { $not_orphans{$_} = 1 } split(/ /, $SOFTDEPS{$port}); + } } @found = grep { $not_orphans{$_} eq 0 } keys %V_INST; } elsif (($subset eq "dependent") and ($odepends{recursive}==0)) { @@ -542,62 +556,81 @@ sub list_ports { if ($odepends{tree}==1) { @found = grep { s/^/ / } @found; unshift (@found, "$seed"); } - } elsif (($subset eq "dependent") and ($odepends{recursive}==1)) { - push @outfile, "$seed"; + } elsif ($subset =~ /^dep(endent|tree)/) { + our $direction = ($subset eq "deptree") ? "fwd" : "rev"; + my $header = (($subset eq "deptree") and ($odepends{tree} == 1)) ? + "-- dependencies ([i] = installed, '-->' = already shown)\n": + "-- reverse dependencies ('-->' = already shown)\n"; + if (($subset eq "deptree") and ($odepends{soft} == 1)) { + $header =~ s/installed,/installed, [s] = installed softdep,/; + } + print $header unless ($odepends{tree} == 0); - my @children = grep { " $DEPENDS{$_} " =~ / $sseed / } @searchspace; - foreach my $sd (@children) { recurse_offtree($sd); } - - sub recurse_offtree { - my $s = shift; push @outfile, (${indent}x(1+$height))."$s"; - my @offspring = grep { " $DEPENDS{$_} " =~ / $s / } @searchspace; - foreach my $dc (@offspring) { - if (grep /^$dc$/, @descendants) { - print "Warning: cyclic dependencies found!\n"; - return; - } - push (@descendants, $dc); - $height = 1+$#descendants; - recurse_offtree($dc); - pop(@descendants); - $height = 1+$#descendants; - } + our $indent=" "; our $height=0; + our $ind; our %seen; our @lineage; my @fosters=(); + $ind = ($V_INST{$seed}) ? "[i]" : "[ ]"; + print "$ind $seed\n" if (($odepends{tree}==1) or ($odepends{all}==1)); + $seen{$seed} = 1; + my @children = ($subset eq "deptree") ? split /[ ,]/, $DEPENDS{$sseed}: + grep { " $DEPENDS{$_} " =~ / $sseed / } @searchspace; + if ($odepends{soft}==1) { + @fosters = ($subset eq "deptree") ? + grep { ($V_INST{$_}) } split /[ ,]/, $SOFTDEPS{$sseed}: + grep { " $SOFTDEPS{$_} " =~ / $sseed / } @searchspace; } - my %seen; - @outfile = grep { !m/^\s*$/ } @outfile; - @outfile = sort(@outfile) unless ($odepends{tree}==1); - @found = ($odepends{tree}==1) ? grep { s/0 / /g } @outfile : - grep !$seen{$_}++, grep { s/0 //g } grep { /0 / } @outfile ; - unshift (@found, $seed) if ($odepends{tree}==1); + foreach my $sd (@children) { recurse_tree(0,$sd,$direction); } + foreach my $sd (@fosters) { recurse_tree(1,$sd,$direction); } + + sub recurse_tree { + my $greedy = shift; my $s = shift; my $direction=shift; + my $ps = (($seen{$s}) and ($odepends{all} !=1)) ? "-->\n" : "\n"; + $ind = ($V_INST{$s}) ? "[i]" : "[ ]"; + $ind = (($ind eq "[i]") and ($greedy)) ? "[s]" : $ind; + print $ind.(${indent}x(1+$height))."$s".$ps unless ($odepends{tree}==0); + return if (($seen{$s}) and ($odepends{all} !=1)); + $seen{$s} = 1; + my %curdeps = ($direction eq "fwd") ? + map {$_ => 0} split /[ ,]/, $DEPENDS{$s} : + map {$_ => 0} grep { " $DEPENDS{$_} " =~ / $s / } @searchspace; + if ($odepends{soft} == 1) { + my @optionals = ($direction eq "fwd") ? + grep { ($V_INST{$_}) } split /[ ,]/, $SOFTDEPS{$s} : + grep { " $SOFTDEPS{$_} " =~ / $s / } @searchspace; + map {$curdeps{$_} = 1} @optionals; + } + + foreach my $dc (keys %curdeps) { + if (grep /^$dc$/, @lineage) { + print "Warning: dependency cycle => ".$dc."\n" unless ($greedy|$curdeps{$dc}); + return; + } + push (@lineage, $dc); + $height = $#lineage+1; + recurse_tree($greedy|$curdeps{$dc},$dc,$direction); + pop(@lineage); + $height = $#lineage+1; + } + } + @found = sort(keys %seen); } # possibilities for the recursive switch have been exhausted else { } } # possibilities for the filter have been exhausted else { } - @found = sort(@found) unless (($subset) and ($subset eq "dependent") and ($odepends{tree}==1)); - return @found if ((! $subset) or ($subset =~ /^(orphans|dependent|locked)$/)); + return @found if ((! $subset) or ($subset =~ /^(orphans|locked)$/)); if (! $osearch{filter}) { return @found; } else { return grep {$_ !~ /$osearch{filter}/} @found; } } -sub port_diff { # returns a scalar indicating how many differences were found - my $dtype=shift; my $lastcol; - my @argq=@_; my $retval=0; my $format="%30s %20s %20s\n"; +sub port_diff { # returns a list of all the ports with differences + my $dtype=shift; my @argq=@_; my @outfile=(); my $retval=0; if ($dtype !~ /^(current|isinst|utd)/) { - printf "$format", "Port", "Installed", "In Repository" if (! $dtype); foreach my $p (sort(keys %V_INST)) { - next if (($osearch{filter}) and ($p !~ /$osearch{filter}/)); - next if ((grep /$p/, @LOCKED) and ($odepends{all}==0)); - $lastcol = ($V_REPO{$p}) ? $V_REPO{$p} : "MISSING!"; - - if ($lastcol ne $V_INST{$p}) { - printf "$format", $p, $V_INST{$p}, $lastcol if (! $dtype); - printf "%s ", $p if ($dtype eq "quick" and $lastcol ne "MISSING!"); - } + push @outfile, "$p $V_INST{$p} $V_REPO{$p}" if (($V_REPO{$p}) and ($V_INST{$p} ne $V_REPO{$p})); + push @outfile, "$p $V_INST{$p} MISSING" if (! $V_REPO{$p}); } - printf "\n" if ($dtype eq "quick"); } elsif ($dtype eq "utd") { while (my $q=shift(@argq) and $retval==0) { $retval-- if (! $V_INST{$q}); @@ -606,92 +639,112 @@ sub port_diff { # returns a scalar indicating how many differences were found } elsif ($dtype =~ /^(current|isinst)$/) { foreach my $q (@argq) { if (! $V_INST{$q}) { - print "$q: not installed\n"; $retval++; + push @outfile, "$q: not installed"; $retval++; } else { - print "$V_INST{$q}\n" if ($dtype eq "current"); - print "$q is installed.\n" if ($dtype eq "isinst"); + push @outfile, "$q: version $V_INST{$q}" if ($dtype eq "current"); + push @outfile, "$q is installed." if ($dtype eq "isinst"); } } } else {} - return $retval; + return $retval, @outfile; } sub deporder { - # returns an indented list if called with first arg "deptree", - # otherwise returns a flattened list, pruned of duplicates. + # returns an annotated list using a variation on depth-first search. # Recursion does NOT continue beyond a dependency satisfied by an alias. - my $format=shift; our $indent="0 "; our $height=0; - our @ancestry=(); our @outfile=(); our @missing; my %seen; + our @treewalk=(); our %seen; our @missing; my @seeds=@_; our @results; + our %imark; our %fmark; our $fmarks; my $type=shift; - recurse_deptree($odepends{soft},@_); + # first strip out all the names not found in the ports collections + push @missing, grep { (! $V_REPO{$_}) } @seeds; + @seeds = grep { ($V_REPO{$_}) } @seeds; + + # determine the minimal set of targets needed to satisfy all dependencies + foreach (@seeds) { recurse_deptree($_) } sub recurse_deptree { - my $greedy=shift; my @seeds=@_; my %curdeps; my @optionals; - push @missing, grep { (! $V_REPO{$_}) } @seeds; - @seeds = grep { ($V_REPO{$_}) } @seeds; + my $s=shift; my %curdeps; my @optionals; - foreach my $s (@seeds) { - my $substitute = who_aliased_to($s); - $s = "$substitute (satisfies dependency $s)" if ($substitute); - push @outfile, (${indent}x(1+$height))."$s"; - next if ($substitute); + my $substitute = who_aliased_to($s); + if ((($substitute) and (! $V_INST{$s})) or ($V_REPO{$s})) { + $seen{$s} = 1; + } else { + push (@missing, $s); return; + } + return if ($substitute); - %curdeps = map { $_ => 0 } split /[ ,]/, $DEPENDS{$s}; - # if the user toggles --softdeps, consider only the - # optional dependencies that are already installed - if ($odepends{soft}*$greedy == 1) { + # cycle detection + if (! grep { $_ eq $s } @treewalk) { + push (@treewalk, $s); + } else { return; } + + %curdeps = map { $_ => 0 } split /[ ,]/, $DEPENDS{$s}; + # if the user toggles --softdeps, consider only the + # optional dependencies that are already installed + if ($odepends{soft} == 1) { @optionals = grep { ($V_INST{$_}) } split /[ ,]/, $SOFTDEPS{$s}; foreach (@optionals) { $curdeps{$_} = 1 } - } - - SUCCESSOR: foreach my $sd (keys %curdeps) { - if (grep /^$sd$/, @ancestry) { - if (($greedy|$curdeps{$sd}) == 1) { next SUCCESSOR; - } else { - print "Warning: cyclic dependency found!\n"; - print ((join " => ", @ancestry)." => $sd\n"); - return; - } - } - push (@ancestry,$sd); - $height = 1+$#ancestry; - recurse_deptree($greedy|$curdeps{$sd},$sd); - pop @ancestry; - $height = 1+$#ancestry; - } } - } + + foreach my $sd (keys %curdeps) { + next if ($seen{$sd}); + recurse_deptree($sd); + } + pop (@treewalk); + } - if ($format eq "deptree") { - @outfile = grep { s/0 / /g; !m/^\s*$/; } @outfile; - } else { - @outfile = grep { !$seen{$_}++ } (grep - { s/0 //g; s/ .satisfies dependency .*//g; !m/^\s*$/; } sort(@outfile)); - } - return @outfile if (($#missing < 0) or ($format eq "quickdep")); - return @outfile, "MISSING", @missing if ($#missing >= 0); + # proceed with the topological sort + my $graphsize = keys %seen; $fmarks = 0; + while ($fmarks < $graphsize) { + my @unvisited = grep { ! ($fmark{$_}) } keys %seen; + my $node = pop @unvisited; + visit($node,0); + } + + sub visit { + my $node = shift; my $greedy = shift; + if ( $fmark{$node} ) { return; } + if ( $imark{$node} ) { + print "cycle detected!\n" unless ($greedy==1); + return; + } + $imark{$node} = 1; + foreach my $dep (split /[ ,]/, $DEPENDS{$node}) { visit($dep,$greedy); } + if ( $odepends{soft} == 1 ) { + foreach my $sd (grep { ($V_INST{$_}) } + split /[ ,]/, $SOFTDEPS{$node}) { + visit($sd,1); + } + } + delete $imark{$node}; + $fmark{$node} = 1; + $fmarks = keys %fmark; + push (@results, $node); + } + if (($#missing>-1) and ($type ne "quick")) { push (@results, "MISSING", @missing); } + return @results; } sub up_inst { # returns scalar references to five arrays - my $type=shift; my @requested=@_; my @targets; my %EXEMPT; my %WANTED; my %pdirs; + my $type=shift; my @requested=@_; my @targets; my %EXEMPT; my %pdirs; my %builtpkg; my %mkcmd; my %addcmd; my %status; my %logfile; my %pvars; my $PKGMK=$opkg{makecommand}; my $PKGADD=$opkg{addcommand}; - # resolve all dependencies if the command was not 'grpinst', - # putting glibc{,-32} at the front of the queue - if ($type eq "grpinst") { - @targets=grep { ($V_REPO{$_}) } @requested; - } else { - @targets=grep { !m/^glibc(|-32)$/ } deporder("quickdep", @requested); + # respect the user-supplied list of ports unless 'depinst' or 'sysup', + # in which case put glibc{,-32} at the front of the queue + if ($type =~ /^(depinst|sysup)$/) { + @requested = deporder("quick",@requested); + @targets=grep { !m/^glibc(|-32)$/ } @requested; unshift @targets, grep { m/^glibc(|-32)$/ } @requested; + } else { + @targets=grep { ($V_REPO{$_}) } @requested; } - # exempt any locked ports from an update operation + # exempt any locked ports unless they were explicitly requested %EXEMPT = map { $_ => 1 } @LOCKED; - %WANTED = map { $_ => 1 } @requested; - if ($action =~ /^(update|install|depinst)$/) { - @targets = grep {(! $EXEMPT{$_}) or ($WANTED{$_})} @targets; + if ($type =~ /^(depinst|sysup)$/) { + @targets = grep {(! $EXEMPT{$_})} @targets; } # first determine the directories from which pkgmk must be called, @@ -700,7 +753,7 @@ sub up_inst { # returns scalar references to five arrays my ($COMPRESSION, $PKG_DIR) = parse_pkgmk_conf(); foreach my $t (@targets) { $opkg{$t} = $opkg{margs}; $pvars{'%n'}=$t; - $opkg{$t} =~ s/-f// unless ($WANTED{$t}); + $opkg{$t} =~ s/-f// unless (grep { $_ eq $t } @requested); $pvars{'%p'} = find_port_by_name($t,1,1,0); $pdirs{$t} = $pvars{'%p'}; ($pvars{'%v'}, $pvars{'%r'}) = (get_pkgfile_fields($pvars{'%p'}))[0,1]; $builtpkg{$t} = ($PKG_DIR) ? "$PKG_DIR/$t#$pvars{'%v'}-$pvars{'%r'}.pkg.tar.$COMPRESSION" : "$pvars{'%p'}/$t#$pvars{'%v'}-$pvars{'%r'}.pkg.tar.$COMPRESSION"; @@ -716,40 +769,47 @@ sub up_inst { # returns scalar references to five arrays } } + if ($opkg{test} eq "yes") { print("*** prt-auf $type: test mode\n\n"); + print "-- Packages changed\n"; } + # build each package, unless already installed or satisfied by an alias - foreach my $t (@targets) { + BUILDLOG: foreach my $t (@targets) { if (who_aliased_to($t)) { $mkcmd{$t} = "echo \"skipped build ($t provided by an alias)\""; } else { - $mkcmd{$t} = "echo \"skipped build ($t up to date)\"" if ((-f $builtpkg{$t}) and - ((-M $builtpkg{$t}) < (-M "$pdirs{$t}/Pkgfile")) and ($opkg{$t} !~ /-f/)); - $mkcmd{$t} = "" if ((port_diff("utd",$t)==0) and !($WANTED{$t})); - $mkcmd{$t} = "" if (($V_INST{$t}) and ($type =~ /^(install|depinst)$/) and ($opkg{$t} !~ /-f/)); + $mkcmd{$t} = "echo \"skipped build ($t up to date)\"" if + ((-f $builtpkg{$t}) and ($opkg{$t} !~ /-f/)) + and ((-M $builtpkg{$t}) < (-M "$pdirs{$t}/Pkgfile")); + $mkcmd{$t} = "" if (($opkg{$t} !~ /-f/) and ($V_REPO{$t} eq $V_INST{$t})); } - if ($mkcmd{$t}) { + if ($opkg{test} eq "yes") { + print("$t\n") if ($mkcmd{$t} ne ""); + next BUILDLOG; + } + if ($mkcmd{$t} ne "") { if ((-f "$pdirs{$t}/pre-install") and ($opkg{runscripts} eq "yes")) { - system("sh","$pdirs{$t}/pre-install") unless ($opkg{test} eq "yes"); + system("sh","$pdirs{$t}/pre-install"); $status{$t} .= ( $?>>8 == 0 ) ? "pre-install ok. " : "pre-install failed. "; } - ($opkg{test} eq "no") ? chdir $pdirs{$t} : print "cd $pdirs{$t}\n"; - ($opkg{test} eq "no") ? system("$mkcmd{$t}") : print "$mkcmd{$t}\n"; + chdir $pdirs{$t}; system("$mkcmd{$t}"); $status{$t} .= ( $?>>8 == 0 ) ? "build ok. " : "build failed. " unless ($logfile{$t}); $status{$t} .= (! log_failure($logfile{$t})) ? "build ok. " : "build failed. " if ($logfile{$t}); $status{$t} = ( $mkcmd{$t} =~ /skipped/ ) ? "build skipped. " : $status{$t}; if (($status{$t} =~ /build ok/) or ($mkcmd{$t} =~ /up to date/)) { $addcmd{$t} =~ s/ -u / / if (port_diff("utd",$t)<0); - ($opkg{test} eq "no") ? system("$addcmd{$t}") : print "$addcmd{$t}\n"; + system("$addcmd{$t}"); $status{$t} .= ( $?>>8 == 0 ) ? "pkgadd ok. " : "pkgadd failed. "; - unlink($logfile{$t}) if (($logfile{$t}) and ($olog{rm_on_success} eq "yes")); + unlink($logfile{$t}) if (($status{$t} =~ /ok. $/) and + ($logfile{$t}) and ($olog{rm_on_success} eq "yes")); } if (($status{$t} =~ /pkgadd ok/) and (-f "$pdirs{$t}/post-install") and ($opkg{runscripts} eq "yes")) { - system("sh","$pdirs{$t}/post-install") unless ($opkg{test} eq "yes"); + system("sh","$pdirs{$t}/post-install"); $status{$t} .= ( $?>>8 == 0 ) ? "post-install ok. " : "post-install failed. "; } } - last if (($status{$t} =~ /failed/) and ($type eq "grpinst")); + last if (($status{$t} =~ /failed/) and ($opkg{group} eq "yes")); } sub log_failure { @@ -759,24 +819,14 @@ sub up_inst { # returns scalar references to five arrays close(FH); return $failed; } - my @ok = grep { $status{$_} =~ /pkgadd ok/ } @targets; - my @ok_pre = grep { $status{$_} =~ /pre-install ok/ } @targets; - my @ok_post = grep { $status{$_} =~ /post-install ok/ } @ok; - my @ok_readme = grep -f $pdirs{$_}."/README", @ok; my @not_ok = grep { $status{$_} =~ /(pkgadd|build) failed/ } @targets; + my @ok = grep { $status{$_} !~ /(pkgadd|build) failed/ } @targets; + my @ok_pre = grep { $status{$_} !~ /pre-install failed/ } @targets; + my @ok_post = grep { $status{$_} !~ /post-install failed/ } @ok; + my @ok_readme = grep -f $pdirs{$_}."/README", @ok; return \@ok, \@ok_pre, \@ok_post, \@ok_readme, \@not_ok; } -sub sysup { - my @targets; my $v_repo; - foreach my $p (keys %V_INST) { - next if grep /^$p$/, @LOCKED; - $v_repo = ($V_REPO{$p}) ? $V_REPO{$p} : "MISSING" ; - push @targets, $p if (($v_repo ne $V_INST{$p}) and ($v_repo ne "MISSING")); - } - return up_inst("sysup",@targets); -} - sub parse_pkgmk_conf { my $CONF="/etc/pkgmk.conf"; my $COMPRESSION; my $PKG_DIR=""; @@ -850,9 +900,8 @@ DIFFERENCES / DEPENDENCIES dependent <port> show installed packages which depend on <port> INSTALL, UPDATE and REMOVAL - install [opt] <port1 port2...> install ports and their dependencies - update [opt] <port1 port2...> update ports and their dependencies - grpinst [opt] <port1 port2...> install these ports, do not resolve dependencies + install [opt] <port1 port2...> install/update ports in the listed order + depinst [opt] <port1 port2...> install/update ports and their dependencies remove [opt] <port1 port2...> remove ports lock <port1 port2...> lock each <port> at its current version unlock <port1 port2...> release the lock on each <port> @@ -876,6 +925,7 @@ COMMON OPTIONS --path print path to port if appropriate (search, list, depends) --regex treat search term as a Perl-compatible regular expression --cache use a cache file + --group stop the install/update operation if any port fails --test do not actually run pkgmk/pkgadd, just print the commands on STDOUT EOF exit;