From 985bb342e7107a8110b8d29fba7f6242d9a3a43c Mon Sep 17 00:00:00 2001 From: John McQuah Date: Fri, 10 Jun 2022 11:17:22 -0400 Subject: [PATCH] prt-auf: initial commit --- man8/prt-auf.8 | 518 ++++++++++++++++++++++++++++++ scripts/prt-auf | 813 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1331 insertions(+) create mode 100644 man8/prt-auf.8 create mode 100755 scripts/prt-auf diff --git a/man8/prt-auf.8 b/man8/prt-auf.8 new file mode 100644 index 0000000..6370543 --- /dev/null +++ b/man8/prt-auf.8 @@ -0,0 +1,518 @@ +.\" man page for prt-auf +.\" last edited June 2022 by John McQuah, jmcquah at disroot dot org +.\" +.\" based on original work by Johannes Winkelmann, jw at tks6 dot net +.\" +.\" .PU +.TH "prt-auf" "8" "" "" "" +.SH "NAME" +.LP +prt\-auf \- add/upgrade frontend to the CRUX pkgutils designed to imitate \fBprt\-get\fP(8) + +(see http://www.crux.nu for an overview of CRUX ports and pkgutils) +.SH "SYNOPSIS" +.B prt\-auf subcommand [options] +.br +.SH "DESCRIPTION" +prt\-auf is a frontend to the CRUX pkgutils, orchestrating their operation +behind the scenes and letting the user focus on higher-level objectives. It +scans both the local ports tree and the installed package database, to +resolve dependency relationships and to determine which installed packages +are out of date. \fBprt\-auf\fP is especially relevant when you want to: + +.PP +.TP +\ \ \ \(bu +add/update a package without first determining where in the ports tree +its build instructions and dependencies are located + +.TP +\ \ \ \(bu +pass multiple packages on one command line for an add/update operation + +.TP +\ \ \ \(bu +show all the dependencies that would be needed by a set of packages + +.TP +\ \ \ \(bu +search for ports by name, by description, or by the files they provide + +.TP +\ \ \ \(bu +show the upstream url or the maintainer contact information + +.PP + +\fBprt\-auf\fP basically serves as an intermediary between your high-level +objectives and the specific calls to pkgmk, pkgadd, and pkgrm that would +achieve them. prt\-auf will search for the necessary information itself in +all the port collections specified in its config file. This allows you to +just request a package for installation, without caring where it actually is +located on your file system. prt\-auf was inspired by \fBprt\-get\fP(8) and +offers an essentially identical user experience. + +.PP + +prt\-auf lets you search for ports by name, find information about ports +(without installing them of course), or print the dependencies of a port (as +a space- or newline-separated list, or with indentation to represent the +tree structure). Note that prt\-auf trusts the port maintainer to provide an +accurate list of dependencies; if this list is incomplete for any of the +ports in your collections, the build might fail. + +.PP + +prt\-auf has a test mode so you can see what effect an install/update +operation would have. Use the \-\-test switch for this (more details in +the \fBOPTIONS\fP section below). + +.SH "RETURN VALUE" +Calling prt\-auf within a shell script sometimes requires you to check its +exit status. Like most Unix tools, prt\-auf returns 0 on success and a +non-zero value otherwise. A typical usage is: +.B if prt\-auf isinst $SOME_PORT; then $TAKE_THIS_ACTION; fi + +.SH "SUBCOMMANDS" +prt\-auf uses so\-called subcommands, which always have to be the first +non-option argument passed. This is very similar to +.B git(1). +[subcommand] can be one of the following: + +.TP +.B install [\-\-margs=] [\-\-aargs=] [ ...] +install/update all packages and their dependencies. Any currently-installed +dependency is left at its current version unless explicitly given on the command +line, in which case prt-auf will bring it up to date. If there have been major +version changes in shared libraries since your last update, it might be advisable to +run 'prt-auf update' instead. + +.TP +.B update [\-\-margs=] [\-\-aargs=] [ ...] +bring all the listed packages and their dependencies up to date. Among 'install', 'update', +and 'grpinst', this action is the most permissive, exempting from updates only the locked +ports in the dependency chain. You should use 'install' instead if you only want to build +the missing dependencies. + +.TP +.B grpinst [\-\-margs=] [\-\-aargs=] [ ...] +install/update all packages in the listed order, but stop if pkgmk or pkgadd was +unsuccessful. 'prt-auf grpinst' can be used to override the automatic dependency resolution. +Among 'install', 'update', and 'grpinst', this action is the most literal, building only the +requested packages and no others. Yet 'grpinst' is still smart enough to call \fBpkgadd\fP(8) +with the '-u' switch, if one of the packages passed as argument is already installed. + +.TP +.B remove [\-\-rargs=] [ ...] +remove packages listed in this order. The only relevant option you can might want to pass to +\fBpkgrm\fP(8) is --root (or -r), used when you want to manage a CRUX installation on a temporarily +mounted filesystem. In order not to confuse the argument parser (which splits on whitespace), +you should format such a request as +.B prt\-auf remove \-\-rargs=\-\-root=/path/to/mounted/crux [ ...] +and \fBprt\-auf\fP will clean up the -r switch so that \fBpkgrm\fP(8) does what you want. + +.TP +.B sysup +Update all installed packages which are outdated. + +.TP +.B lock +and +.B unlock +commands to keep the currently installed versions, even if there are +changes in the ports tree. + +.TP +.B lock [...] +Do not update these packages in a +.B sysup +operation + +.TP +.B unlock [...] +Remove lock from these packages + +.TP +.B listlocked +List names of packages which are locked. + +.TP +.B diff [--all] +show differences between installed packages and ports in the ports +tree. Locked packages are only displayed if you use the --all switch. + +.TP +.B quickdiff +prints a simple list of packages which have a different version in the +ports tree than what is installed. + +.TP +.B search [\-\-path] [\-\-regex] +Search the ports tree for +.B expr +in their name + +.TP +.B dsearch [\-\-path] [\-\-regex] +Search the ports tree (both name and description) for the pattern +\fBexpr\fP. The search in the description is not case sensitive. Note that +this requires prt\-auf to read every Pkgfile, which makes it rather slow; if +you like this, consider using the cache functionality, so you only have to +spend this time once after updating the ports tree has been updated. + +.TP +.B fsearch [\-\-path] [\-\-regex] +Search the ports tree for file names that match \fBpattern\fP. +Pattern should be a Perl-compatible regular expression (e.g. prt-auf fsearch +--regex 'liblz(o2|ma).*') unless it contains no metacharacters (such as: +, +*, ., / ), in which case you can omit the \-\-regex switch. + +.TP +.B info +Print available info for a port + +.TP +.B path +Print the path of a port + +.TP +.B readme +Print the port's README file if it exists + +.TP +.B depends [ ...] +print a recursive list of dependencies needed to install the packages passed +as argument. It shows a list of the dependencies that were found in the +ports tree, plus a list of the dependencies that could not be found. + +.TP +.B quickdep [ ...] +print a simple list of recursive dependencies for all the packages passed +as argument. The output is formatted to be useful in command substitution, +e.g. instead of running +.B prt\-auf depinst xorg-server +you might obfuscate your intentions with a gratuitous loop: +.B for i in $(prt\-auf quickdep xorg-server); do prt\-auf isinst $i || prt\-auf install $i; done + +Note: output is restricted to those dependencies that can be found in the ports tree. It might be +useful to run +.B prt\-auf depends | grep \(dq\-\- missing packages\(dq +as a first step, in order to ensure that your ports tree has everything needed for successful +builds. + +.TP +.B deptree +print a tree of the dependencies of the package +.B package. + +.SH "" + +Note that soft (optional) dependencies are NOT considered when running +prt\-auf depends, prt\-auf quickdep, or prt\-auf deptree. The port maintainer +often provides a README if significant loss of functionality might result from +not having an optional dependency present when building, so be sure to interpret the output of +.B prt\-auf +in light of the information provided in such a README. + +.TP +.B dependent +print a list of ports which have +.B package +in their "Depends on:" line. As with +.B depends, quickdep, deptree, +the Pkgfile line for soft (optional) dependencies is NOT parsed during this operation, +so the output might omit some of the ports that were linked against +.B package +during compilation. + +By default, output is restricted to ports that are installed. To see all hard dependencies, +add the --all switch; use --recursive to get a recursive list (without duplication), +and --tree to get a nicely indented one (note that --tree implies +--recursive). + +.TP +.B dup +List ports which can be found in multiple directories configured in +.B /etc/prt-get.conf + +.TP +.B list [\-v|\-vv] +List ports available in the ports tree. It's basically the same as +.B ports \-l +but looks in all directories specified in the config file. + +.TP +.B listinst [\-v|\-vv] +List installed ports. It's basically the same as +.B pkginfo \-i, +but omits version when called without verbose (\-v, \-vv) switch. Plus +it is notably faster in my tests. \-v adds version information, \-vv +adds version and description. + +.TP +.B listorphans +List installed ports which do not appear in the "Depends on:" line of +any other port currently installed. Output appears alphabetically separated by newlines, making it +suitable for process substitution as shown in the +.B EXAMPLES +section below. Note that some core ports might be runtime +dependencies despite their absence in the "Depends on:" line; see \fBPkgfile(5)\fP +for an explanation of this practice. Also remember that this operation does NOT account for soft +(optional) dependencies. Removing a non-core package returned by this command might require a +rebuild of other packages; use revdep(1) to locate such breakage. + + +.TP +.B isinst [ ...] +Check whether each package given on the command line is installed. Output in the case of multiple +arguments is separated by newlines, suitable for processing by awk or grep. Similar to +.B pkginfo \-i|grep \-E '^(package1|package2|...)' +but does not print the version information. This command has a return value of 0 if +all packages given as argument are installed, otherwise a return value greater than 0. + +.TP +.B current [ ...] +Shows the currently-installed version of , or a message +that is not installed. Also takes more than one package as +argument. + +.TP +.B ls [--path] +Prints out a listing of the port's directory + +.TP +.B cat [] +Prints out the file to stdout. If is not specified, 'Pkgfile' is used. If set, uses $PAGER. + +.TP +.B edit [] +Edit the file using the editor specified in the $EDITOR environment variable. +If is not specified, 'Pkgfile' is used. + +.TP +.B help +Shows a help screen + +.TP +.B version +Shows the current version of prt\-auf + +.TP +.B cache +create a cache file from the ports tree, which will be used whenever \fBprt\-auf\fP +is invoked with the --cache option. Remember to run \fBprt\-auf cache\fP each time +you update the ports tree, or automate this step by appending a line to the +\fBports\fP(8) script. If you invoke \fBprt\-auf\fP from a symbolic link that ends +in 'cache', \fBprt\-auf\fP will act as if it saw the --cache option on the command +line, so the symbolic link \fBprt\-cache\fP -> \fBprt\-auf\fP will save you the +hassle of typing '--cache' each time. Cache files generated by \fBprt\-auf\fP are +fully compatible with those generated by \fBprt\-get\fP(8). + +.SH "OPTIONS" + +The following options are primarily useful for install/update transactions. + +.TP +.B -fr +Force rebuild, Implies 'pkgmk -f'; same as --margs=-f + +.TP +.B -us +Update signature, implies 'pkgmk -us'; same as --margs=-us + +.TP +.B -is +Ignore signature, implies 'pkgmk -is'; same as --margs=-is + +.TP +.B -uf +Update footprint, implies 'pkgmk -uf'; same as --margs=-uf + +.TP +.B -if +Ignore footprint, implies 'pkgmk -if'; same as --margs=-if + +.TP +.B -ns +No stripping, implies 'pkgmk -ns'; same as --margs=-ns + +.TP +.B -kw +Keep working directory, implies 'pkgmk -kw'; same as --margs=-kw + +.TP +.B \-\-margs="...", e.g. \-\-margs="\-im" +additional arguments to be passed to pkgmk; +note that \-d is already passed to pkgmk anyway + +.TP +.B \-\-aargs="...", e.g. \-\-aargs="\-f" +additional arguments to be passed to pkgadd + +.TP +.B \-\-cache +Use cache file for this command. + +.SH "" + +The following options affect the output of non-install (information-seeking) transactions. + +.TP +.B \-v, \-vv +(verbosity level) Show version of a port (\-v), or show both version +and description (\-vv). Passing more than one of these options is equivalent to \-vv. + +.TP +.B \-\-path +Show path info for the ports found by a search or a dependency calculation + +.TP +.B \-\-regex +Interpret filter and search pattern as regular expression + + +.SH "CONFIGURATION" + +Most of the directives available in prt\-get.conf(5) are also recognized and +respected by \fBprt\-auf\fP. Notably, you can specify the active port +collections by ensuring that they appear on lines beginning with 'prtdir '. +You can also toggle the running of pre-/post-install scripts by editing the +line that contains 'runscripts'. You can specify alternatives to the +default pkgutils programs ( /usr/bin/pkgmk, /usr/bin/pkgadd, and +/usr/bin/pkgrm ) +by editing the lines for 'makecommand', 'addcommand', and 'removecommand', respectively. + +.SH "TECHNICAL DETAILS" + +\fBprt\-auf\fP aims to recreate the familiar experience of \fBprt\-get\fP(8), in a +tidy Perl program that novice CRUX hackers would find less intimidating. By keeping its +inner workings entirely within one file, \fBprt\-auf\fP makes it easier for CRUX newcomers +to understand the architecture of the \fBports\fP(8) system and the \fBpkgutils\fP. + +One intended consequence of the less-intimidating code base is that bug reports and +feature requests can receive the attention of more CRUX users, rather than just the +handful of developers who have C++ experience. In order to facilitate the insertion +of new code to satisfy any feature requests, this section provides an outline of the +\fBprt\-auf\fP design. + +The program begins by declaring all the variables that are shared among subroutines. +Some of these variables are initialized right away, but other variables are only +initialized once the program knows the requested action. + +After all the arguments are parsed (and screened for validity), the hash maps \fI%opkg\fP, +\fI%odepends\fP, and \fI%osearch\fP will retain in memory the user's desired settings. Then the +relevant data structures are populated from the files on disk (the cache, if +\-\-cache was passed on the command line, the database of installed packages in /var/lib/pkg, +the list of locked ports, the list of aliases, or each \fBPkgfile\fP(5) found in the ports tree). + +Control is now passed to the subroutine that satisfies the given request. Many of these +subroutines return a simple array of strings, most notably the subroutions \fIlist_ports()\fP, +\fIdeporder()\fP, and \fIport_diff()\fP. But the \fIup_inst()\fP subroutine returns references +to five different arrays, so that post-processing can provide informative output regarding which +ports were successfully installed, and which ports failed. + +The final section of the main program (post-processing) considers the distinctive output of each +subroutine and customizes the handling of the \fI@results\fP array accordingly. This section is +also where the verbose switch (-v|-vv) is taken into account, appending to each element of +\fI@results\fP the version or description of the ports found in the search. + +Here are some of the deliberate deviations from the behaviour of \fBprt\-get\fP(8), the first of +which was on the TODO list in the prt-get source tree. +.PP +.TP +\ \ \ \(bu mixed install/update mode. Packages given on the command line can be present or not, and +\fBprt\-auf\fP will figure out the right way to call \fBpkgadd\fP(8) for each one. + +.TP +\ \ \ \(bu merged install and depinst. While they still retain some distinguishability in how they +treat a given set of targets, both of them now resolve dependencies by default. The old +behaviour of \fBprt\-get install\fP can be approximated by the 'grpinst' action of \fBprt\-auf\fP. + +.TP +\ \ \ \(bu logging is not handled internally. In the event that stdout is \fInot\fP redirected to a +file, consider using the 'grpinst' action so that error messages remain in the scrollback buffer. +If \fBprt\-auf\fP is used non-interactively (say, in a cron job), then another non-interactive +process can take care of renaming the file where the stdout of \fBprt\-auf\fP was dumped. This +design decision is closer to the spirit of "do one thing and do it well", although \fBprt\-auf\fP +ignores this advice elsewhere by implementing such luxuries as +.B prt\-auf ls, prt\-auf edit, prt\-auf readme +when command substitutions like +.B ls $(prt\-auf path $desired_port) +or +.B vim $(prt\-auf path $desired_port)/Pkgfile +are perfectly cromulent ways to do the same thing. + +.SH "EXAMPLES" +.TP +.B prt\-auf install irssi +Download, build and install irssi, with one simple command + +.TP +.B prt\-auf install paper yasm +Install paper and yasm (and any needed dependencies). + +.TP +.B prt\-auf update bmake cmake +Update bmake and cmake. Abort with an informative error message if either package is not yet +installed, allowing you to issue a revised command. + +.TP +.B prt\-auf update -fr openssh +Update your current version of openssh, forcing a rebuild even if no version +difference is detected. Useful if there was a major version change in one of +its dependencies, and \fBrevdep openssh\fP indicates a broken package. :\-) + +.TP +.B prt\-auf info glib-networking +Show info about glib-networking + +.TP +.B MISSLIBS=$(revdep -vvv mpv | awk -F ':' '/(missing library)/ {print $3}'); [ -n \(dq${MISSLIBS[@]}\(dq ] && for i in ${MISSLIBS[@]}; do prt\-auf fsearch $i; done +(adapted from a script by ppetrov^) Check for the presence of the runtime libraries needed by mpv. +If any are absent, search the footprints to determine which ports provide the missing libraries. + +.TP +.B prt\-auf dsearch irc +Return a list of all ports having "irc" in their name or description + +.TP +.B comm -13 <(ls /usr/ports/core) <(prt-auf listorphans) +(based on comments from Romster and jue) Filter out the core ports from the list of orphans, in +shells (like bash) that support process substitution + +.TP +.B comm -13 <(cat ~/.keepers <(ls /usr/ports/core) | sort) <(prt\-auf listorphans) | xargs prt\-auf remove +(system-hosing extension of the above) A one-liner inspired by \fBpkg\-clean\fP +and \fBpkgfoster\fP, but without the safeguard of interactivity. \fBDo not try this on a +mission-critical system.\fP + +.TP +.B prt\-auf isinst $(prt\-auf quickdep $(prt\-auf quickdiff)) | awk '/not installed/ {print $2}' +(adapted from a comment by Fun) After updating your ports tree, print out a list of dependencies +that were not needed the last time you built your currently-installed ports, but are needed now by +the newer versions of these ports. The output of this command is sorted by dependencies, therefore +suitable for piping to \fBxargs prt\-auf install\fP or \fBxargs prt\-auf grpinst\fP. + +.TP +.B prt\-auf grpinst $(prt\-auf quickdep graphviz) +Installed all packages needed for graphviz . Remember that grpinst stops +installing when one package installation fails. + +.TP +.B prt\-auf listinst | xargs prt\-auf depends | xargs prt-auf grpinst \-\-aargs="\-r=/mnt" +Sort the list of installed packages by dependencies, and then install all +those packages onto a backup filesystem (mounted at /mnt). If you have a +customized pkgadd.conf that you want applied to this operation, either copy +it to /mnt/etc where pkgadd will be looking for it, or pass the additional +option \-\-aargs=\(dq\-c /etc/pkgadd.conf\(dq to the grpinst command. + +.TP +.B prt\-auf list --path --regex '^xorg.*' | grep -v "/usr/ports/xorg" +Show the ports whose names begin with xorg, but which appear outside the xorg port collection. +(At the time of writing, this command returned at least two font ports.) + +.SH "AUTHORS" +John McQuah , based on the prt\-get manpage by +Johannes Winkelmann, and other sources cited inline. +.SH "SEE ALSO" +prt\-get.conf(5), Pkgfile(5), pkgmk(8), pkgadd(8), pkgrm(8), ports(8) diff --git a/scripts/prt-auf b/scripts/prt-auf new file mode 100755 index 0000000..9296baf --- /dev/null +++ b/scripts/prt-auf @@ -0,0 +1,813 @@ +#!/usr/bin/perl +# +# prt-auf --- add/update frontend to CRUX pkgutils (offers mostly the same +# user experience as prt-get except for the slight delay +# entailed by Perl having to compile this file on startup) +# +# distributed under the same license as the pkgutils, +# https://crux.nu/gitweb/?p=tools/pkgutils.git;a=blob_plain;f=COPYING;hb=HEAD +# +use warnings; +use strict; + +################### Initialize global variables ####################### +my $title="prt-auf"; my $version=0.5; +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-get.cache"; my $LOCKED; my %ALIASES; my %DEPENDS; +my @allports; my %V_REPO; my %V_INST; my %DESC; +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 = ( 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" ); + +my @bldirs = parse_prt_conf(); +my @basedirs = @{$bldirs[0]}; my @localports = @{$bldirs[1]}; + +################### Process the given command ######################### +my ($action, @query) = parse_args(@ARGV); + +# load some data structures into memory for the actions that need them +if ($action !~ /^(fsearch|isinst|current)$/) { + @allports = list_ports(); + fill_hashes_from_pkgfiles(); +} +if ($osearch{cache}==1) { fill_hashes_from_cache(); } + +if ($action !~ /^(quickdep|search|dsearch|fsearch|info|dup|readme|cat)$/) { + open (DB, $pkgdb) or die "Could not open package database!\n"; + local $/=""; + while () { $V_INST{$1} = $2 if m/^(.*)\n(.*)-[0-9]+\n/; } + close (DB); +} + +if ($action =~ /^(diff|quickdiff|depends|deptree|install|update|depinst|sysup)$/) { + get_locked_and_aliased(); +} + +############## Branch based on the requested action ################# + +if ($action eq "path") { + @results = find_port_by_name($query[0],1,1,0); +} elsif ($action eq "search") { + @results = find_port_by_name($query[0],0,$osearch{path},1); +} elsif ($action eq "fsearch") { + $hh = find_port_by_file(".footprint", $query[-1]); +} elsif ($action eq "dsearch") { + @results = find_port_by_desc($query[0]); +} elsif ($action eq "info") { + $portpath = find_port_by_name($query[0],1,1,0); + @results = get_pkgfile_fields($portpath,"all") if ($portpath); +} elsif ($action eq "cache") { dump_flat_db(); +} elsif ($action eq "lock") { port_lock(@query); +} elsif ($action eq "unlock") { port_unlock(@query); +} elsif ($action eq "dup") { find_dups(); +} elsif ($action eq "ls") { port_ls($query[0]); +} 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 "remove") { $ind=uninstall(@query); +} elsif ($action eq "sysup") { @results = sysup(); +} elsif ($action =~ /^(install|update|depinst|grpinst)$/) { + @results = up_inst($1,@query); +} elsif ($action =~ /^(isinst|current)$/) { + $ind = port_diff($1,@query); +} elsif ($action =~ /(.*)diff$/) { + $ind = port_diff($1); +} elsif ($action =~ /^list(.*)/) { + @results = list_ports($1); +} elsif ($action eq "help") { print_help(); +} elsif ($action eq "version") { print "$title $version\n"; +} else { printf "Unsupported command '$action'.\n"; } + +#################### Post-transaction reports ####################### +$strf = "%s\n"; +if (($action =~ /^(listinst|listorphans)/) + or (($action eq "dependent") and ($odepends{all}==0))) { + foreach my $result (@results) { + $result .= " $V_INST{$result}" if $osearch{verbose}==1; + $result .= " $V_INST{$result}\n$DESC{$result}\n" if $osearch{verbose}>1; + printf "$strf", $result; + } +} elsif ($action =~ /^(list|search|dsearch|path|dependent)/) { + exit 1 if ($#results < 0); + foreach my $result (@results) { + $result =~ s/.*\/(.*)$/$1/ if (($action ne "path") and ($osearch{path}==0)); + $result .= " $V_REPO{$result}" if $osearch{verbose}==1; + $result .= " $V_REPO{$result}\n$DESC{$result}\n" if $osearch{verbose}>1; + printf $strf, $result; + } +} elsif ($action =~ /^(fsearch)/) { + my %hits = %{$hh}; $strf = "%20s %s\n"; + printf $strf, "Found in", "Matching File"; + foreach my $fh (keys %hits) { + chomp($hits{$fh}); + printf $strf, $fh, (split /\s/, $hits{$fh})[-1]; + } +} elsif ($action =~ /^(diff|quickdiff|current|isinst)$/) { + exit $ind; +} elsif ($action =~ /^(depends|deptree|quickdep)$/) { + print "-- dependencies ([i] = installed, [a] = provided by an alias)\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/ .provided by .*// if ($action eq "deptree"); + $dep = (split / /, $cleandep)[-1]; + next if (($seen{$dep}) and ($odepends{all}==0)); + $seen{$dep}=1; + if ($action ne "quickdep") { + $ind = (grep { $_ eq $dep } @installed) ? "[i]" : "[ ]"; + if ($ind ne "[i]") { + $ind = (who_aliased_to($dep)) ? "[a]" : $ind; + } + } + $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 "%s ", $dep if ($action eq "quickdep"); + } + print "\n" if ($action eq "quickdep"); +} elsif ($action eq "info") { + $strf = "%14s: %s\n"; + exit 1 if ($#results < 0); + my @fields = ("Name", "Repository", "Version", "Release", "Description", + "Dependencies", "URL", "Packager", "Maintainer", + "Readme", "PreInstall", "PostInstall"); + $results[1] =~ s/^(.*)\/.*$/$1/; + for (my $i=0; $i<7; $i++) { + printf $strf, $fields[$i], $results[$i]; + } + printf $strf, $fields[8], $results[8]; +} 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)$/) { + 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 "no") and (@ok)) { + print "Successful ports:\n"; + foreach my $k (@ok) { + $note = (grep /^$k$/, @ok_pre) ? " pre: ok. " : ""; + $note .= (grep /^$k$/, @ok_post) ? " post: ok. " : ""; + $note = ((grep /^$k$/, @ok_pre) or (grep /^$k$/, @ok_post))? "($note)" : ""; + print " $k $note\n"; + } + print "\n"; + } + if (($opkg{test} eq "no") and (@ok_readme)) { + print "Successful ports with README files:\n"; + foreach (@ok_readme) { print " $_\n"; } + print "\n"; + } + if (($opkg{test} eq "no") and (@not_ok)) { + print "Ports with pkgmk/pkgadd failures:\n"; + foreach (@not_ok) { print " $_\n"; } + print "\n"; + } +} else {} + +# Done! + +#################### Begin Subroutines ####################### +sub parse_args { + my @query; + $osearch{cache} = 1 if ($0 =~ /cache$/); + while (my $arg = shift) { + if ($arg =~ /^(search|dsearch|fsearch|path|info|list|dup)$/) { $action = $1; + } elsif ($arg =~ /^(install|update|depinst|grpinst|sysup)$/) { $action = $1; + } elsif ($arg =~ /^(lock|unlock|listlocked|current|isinst)$/) { $action = $1; + } elsif ($arg =~ /^(diff|quickdiff|listinst|listorphans)$/) { $action = $1; + } elsif ($arg =~ /^(depends|deptree|quickdep|dependent)$/) { $action = $1; + } elsif ($arg =~ /^(readme|cat|edit|ls|help|version|cache)$/) { $action = $1; + } elsif ($arg eq "--tree") { $odepends{tree} = 1; $odepends{recursive} = 1; + } elsif ($arg eq "--all") { $odepends{all} = 1; + } elsif ($arg eq "--recursive") { $odepends{recursive} = 1; + } elsif ($arg eq "--path") { $osearch{path} = 1; + } elsif ($arg eq "--regex") { $osearch{regex} = 1; + } elsif ($arg eq "-v") { $osearch{verbose} += 1; + } elsif ($arg eq "-vv") { $osearch{verbose} += 2; + } elsif ($arg eq "--cache") { $osearch{cache} = 1; + } elsif ($arg eq "--test") { $opkg{test} = "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; + } elsif ($arg =~ /^--aargs=(-r|--root)=(.*)/) { $opkg{aargs} .= "$1 $2"; + } elsif ($arg =~ /^--rargs=(-r|--root)=(.*)/) { $opkg{rargs} .= "$1 $2"; + } elsif ($arg =~ /^-/) { + print "'$arg' is not a recognized option.\n"; exit 1; + } else { push (@query, $arg); } + } + if (($#query > -1) and + ($action =~ /^(diff|quickdiff|cache|list|dup|sysup)/)) { + print "warning: $1 takes no arguments; ignoring those given.\n"; + } + if (($#query > 0) and + ($action =~ /^(search|dsearch|fsearch|info|readme|path|ls)$/)) { + print "warning: $1 takes only one argument; ignoring all but the last.\n"; + } + if ((! @query) and + ($action =~ /^(search|dsearch|fsearch|info|readme|path|ls)$/)) { + print "$1 requires an argument.\n"; exit 1; + } + if (($#query != 0) and + ($action =~ /^(deptree|dependent)$/)) { + print "$1 requires exactly one argument.\n"; exit 1; + } + if (($#query < 0) and + ($action =~ /^(install|update|depinst|grpinst|remove)$/)) { + print "$1 requires at least one argument.\n"; exit 1; + } + return $action, @query; +} + +sub parse_prt_conf { + my @basedirs; my @localports; my $runscripts; my $make; my $add; my $remove; + my $conf = "/etc/prt-get.conf"; + + open(PORTS, $conf) or die "could not open $conf"; + while () { chomp; + if ( /^prtdir\s+/ ) { + my $line = $_; + $line =~ s/^prtdir\s+//; #remove the leading directive + $line =~ s/#.*$//; #strip inline comments like this one + $line =~ s/\s+//g; #collapse all whitespace, even if in a path! + if ( $line !~ /:/ ) { + push @basedirs, $line if (-d $line); + } else { + my @a = split(/:/, $line); + my @b = split(/,/, $a[1]); + while ( my $c = pop @b ) { + my $port = $a[0] . "/" . $c; + push @localports, $port if (-d $port); + } + } + } + $opkg{runscripts} = $1 if /^runscripts\s+(.*)#/; + $opkg{makecommand} = $1 if /^makecommand\s+(.*)#/; + $opkg{addcommand} = $1 if /^addcommand\s+(.*)#/; + $opkg{removecommand} =$1 if /^removecommand\s+(.*)#/; + } + close(PORTS); + return \@basedirs, \@localports; +} + +sub find_dups { + my %seen; + foreach my $pp (@allports) { + my $pn = (split /\//, $pp)[-1]; + $seen{$pn}++; + } + my @dups = grep { $seen{$_} > 1 } keys %seen; + foreach my $dup (@dups) { + print "$dup\n" if ($osearch{verbose}==0); + my @hits = grep /\/$dup$/, @allports; + print "$hits[0] > $hits[1]\n" if ($osearch{verbose}==1); + if ($osearch{verbose}>1) { + print "* $dup\n"; + while (my $h=shift(@hits)) { print " $h\n"; } + } + } +} + +sub get_locked_and_aliased { + local $/ = "\n"; + open (AL, $prtalias) or return; + while () { $ALIASES{$1} = $2 if m/^\s*(.*)\s*:\s*(.*)/; } + close (AL); + open (LK, $prtlocker) or return; + while () { $LOCKED .= " $_"; } + close (LK); +} + +sub who_aliased_to { + my $target = shift; + my @substitutes = grep { defined $V_INST{$_} } keys %ALIASES; + @substitutes = grep { $ALIASES{$_} eq $target } @substitutes; + my $who = (@substitutes) ? $substitutes[0] : undef ; + return $who; +} + +sub dump_flat_db { + my $FS; my @pstats; my $p; + open (CACHE,'>',$prtcache) or die "cannot create a new cache file"; + print CACHE "V5\n"; + foreach my $pp (@allports) { + $p = (split /\//, $pp)[-1]; + @pstats = get_pkgfile_fields($pp,"all"); + for (my $ps=0; $ps<=$#pstats; $ps++) { + printf CACHE "%s\n", $pstats[$ps]; + } + } close (CACHE); + print "cache created.\n"; +} + +sub fill_hashes_from_cache { + open (my $cf,$prtcache) or die "cannot use $prtcache as a cache!\n"; + local $/="\n"; my $p; my $deps; + my $ignored=<$cf>; # first line only contains the cache format version + + while (1) { + $p = <$cf>; last unless defined $p; + chomp($p); + $ignored = <$cf>; $V_REPO{$p} = <$cf>; + $ignored = <$cf>; $DESC{$p} = <$cf>; + $deps = <$cf>; + chomp($deps, $DESC{$p}, $V_REPO{$p}); + $DEPENDS{$p} = ($deps ne "") ? $deps : " "; + $DEPENDS{$p} =~ s/,/ /g; + for (my $i=6; $i<13; $i++) { $ignored = <$cf>; } + } + close ($cf); +} + +sub fill_hashes_from_pkgfiles { + foreach my $pp (@allports) { + my $p = (split /\//, $pp)[-1]; + + my ($rver, $rrel, $rdesc, $rdeps) = get_pkgfile_fields($pp); + $V_REPO{$p} = ($rver) ? $rver : "0"; + $DEPENDS{$p} = ($rdeps) ? $rdeps : ""; + $DEPENDS{$p} =~ s/,/ /g; + $DESC{$p} = ($rdesc) ? $rdesc : ""; + } +} + +sub get_pkgfile_fields { + my ($descrip, $url, $maintainer, $packager, $Version, $Release)=('','','','','',0,0); + my ($readme, $preInstall, $postInstall, $Dependencies)=("no","no","no",''); + my $portpath = shift; my $Name = (split /\//, $portpath)[-1]; + my $pkgfile = "$portpath/Pkgfile"; + + $readme = "yes" if (-f "$portpath/README") or (-f "$portpath/README.md"); + $preInstall = "yes" if (-f "$portpath/pre-install"); + $postInstall = "yes" if (-f "$portpath/post-install"); + $portpath =~ s/\/[a-zA-Z0-9]+$//; + + open(PF,$pkgfile) or die "Cannot open $pkgfile for reading!\n"; + while () { + chomp; + if (s/^# Description:\s*(.*)/$1/) { $descrip = $_; } + elsif (s/^# URL:\s*(.*)/$1/) { $url = $_; } + elsif (s/^version=(.*)/$1/) { $Version = $_; } + elsif (s/^release=(.*)/$1/) { $Release = $_; } + elsif (s/^# Depends on:\s*(.*)/$1/) { $Dependencies = $_; } + elsif (s/^# Packager:\s*(.*)/$1/) { $packager = $_; } + elsif (s/^# Maintainer:\s*(.*)/$1/) { $maintainer = $_; } + else {} + } close(PF); + + if (shift) { + return $Name, $portpath, $Version, $Release, $descrip, $Dependencies, $url, + $packager, $maintainer, $readme, $preInstall, $postInstall, $built_pkg; + } else { return $Version, $Release, $descrip, $Dependencies; } +} + +sub find_port_by_file { # for now only used to search footprints, but can be generalized + my $portfile = shift; my $query = shift; my ($lp, $candidate, $fh); my %hits=(); + my $linewanted = qr/$query/is; + LOCALENTRY: foreach $lp (@localports) { + open ($fh, "$lp/$portfile") or die "cannot open $portfile for $lp\n"; + while (<$fh>) { + next if ($portfile eq "Pkgfile" and $_ !~ /^(name=|# Description)/); + next LOCALENTRY if $hits{$lp}; + $hits{$lp} = $_ if $_ =~ $linewanted; + } close ($fh); + } + foreach my $collection (@basedirs) { + my $prefix = ( $osearch{path} == 1 ) ? "$collection/" : ""; + opendir (DIR, $collection) or return; + PORTENTRY: foreach $candidate (sort(readdir(DIR))) { + next if (! -f "$collection/$candidate/$portfile"); + open ($fh, "$collection/$candidate/$portfile") or die "cannot open $portfile in $candidate\n"; + while (<$fh>) { + next PORTENTRY if $hits{"$prefix$candidate"}; + $hits{"$prefix$candidate"}=$_ if $_ =~ $linewanted; + } close ($fh); + } closedir(DIR); + } + return \%hits; +} + +sub find_port_by_desc { + my $query=shift; + $query =~ s/\+/\\\+/g unless ($osearch{regex}==1); + $query =~ s/\./\\\./g unless ($osearch{regex}==1); + my @hits = grep { $DESC{$_} =~ /$query/i } keys %DESC; + return @hits; +} + +sub find_port_by_name { + my $query = shift; my $exact=shift; my $fullpath=shift; my $exhaustive=shift; + $query =~ s/\+/\\\+/g unless ($osearch{regex}==1); + $query =~ s/\./\\\./g unless ($osearch{regex}==1); + my $pattern = ($exact==1) ? qr/\/$query$/s : qr/$query/is; + my @hits = grep { $_ =~ $pattern } @allports; + @hits = grep { s/^(.*)\/// } @hits if ($fullpath==0); + return @hits if ($exhaustive==1); + return $hits[0] if ($exhaustive==0); +} + +sub uninstall { + my $PKGRM = $opkg{removecommand}; + my @targets = grep { defined $V_INST{$_} } @_; + my @rubbish = grep { ! defined $V_INST{$_} } @_; + foreach my $r (@rubbish) { print "$r not installed; ignoring.\n"; } + my %removed = map { $_ => 0 } @targets; + foreach my $t (@targets) { + ($opkg{test} eq "no") ? system($PKGRM,$opkg{rargs},$t) : print "$PKGRM $opkg{rargs} $t\n"; + $removed{$t}=1 if ($?>>8 == 0); + } + return \%removed; +} + +sub port_lock { + my @newlocks = grep { " $LOCKED " !~ / $_ / } @_; + if (@newlocks) { + open (LK,'>>',$prtlocker) or die "cannot open $prtlocker for writing.\n"; + foreach my $lp (@newlocks) { + print LK "$lp\n"; + print STDOUT "$lp locked.\n"; + } close (LK); + } +} + +sub port_unlock { + foreach my $ul (@_) { $LOCKED =~ s/ $ul / /; } + my @newlocks = split / /, $LOCKED; + open (LL, $prtlocker."-tmp",">"); + foreach my $nl (@newlocks) { print LL "$nl\n"; } + close (LL); + system ("mv",$prtlocker."-tmp",$prtlocker); +} + +sub list_ports { + my @found; my $filter = shift; + our $indent="0 "; our $height=0; our @descendants=(); our @outfile; + + if (! $filter) { # empty arg: list all valid ports + foreach my $collection (@basedirs) { + opendir (DIR, $collection) or next; + foreach my $port (sort(readdir DIR)) { + next if (! -f "$collection/$port/Pkgfile"); + push (@found, "$collection/$port"); + } closedir (DIR); + } + foreach my $lp (@localports) { + push (@found, $lp) if (-f "$lp/Pkgfile"); + } + } elsif ($filter eq "inst") { @found = keys %V_INST; + } elsif ($filter eq "locked") { @found=split / /, $LOCKED; + } elsif ($filter =~ /^(orphans|dependent)$/) { + my $seed=shift; + if ( ((! $seed) and ($filter eq "dependent")) or + ((shift @_) and ($filter eq "dependent")) ) { + print "dependent requires exactly one argument.\n"; + return; + } + if (($filter eq "dependent") and (! find_port_by_name($seed,1,1,0))) { + print "$seed not found in the ports tree.\n"; return; + } + + our @searchspace=(($filter eq "orphans") or ($odepends{all}==0)) ? + keys %V_INST : keys %DEPENDS; + @searchspace = grep { defined $DEPENDS{$_} } @searchspace; + + if ($filter eq "orphans") { + my $inst_deps=""; + foreach my $port (@searchspace) { + $inst_deps .= " $DEPENDS{$port} " if ($DEPENDS{$port}); + } + @found = grep { $inst_deps !~ / $_ / } @searchspace; + } elsif (($filter eq "dependent") and ($odepends{recursive}==0)) { + @found = grep { " $DEPENDS{$_} " =~ / $seed / } @searchspace; + } elsif (($filter eq "dependent") and ($odepends{recursive}==1)) { + push @outfile, "$seed"; + + my @children = grep { " $DEPENDS{$_} " =~ / $seed / } @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; + } + } + + 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 } @outfile; + unshift (@found, $seed); + } + # possibilities for the recursive switch have been exhausted + else { } + } # possibilities for the filter have been exhausted + else { } + return @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"; + + if ($dtype !~ /^(current|isinst|utd)/) { + printf "$format", "Port", "Installed", "In Repository" if (! $dtype); + foreach my $p (keys %V_INST) { + next if ((" $LOCKED " =~ / $p /) 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!"); + } + } + printf "\n" if ($dtype eq "quick"); + } elsif ($dtype eq "utd") { + while (my $q=shift(@argq) and $retval==0) { + $retval-- if (! $V_INST{$q}); + $retval++ if (($V_INST{$q}) and ($V_REPO{$q}) and ($V_INST{$q} ne $V_REPO{$q})); + } + } elsif ($dtype =~ /^(current|isinst)$/) { + foreach my $q (@argq) { + if (! $V_INST{$q}) { + print "$q: not installed\n"; $retval++; + } else { + print "$q: version $V_INST{$q}\n" if ($dtype eq "current"); + print "$q is installed.\n" if ($dtype eq "isinst"); + } + } + } else {} + return $retval; +} + +sub deporder { + # returns an indented list if called with first arg "deptree", + # otherwise returns a flattened list, pruned of duplicates. + # Recursion does NOT continue beyond a dependency satisfied by an alias. + + my $format=shift; our $indent="0 "; our $height=0; our @seeds = @_; + our @ancestry=(); our @outfile=(); our @missing; my %installable; my %seen; + + foreach my $s (@seeds) { + if (find_port_by_name($s,1,0,0)) { $installable{$s} = 1; + } else { $installable{$s} = 0; + print "$s not found in the ports tree; ignoring.\n"; + } + } + + foreach my $s (grep { $installable{$_}==1 } @seeds) { + recurse_deptree($s); + } + + sub recurse_deptree { + my $s = shift; + if (! grep /$s$/, @allports) { push @missing, "$s"; return; } + my $substitute = who_aliased_to($s); + my $note = ($substitute) ? " (provided by $substitute)" : ""; + push @outfile, (${indent}x(1+$height))."$s$note"; + + my $depstr = $DEPENDS{$s} unless ($substitute); + if ($depstr) { + my @sdeps = split /[ ,]/, $depstr; + foreach my $sd (@sdeps) { + if (grep /^$sd$/, @ancestry) { + print "Warning: cyclic dependency found!\n"; + print ((join " => ", @ancestry)."$sd\n"); + return; + } + push (@ancestry,$sd); + $height = 1+$#ancestry; + recurse_deptree($sd); + pop @ancestry; + $height = 1+$#ancestry; + } + } + } + if ($format eq "deptree") { + @outfile = grep { s/0 / /g; !m/^\s*$/; } @outfile; + } else { + @outfile = grep !$seen{$_}++, + (grep { s/0 //g; s/ .provided by .*//g; !m/^\s*$/; } sort(@outfile)); + } + return @outfile unless ($#missing >= 0); + return @outfile, "MISSING", @missing if ($#missing >= 0); +} + +sub up_inst { # returns scalar references to five arrays; + my $type=shift; my @requested=@_; + my $PKGMK=$opkg{makecommand}; my $PKGADD=$opkg{addcommand}; + my $SUDO="/usr/bin/doas"; my $FAKEROOT="/usr/bin/fakeroot"; + + $SUDO = (-x $SUDO) ? $SUDO : "/usr/bin/sudo"; + $SUDO = (-x $SUDO) ? $SUDO : "/usr/bin/su -c"; + $FAKEROOT = (-x $FAKEROOT) ? $FAKEROOT : "/usr/bin/su -c"; + + # prepend commands with sudo/doas/fakeroot if the effective user id is not root + if ($> != 0) { $PKGADD = "$SUDO $PKGADD"; + $PKGMK = "$FAKEROOT $PKGMK"; } + + # resolve all dependencies unless the command was 'grpinst' + my @targets=($type eq "grpinst") ? @_ : deporder("quickdep",@_); + + # filter out the invalid ports if deporder() didn't do so already + @targets = grep { (join " ", @allports) =~ / $_ / } @targets if ($type eq "grpinst"); + + # exempt any locked ports from an sysup operation + if ($action eq "sysup") { @targets = grep {" $LOCKED " !~ / $_ /} @targets; + } else { + @targets = grep {(" $LOCKED " !~ / $_ /) or (grep /$_/, @requested)} @targets; + } + + # first determine the directories from which pkgmk must be called + # and the package that will appear after a successful build + my %pdirs; my %builtpkg; my %mkcmd; my %addcmd; my %status; + foreach my $t (@targets) { + $opkg{$t} = $opkg{margs}; + $opkg{$t} =~ s/-f// if !(grep /$t/, @requested); + $pdirs{$t} = find_port_by_name($t,1,1,0); + $builtpkg{$t} = find_built_pkg($t); + $mkcmd{$t} = "$PKGMK -d $opkg{$t}"; + $addcmd{$t} = "$PKGADD -u $builtpkg{$t}"; + $status{$t} = "not done"; + } + + # build each package, unless already installed or satisfied by an alias + foreach my $t (@targets) { + if (who_aliased_to($t)) { + $mkcmd{$t} = "echo \"skipped build ($t provided by an alias)\""; + } else { + $mkcmd{$t} = "" if ((port_diff("utd",$t)==0) and ($opkg{margs} !~ /-f/)); + $mkcmd{$t} = "" if (($type eq "install") and ($V_INST{$t}) and ($opkg{margs} !~ /-f/)); + $mkcmd{$t} = "echo \"skipped build ($t up to date)\"" if ((-f $builtpkg{$t}) and + ((-M $builtpkg{$t}) < (-M "$pdirs{$t}/Pkgfile"))); + } + + if ($mkcmd{$t}) { + if ((-f "$pdirs{$t}/pre-install") and ($opkg{runscripts} eq "yes")) { + system("sh","$pdirs{$t}/pre-install") unless ($opkg{test} eq "yes"); + $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"; + $status{$t} .= ( $?>>8 == 0 ) ? "build ok. " : "build failed. "; + $status{$t} = ( $mkcmd{$t} =~ /skipped/ ) ? "build skipped. " : $status{$t}; + if ($status{$t} =~ /build ok/) { + $addcmd{$t} =~ s/ -u / / if (port_diff("utd",$t)<0); + ($opkg{test} eq "no") ? system("$addcmd{$t}") : print "$addcmd{$t}\n"; + $status{$t} .= ( $?>>8 == 0 ) ? "pkgadd ok. " : "pkgadd failed. "; + } + 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"); + $status{$t} .= ( $?>>8 == 0 ) ? "post-install ok. " : "post-install failed. "; + } + } + + last if (($status{$t} =~ /failed/) and ($type eq "grpinst")); + } + + 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; + return \@ok, \@ok_pre, \@ok_post, \@ok_readme, \@not_ok; +} + +sub sysup { + my @targets; my $v_repo; + foreach my $p (keys %V_INST) { + $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("update",@targets); +} + +sub find_built_pkg { + my $target = shift; my $CONF="/etc/pkgmk.conf"; local $/="\n"; + my $COMPRESSION; my $PKG_DIR; my $portpath = (find_port_by_name($target,1,1,0))[0]; + my ($version, $release) = (get_pkgfile_fields($portpath))[0..1]; + + open (CF,$CONF) or return; + while () { + $COMPRESSION=$1 if m/^PKGMK_COMPRESSION_MODE=(.*)/; + $PKG_DIR=$1 if m/^PKGMK_PACKAGE_DIR=(.*)/; + } close (CF); + + if ($COMPRESSION) { + $COMPRESSION =~ s/"//g; + $COMPRESSION =~ s/'//g; + } else { $COMPRESSION = "gz"; } + + if ($PKG_DIR) { + $PKG_DIR =~ s/"//g; $PKG_DIR =~ s/'//g; + $PKG_DIR =~ s/\$\{name\}/$target/g; + $PKG_DIR =~ s/\$name/$target/g; + } else { $PKG_DIR = $portpath; } + + return "$PKG_DIR/$target#$version-$release.pkg.tar.$COMPRESSION"; +} + +sub port_ls { + my $port=shift; my $pp=find_port_by_name($port,1,1,0); + return if (! defined $pp); + opendir (DIR,$pp) or die "Cannot open $pp for directory listing!\n"; + foreach my $l (sort(readdir(DIR))) { + next if (($l eq ".") or ($l eq "..")); + print "$l\n"; + } closedir (DIR); +} + +sub port_edit { + my $type=shift; my $port=shift; + my $file=shift; my $pp=find_port_by_name($port,1,1,0); + return if (! defined $pp); + my $EDITOR = ($ENV{EDITOR}) ? $ENV{EDITOR} : "/usr/bin/vi"; + + if ($type eq "readme") { + port_edit("cat",$pp,"README") if (-f "$pp/README"); + port_edit("cat",$pp,"README.md") if (-f "$pp/README.md"); + } + + if (($file) and (-f "$pp/$file") and ($type eq "edit")) { + exec ($EDITOR,"$pp/$file"); + } else { exec ($EDITOR,"$pp/Pkgfile"); } + + if (($file) and (-f "$pp/$file") and ($type eq "cat")) { + open (PF, "$pp/$file") or die "Could not open $pp/$file.\n"; + while () { print $_ ; } + close (PF); + } else { + open (PF, "$pp/Pkgfile") or die "Could not open $pp/Pkgfile.\n"; + while () { print $_ ; } + close (PF); + } +} + +sub print_help { + print < [options] +where the actions include: + +SEARCH + search show port names containing + dsearch show ports containing in the name or description + fsearch show ports that provide filenames matching + +DIFFERENCES / DEPENDENCIES + quickdiff show outdated packages on a single line, separated by spaces + quickdep show the dependencies needed by , on a single line + deptree show dependency tree for + dependent show installed packages which depend on + +INSTALL, UPDATE and REMOVAL + install [opt] install ports and their dependencies + update [opt] update ports and their dependencies + remove [opt] remove ports + +GENERAL INFORMATION + list ports in the active repositories + listinst ports currently installed + listorphans installed ports that no other port claims as a hard dependency + dup ports that appear more than once in the active collections + info version, description, dependencies of + path location from which pkgmk would be called to build + ls the files in the directory + cat the contents of / on STDOUT + isinst whether port is installed + current installed version of port + +SYSTEM UPDATE + sysup update all outdated ports, except those that are locked + lock lock each at its current version + unlock release the lock on each + listlocked list locked ports + +COMMON OPTIONS + -v show version in listing + -vv show version and decription in listing + --path print path to port if appropriate (search, list, depends) + --cache use a cache file + --test do not actually run pkgmk/pkgadd, just print the commands on STDOUT +EOF +}