diff options
-rw-r--r-- | misc/porteasy/Makefile | 2 | ||||
-rw-r--r-- | misc/porteasy/src/porteasy.8 | 128 | ||||
-rw-r--r-- | misc/porteasy/src/porteasy.pl | 523 | ||||
-rw-r--r-- | ports-mgmt/porteasy/Makefile | 2 | ||||
-rw-r--r-- | ports-mgmt/porteasy/src/porteasy.8 | 128 | ||||
-rw-r--r-- | ports-mgmt/porteasy/src/porteasy.pl | 523 |
6 files changed, 952 insertions, 354 deletions
diff --git a/misc/porteasy/Makefile b/misc/porteasy/Makefile index 640577dc2672..ea9d553030ed 100644 --- a/misc/porteasy/Makefile +++ b/misc/porteasy/Makefile @@ -8,7 +8,7 @@ # PORTNAME= porteasy -PORTVERSION= 1.7 +PORTVERSION= 2.0 CATEGORIES= misc MASTER_SITES= # none DISTFILES= # none diff --git a/misc/porteasy/src/porteasy.8 b/misc/porteasy/src/porteasy.8 index f142e13e0bab..33b70ca77abe 100644 --- a/misc/porteasy/src/porteasy.8 +++ b/misc/porteasy/src/porteasy.8 @@ -35,7 +35,7 @@ .Nd fetch and build ports .Sh SYNOPSIS .Nm porteasy -.Op Fl abcefhikluVv +.Op Fl abCceFfhikluVv .Op Fl D Ar date .Op Fl d Ar dir .Op Fl p Ar dir @@ -50,18 +50,25 @@ automatically, keeping track of dependencies. The following options are available: .Bl -tag -width Fl .It Fl a -Use the FreeBSD project's anoncvs server as CVS root. +Use the FreeBSD project's anoncvs server as CVS root directory. .It Fl b Build the selected ports. +.It Fl C +Don't clean port directories after building. .It Fl c Clean the selected ports. .It Fl D Ar date -Specify a date to use for CVS operations. +Specify a date to use for +.Xr cvs 1 +operations. .It Fl d Ar dir Specify the package database directory (normally .Pa /var/db/pkg ) . .It Fl e Deselect ports that are already installed. +.It Fl F +Force installation and registration, even if the port is already +installed. .It Fl f Fetch the selected ports. .It Fl h @@ -78,9 +85,12 @@ Specify the ports directory (normally .It Fl r Ar dir Specify the CVS root directory. .It Fl t Ar tag -Specify a tag to use for CVS operations. +Specify a tag to use for +.Xr cvs 1 +operations. .It Fl u -Update all necessary files using CVS. +Update all necessary files using +.Xr cvs 1 . .It Fl V Show the .Nm @@ -88,6 +98,18 @@ version number and exit. .It Fl v Verbose mode: show more information about what is being done. .El +.Ss Port names +The port names listed on the command line may be either unqualified or +fully qualified. +A fully qualified port name is the path to the port directory relative +to the root of the ports tree (i.e. the port's category and name +separated by a slash). +An unqualified port name is the name of the package built by the +intended port, or part of that name. +.Pp +Unqualified names need to be looked up in the ports index, which is +usually slightly out of date, so fully qualified names should be used +whenever possible. .Ss Sequence of operation This section describes the operations performed by .Nm @@ -96,42 +118,45 @@ and the order in which they are performed. .It Update index If the .Fl u -option was specified, the index file is updated from CVS. +option was specified and some unqualified port names were listed on +the command line, the index file is updated using +.Xr cvs 1 . .It Select ports -The ports listed on the command line are looked up in the index, using -simple heuristics to identify incompletely named ports. +The selection list is initialized with the ports listed on the command +line, marked as explicit dependencies. +Any unqualified names are looked up in the index, using simple +heuristics to identify incompletely named ports. If a certain match is not found, .Nm prints a list of possible matches and exits. .Pp All direct and indirect dependencies of the ports listed on the command line are also selected and marked as dependencies. +.It Update ports tree and discover dependencies +If the +.Fl u +option was specified, the port directories for all selected ports are +updated using +.Xr cvs 1 . +Each selected port's Makefile is scanned to discover dependencies, +which are in turn selected and marked as implicit dependencies. +This process is repeated until no new dependencies are found. +.It Deselect installed ports .Pp If the .Fl e option was specified, .Nm -then checks to see if any of the required ports are already installed; +checks to see if any of the selected ports are already installed; those that are are deselected. -.Pp -Selected ports that were specified on the command line are marked as -explicitly selected, while dependencies are marked as implicitly -selected. +This process is not very accurate, as it will not detect if an older +or alternate version of a selected port is installed. .It List selected ports If the .Fl l -option was specified, all selected ports are listed. +option was specified, the fully qualified name and package name of all +selected ports are listed. Explicitly selected ports are indicated with a star. -.It Update ports tree -If the -.Fl u -option and at least one of the -.Fl b , -.Fl f , -.Fl i -or -.Fl k -options was specified, all selected ports are updated from CVS. .It Describe selected ports If the .Fl i @@ -168,7 +193,7 @@ options was specified, runs the .Sq fetch target on every selected port. -.It Install ports +.It Build, install, package, clean ports If one or both of the .Fl f or @@ -177,45 +202,36 @@ options were specified, .Nm runs the .Sq install -target on every explicitly selected port. -Dependencies are handled by the ports system. -.It Build packages -If the -.Fl k -option was specified, -.Nm -runs the -.Sq package -target on every explicitly selected port. -Dependencies are handled by the ports system. -.It Clean the tree (again) -If one or both of the -.Fl f or -.Fl k -options were specified, -.Nm -finally runs the +.Sq package +target, followed by the .Sq clean -target on every selected port once it is -installed and (optionally) its package has been built. +target (unless the +.Fl C +option was specified), on every explicitly selected port. +.Nm +lets the ports system handle dependencies on its own, since the +reported dependencies are sometimes too inclusive. .El .Sh IMPLEMENTATION NOTES There may be a significant difference between what ports are selected (and listed if the .Fl l option is specified) and what ports are actually installed and/or have -packages built for them, since: -.Bl -bullet -.It -the index file lists all dependencies, including ones that are -conditional on system configuration or build-time options. -.It -implicitly selected ports that are already installed, or somehow pass -the dependency check (e.g. because an alternate, equivalent port has -been installed) will be passed over by the ports system, as indeed -they should. -.El +packages built for them, since implicitly selected ports that are +already installed, or somehow pass the dependency check (e.g. because +an alternate, equivalent port has been installed) will be passed over +by the ports system, as indeed they should. +.Pp +.Nm +tries to minimize the number of times +.Xr cvs 1 +is invoked, since the overhead involved in connecting to a remote +server is usually quite high (and the user might have to type a +password every time), but prefers correctness to performance. +The maximum number of invocations is (2 + NC + NP), where NC and NP +are the number of distinct categories and ports (including master +directories and dependencies). .Sh FILES .Nm maintains and operates on a ports tree, normally diff --git a/misc/porteasy/src/porteasy.pl b/misc/porteasy/src/porteasy.pl index eb83b9d631d3..4800cdb13b9c 100644 --- a/misc/porteasy/src/porteasy.pl +++ b/misc/porteasy/src/porteasy.pl @@ -30,19 +30,22 @@ # use strict; -use Data::Dumper; use Fcntl; use Getopt::Long; -my $VERSION = "1.7"; +my $VERSION = "2.0"; # Constants sub ANONCVS_ROOT { ":pserver:anoncvs\@anoncvs.FreeBSD.org:/home/ncvs" } sub REQ_EXPLICIT { 1 } sub REQ_IMPLICIT { 2 } +sub REQ_MASTER { 4 } + +sub PATH_CVS { "/usr/bin/cvs" } +sub PATH_LDCONFIG { "/sbin/ldconfig" } +sub PATH_MAKE { "/usr/bin/make" } # Global parameters -my $cvs = "/usr/bin/cvs"; # CVS command my $dbdir = "/var/db/pkg"; # Package database directory my $index = undef; # Index file my $portsdir = "/usr/ports"; # Ports directory @@ -55,8 +58,10 @@ my $clean = 0; # Clean ports my $cvsroot = 0; # CVS root directory my $exclude = 0; # Do not list installed ports my $fetch = 0; # Fetch ports +my $force = 0; # Force package registration my $help = 0; # Show help text my $info = 0; # Show port info +my $dontclean = 0; # Don't clean after build my $packages = 0; # Build packages my $list = 0; # List ports my $build = 0; # Build ports @@ -68,8 +73,9 @@ my $ecks = 0; # The undocumented option. # Global variables my %ports; # Maps ports to their directory. my %strop; # Inverse of the above map -my %dependencies; # Maps ports to their dependency lists. my %reqd; # Ports that need to be installed +my %installed; # Ports that are already installed +my $suppressed; # Suppress output # # Shortcut for 'print STDERR' @@ -81,7 +87,7 @@ sub stderr(@) { # # Similar to err(3) # -sub err($$@) { +sub bsd::err($$@) { my $code = shift; # Return code my $fmt = shift; # Format string my @args = @_; # Arguments @@ -96,7 +102,7 @@ sub err($$@) { # # Similar to errx(3) # -sub errx($$@) { +sub bsd::errx($$@) { my $code = shift; # Return code my $fmt = shift; # Format string my @args = @_; # Arguments @@ -109,33 +115,121 @@ sub errx($$@) { } # +# Similar to warn(3) +# +sub bsd::warn($@) { + my $fmt = shift; # Format string + my @args = @_; # Arguments + + my $msg; # Error message + + $msg = sprintf($fmt, @args); + stderr("$msg: $!\n"); +} + +# +# Similar to warnx(3) +# +sub bsd::warnx($@) { + my $fmt = shift; # Format string + my @args = @_; # Arguments + + my $msg; # Error message + + $msg = sprintf($fmt, @args); + stderr("$msg\n"); +} + +# +# Call the specified sub with output suppressed +# +sub suppress($@) { + my $subr = shift; # Subroutine to call + my @args = @_; # Arguments + + my $oldsuppressed; # Old suppress flag + my $rtn; # Return value + + if (!$verbose) { + $oldsuppressed = $suppressed; + $suppressed = 1; + } + $rtn = &{$subr}(@args); + if (!$verbose) { + $suppressed = $oldsuppressed; + } + return $rtn; +} + + +# # Print an info message # -sub info($) { +sub info(@) { + + my $msg; # Message + if ($verbose) { - chomp($_[0]); - stderr(">>> $_[0]\n"); + $msg = join(' ', @_); + chomp($msg); + stderr("$msg\n"); } } # +# Print an info message about a subprocess +# +sub cmdinfo(@) { + info(">>>", @_); +} + +# # Change working directory # sub cd($) { my $dir = shift; # Directory to change to - info("cd $dir"); + cmdinfo("cd $dir"); chdir($dir) - or err(1, "unable to chdir to %s", $dir); + or bsd::err(1, "unable to chdir to %s", $dir); } # -# Run a command using system() +# Run a command and return its output # -sub cmd(@) { - - info(join(" ", @_)); - return (system(@_) == 0); +sub cmd($@) { + my $cmd = shift; # Command to run + my @args = @_; # Arguments + + my $pid; # Child pid + local *PIPE; # Pipe + my $output; # Output + + cmdinfo(join(" ", $cmd, @args)); + if (!defined($pid = open(PIPE, "-|"))) { + bsd::err(1, "open()"); + } elsif ($pid == 0) { + exec($cmd, @args); + die("child: exec(): $!\n"); + } + $output = ""; + while (<PIPE>) { + $output .= $_; + if (!$suppressed) { + stderr($_); + } + } + if (!close(PIPE)) { + if ($? & 0xff) { + bsd::warnx("%s caught signal %d", $cmd, $? & 0x7f); + } elsif ($? >> 8) { + bsd::warnx("%s returned exit code %d", $cmd, $? >> 8); + } else { + bsd::warn("close()"); + } + return undef; + } + return $output || "\n"; } # @@ -146,6 +240,9 @@ sub cvs($;@) { my @args; # Arguments to CVS + if (!$update) { + return "\n"; + } push(@args, "-f", "-z3", "-R", "-d$cvsroot", $verbose ? "-q" : "-Q", $cmd, "-A"); if ($cmd eq "checkout") { @@ -160,7 +257,7 @@ sub cvs($;@) { push(@args, "-D$date"); } push(@args, @_); - return cmd($cvs, @args); + return cmd(&PATH_CVS, @args); } # @@ -172,8 +269,8 @@ sub make($@) { push(@args, "PORTSDIR=$portsdir") unless ($portsdir eq "/usr/ports"); - cd("$portsdir/$ports{$port}"); - cmd("make", @args); + cd("$portsdir/$port"); + return cmd(&PATH_MAKE, @args); } # @@ -184,7 +281,7 @@ sub ecks() { local *FILE; # File handle sysopen(FILE, "/var/db/port.mkversion", O_RDWR|O_CREAT|O_TRUNC, 0644) - or err(1, "open()"); + or bsd::err(1, "open()"); print(FILE "20380119\n"); close(FILE); } @@ -201,13 +298,13 @@ sub update_index() { cd($parent); if (-f "ports/INDEX" || (-d "ports" && -d "ports/CVS")) { cvs("update", "ports/INDEX") - or errx(1, "error updating the index file"); + or bsd::errx(1, "error updating the index file"); } else { cvs("checkout", "-l", "ports") - or errx(1, "error checking out the index file"); + or bsd::errx(1, "error checking out the index file"); } cvs("update", "-l", "ports/Mk") - or errx(1, "error updating the Makefiles"); + or bsd::errx(1, "error updating the Makefiles"); } # @@ -218,81 +315,103 @@ sub read_index() { local *INDEX; # File handle my $line; # Line from file + info("Reading index file"); sysopen(INDEX, $index, O_RDONLY) - or err(1, "can't open $index"); + or bsd::err(1, "can't open $index"); while ($line = <INDEX>) { my @port; # Port info - my @depend; # Dependencies - @port = split(/\|/, $line); + @port = split(/\|/, $line, 3); $port[1] =~ s|^/usr/ports/*||; $ports{$port[0]} = $port[1]; $strop{$port[1]} = $port[0]; - @depend = split(' ', "$port[7] $port[8]"); - $dependencies{$port[0]} = \@depend; } close(INDEX); info(keys(%ports) . " ports in index"); } # +# Find a port by a portion of it's package name +# +sub find_port($) { + my $port = shift; # Port to find + + my @suggest; # Suggestions + + stderr("can't find required port '$port'"); + @suggest = grep(/^$port/i, keys(%ports)); + if (@suggest == 1 && $suggest[0] =~ m/^$port[0-9.-]/) { + $port = $ports{$suggest[0]}; + stderr(", assuming you mean '$port'.\n"); + return $port; + } elsif (@suggest) { + stderr(", maybe you mean:\n " . (join("\n ", @suggest))); + } + stderr("\n"); + return undef; +} + +# # Add a port to the list of required ports # -sub add_port($$); # Prototype to silence warning sub add_port($$) { my $port = shift; # Port to add my $req = shift; # Requirement (explicit or implicit) - my $err = 0; # Error count - - if (exists($reqd{$port})) { - $reqd{$port} |= $req; - return 0; - } - if (!exists($ports{$port})) { - my @suggest; # Suggestions - - stderr("can't find required port '$port'"); - @suggest = grep(/^$port/i, keys(%ports)); - if (@suggest == 1 && $suggest[0] =~ m/^$port[0-9.-]/) { - $port = shift(@suggest); - stderr(", assuming you mean '$port'.\n"); - } elsif (@suggest) { - stderr(", maybe you mean:\n " . (join("\n ", @suggest)) . "\n"); - return 1; + my $realport; # Real port name + my $pkgname; # Package name + + if ($port =~ m|^([^/]+/[^/]+)$|) { + $realport = $1; + } else { + if (exists($ports{$port})) { + $realport = $ports{$port}; } else { - stderr("\n"); - return 1; + $realport = find_port($port); } } - $reqd{$port} = $req; - foreach $port (@{$dependencies{$port}}) { - $err += add_port($port, &REQ_IMPLICIT); + if (!$realport) { + return 1; + } + if (!exists($reqd{$realport})) { + $reqd{$realport} = 0; } - return $err; + $reqd{$realport} |= $req; + return 0; } # # Find master directory for a port # -sub find_master($$) { - my $category = shift; # Category +sub find_master($) { my $port = shift; # Port local *FILE; # File handle my $master; # Master directory - open(FILE, "$portsdir/$category/$port/Makefile") - or err(1, "unable to read Makefile for $category/$port"); + # Look for MASTERDIR in the Makefile. We can't use 'make -V' + # because the Makefile might try to include the master port's + # Makefile, which might not be checked out yet. + open(FILE, "$portsdir/$port/Makefile") + or bsd::err(1, "unable to read Makefile for $port"); while (<FILE>) { if (/^MASTERDIR\s*=\s*(\S+)\s*$/) { $master = $1; + } elsif (/^\.?include \"([^\"]+)\/Makefile\"\s*$/) { + $master = $1; + } + if (defined($master)) { $master =~ s/^\$\{.CURDIR\}//; - $master = "/$category/$port/$master"; + $master = "/$port/$master"; $master =~ s|/+|/|g; 1 while ($master =~ s|/[^\./]*/\.\./|/|); $master =~ s|^/||; + if ($master !~ m|^[^/]+/[^/]+$|) { + bsd::warn("invalid master for %s: %s", $port, $master); + next; + } close(FILE); + info("$master is master for $port\n"); return $master; } } @@ -301,6 +420,88 @@ sub find_master($$) { } # +# Find a dynamic library +# +sub find_library($) { + my $library = shift; # Library to find + + my $ldconfig; # Output from ldconfig(8) + + $ldconfig = suppress(\&cmd, (&PATH_LDCONFIG, "-r")) + or errx(1, "unable to run ldconfig"); + if ($ldconfig =~ m/^\s*\d+:-l$library => (.*)$/m) { + info("The $library library is installed as $1"); + return 1; + } + return 0; +} + +# +# Find a binary +# +sub find_binary($) { + my $binary = shift; # Binary to find + + my $dir; # Directory + + if ($binary =~ m|^/|) { + info("$binary is installed as $binary"); + return (-x $binary); + } + foreach $dir (split(/:/, $ENV{'PATH'})) { + if (-x "$dir/$binary") { + info("$binary is installed as $dir/$binary"); + return 1; + } + } + return 0; +} + +# +# Find a port's dependencies +# +sub find_dependencies($) { + my $port = shift; # Port + + my $dependvars; # Dependency variables + my $item; # Iterator + my %depends; # Hash of dependencies + my ($lhs, $rhs); # Left, right hand side of dependency spec + + $dependvars = suppress(\&make, ($port, + "-VFETCH_DEPENDS", + "-VBUILD_DEPENDS", + "-VRUN_DEPENDS", + "-VLIB_DEPENDS", + "-VDEPENDS")) + or bsd::errx(1, "failed to obtain dependency list"); + %depends = (); + foreach $item (split(' ', $dependvars)) { + if ($item !~ m|^([^:]+):$portsdir/([^/]+/[^/]+)(:[^:]+)?$|) { + bsd::warnx("invalid dependency: %s", $item); + } + ($lhs, $rhs) = ($1, $2); + if ($exclude) { + if ($installed{$rhs}) { + next; + } + info("Verifying status of $rhs ($lhs)"); + if (($lhs =~ m|^/| && -f $lhs) || + ($lhs =~ m/\.\d+$/ && find_library($lhs)) || + find_binary($lhs)) { + info("$rhs seems to be installed"); + $installed{$rhs} = 1; + next; + } + $installed{$rhs} = -1; + } + info("Adding $rhs as a dependency for $port"); + $depends{$rhs} = 1; + } + return keys(%depends); +} + +# # Update all necessary files to build the specified ports # sub update_ports_tree(@) { @@ -308,39 +509,92 @@ sub update_ports_tree(@) { my $port; # Port name my $category; # Category name - my %updated; # Hash of updated ports - my %additional; # Additional dependencies - my $todo; # Have something to do + my %upd_cat; # Hash of updated categories + my %upd_port; # Hash of updated ports + my %processed; # Hash of processed ports + my @additional; # Additional dependencies + my $n; # Pass count foreach $port (@ports) { - $additional{$ports{$port}} = 1; + push(@additional, $port); } - for (;;) { - my %update_now; # Ports that need updating now + for ($n = 0; ; ++$n) { + my @update_now; # Ports that need updating now + my $item; # Iterator my $master; # Master port + my $dependency; # Dependency - foreach $port (keys(%additional)) { - ($category, $port) = split(/\//, $port); - if (!$updated{$category}->{$port}) { - $update_now{$category}->{$port} = 1; - $updated{$category}->{$port} = 1; + # Determine which ports need updating + foreach $item (@additional) { + next if $processed{$item}; + ($category, $port) = split(/\//, $item); + if (!exists($upd_port{$category})) { + $upd_port{$category} = {}; } + if (!exists($upd_port{$category}->{$port})) { + $upd_port{$category}->{$port} = 0; + } + push(@update_now, $item); } - last unless keys(%update_now); - cd($portsdir); - cvs("update", "-l", keys(%update_now)) - or errx(1, "error updating the category directories"); - foreach $category (keys(%update_now)) { - cd("$portsdir/$category"); - cvs("update", keys(%{$update_now{$category}})) - or errx(1, "error updating the '$category' category"); + last unless @update_now; + info("Pass $n:", @update_now); + + # Update the relevant sections of the ports tree + foreach $category (keys(%upd_port)) { + my @ports; # Ports to update + + if (!$upd_cat{$category}) { + cd($portsdir); + cvs("update", "-l", $category) + or bsd::errx(1, "error updating the '$category' category"); + $upd_cat{$category} = 1; + } + foreach $port (keys(%{$upd_port{$category}})) { + next if ($upd_port{$category}->{$port}); + push(@ports, $port); + $upd_port{$category}->{$port} = 1; + } + if (@ports) { + cd("$portsdir/$category"); + cvs("update", @ports) + or bsd::errx(1, "error updating the '$category' category"); + } } - foreach $category (keys(%update_now)) { - foreach $port (keys(%{$update_now{$category}})) { - if ($master = find_master($category, $port)) { - $additional{$master} = 1; + + # Process all unprocessed ports we know of so far + foreach $port (@update_now) { + # See if the port has an unprocessed master port + if (($master = find_master($port)) && !$processed{$master}) { + add_port($master, &REQ_MASTER); + info("Adding $master to head of line\n"); + unshift(@additional, $master); + # Need to process master before we continue + next; + } + + # Find the port's package name + if (!exists($strop{$port})) { + if ($strop{$port} = suppress(\&make, ($port, "-VPKGNAME"))) { + chomp($strop{$port}); + } else { + warnx("failed to obtain package name for $port"); + } + } + + # Find the port's dependencies + foreach $dependency (find_dependencies($port)) { + next if ($processed{$dependency}); + if ($reqd{$port} == &REQ_MASTER) { + add_port($dependency, &REQ_MASTER); + } else { + add_port($dependency, &REQ_IMPLICIT); } + info("Adding $dependency to back of line\n"); + push(@additional, $dependency); } + + # Mark port as processed + $processed{$port} = 1; } } } @@ -354,8 +608,8 @@ sub show_port_info($) { local *FILE; # File handle my $info; # Port info - sysopen(FILE, "$portsdir/$ports{$port}/pkg-descr", O_RDONLY) - or err(1, "can't read description for $port"); + sysopen(FILE, "$portsdir/$port/pkg-descr", O_RDONLY) + or bsd::err(1, "can't read description for $port"); $info = join("| ", <FILE>); close(FILE); print("+--- $port:\n| ${info}+---\n"); @@ -371,7 +625,7 @@ sub list_installed() { my $unknown; # Unknown ports opendir(DIR, $dbdir) - or err(1, "can't read database directory"); + or bsd::err(1, "can't read database directory"); print("Installed ports:\n"); foreach $port (readdir(DIR)) { next if ($port eq "." || $port eq ".." || ! -d "$dbdir/$port"); @@ -393,7 +647,8 @@ sub list_installed() { sub clean_port($) { my $port = shift; # Port to clean - make($port, "clean"); + make($port, "clean") + or bsd::warnx("failed to clean %s", $port); } # @@ -405,10 +660,10 @@ sub clean_tree() { # We could just cd to $portsdir and 'make clean', but it'd # be extremely noisy due to only having a partial tree - #errx(1, Dumper(\%strop)); foreach $port (keys(%ports)) { - if (-d "$portsdir/$ports{$port}") { - make($port, "clean", "NO_DEPENDS=yes"); + if (-d "$portsdir/$port") { + make($port, "clean", "NO_DEPENDS=yes") + or bsd::warnx("failed to clean %s", $port); } } } @@ -419,7 +674,8 @@ sub clean_tree() { sub fetch_port($) { my $port = shift; # Port to fetch - make($port, "fetch"); + make($port, "fetch") + or bsd::errx(1, "failed to fetch %s", $port); } # @@ -428,9 +684,21 @@ sub fetch_port($) { sub build_port($) { my $port = shift; # Port to build - make($port, - $packages ? ("package", "DEPENDS_TARGET=package") : "install", - "clean"); + my @makeargs; # Arguments to make() + + if ($packages) { + push(@makeargs, "package", "DEPENDS_TARGET=package"); + } else { + push(@makeargs, "install"); + } + if ($force) { + push(@makeargs, "-DFORCE_PKG_REGISTER"); + } + if (!$dontclean) { + push(@makeargs, "clean"); + } + make($port, @makeargs) + or bsd::errx(1, "failed to %s %s", $packages ? "package" : "build", $port); } # @@ -438,7 +706,7 @@ sub build_port($) { # sub usage() { - stderr("Usage: porteasy [-abcefhikluVv] [-d dir] [-D date]\n" . + stderr("Usage: porteasy [-abCceFfhikluVv] [-d dir] [-D date]\n" . " [-p dir] [-r dir] [-t tag] [port ...]\n"); exit(1); } @@ -463,6 +731,7 @@ Options: -a, --anoncvs Use the FreeBSD project's anoncvs server -b, --build Build required ports -c, --clean Clean the specified ports + -C, --dontclean Don't clean after build -e, --exclude-installed Exclude installed ports -f, --fetch Fetch distfiles -h, --help Show this information @@ -488,6 +757,7 @@ Report bugs to <des\@freebsd.org>. MAIN:{ my $port; # Port name my $err = 0; # Error count + my $need_index; # Need the index # Scan command line options Getopt::Long::Configure("auto_abbrev", "bundling"); @@ -495,9 +765,11 @@ MAIN:{ "a|anoncvs" => \$anoncvs, "b|build" => \$build, "c|clean" => \$clean, + "C|dontclean" => \$dontclean, "D|date=s" => \$date, "d|dbdir=s" => \$dbdir, "e|exclude-installed" => \$exclude, + "F|force-pkg-register" => \$force, "f|fetch" => \$fetch, "h|help" => \$help, "i|info" => \$info, @@ -528,25 +800,22 @@ MAIN:{ if (!@ARGV && (!$clean && !$info || $build || $fetch)) { usage(); } - + if ($portsdir !~ m/^\//) { $portsdir = `pwd` . $portsdir; $portsdir =~ s/\n/\//s; } if ($portsdir !~ m/\/ports\/?$/) { - errx(1, "ports directory must be named 'ports'"); + bsd::errx(1, "ports directory must be named 'ports'"); } $index = "$portsdir/INDEX"; - # 'package' implies 'build', which implies 'fetch'. + # 'package' implies 'build' if ($packages) { $build = 1; } - if ($build) { - $fetch = 1; - } # Set and check CVS root if ($anoncvs) { @@ -556,47 +825,60 @@ MAIN:{ $cvsroot = $ENV{'CVSROOT'}; } if (!$cvsroot) { - errx(1, "No CVS root, please use the -r option or set \$CVSROOT"); + bsd::errx(1, "No CVS root, please use the -r option or set \$CVSROOT"); + } + + # Check if we need the index + if (!@ARGV && $info) { + $need_index = 1; + } + foreach $port (@ARGV) { + if ($port !~ m/\//) { + $need_index = 1; + } } - # Read the ports index - if ($update) { + # Step 1: read the ports index + if ($need_index) { update_index(); + read_index(); } - read_index(); - # Read list of explicitly required ports + # Step 2: build list of explicitly required ports foreach $port (@ARGV) { $err += add_port($port, &REQ_EXPLICIT); } if ($err) { - errx(1, "some required ports were not found."); + bsd::errx(1, "some required ports were not found."); + } + + # Step 3: update port directories and discover dependencies + if (!($build || $fetch || ($info && @ARGV) || $list)) { + $update = 0; } + update_ports_tree(keys(%reqd)); - # Deselect ports which are already installed + # Step 4: deselect ports which are already installed if ($exclude) { foreach $port (keys(%reqd)) { - if (-d "$dbdir/$port") { + if ((exists($installed{$port}) && $installed{$port} > 0) || + -d "$dbdir/$strop{$port}") { info("$port is already installed"); delete $reqd{$port}; } } } - # List required packages + # Step 5: list selected ports if ($list) { foreach $port (sort(keys(%reqd))) { + next if ($reqd{$port} == &REQ_MASTER); print((($reqd{$port} & &REQ_EXPLICIT) ? " * " : " "), - "$port\n"); + "$port ($strop{$port})\n"); } } - # Update port directories - if ($update && ($build || $fetch || $info)) { - update_ports_tree(keys(%reqd)); - } - - # Show info + # Step 6: show info (or list installed packages) if ($info) { if (!@ARGV) { list_installed(); @@ -609,7 +891,7 @@ MAIN:{ } } - # Clean + # Step 7: clean the ports directories (or the entire tree) if ($clean) { if (!@ARGV) { clean_tree(); @@ -622,16 +904,17 @@ MAIN:{ } } - # Fetch ports + # Step 8: fetch distfiles if ($fetch) { foreach $port (keys(%reqd)) { - fetch_port($port); + if ($reqd{$port} != &REQ_MASTER) { + fetch_port($port); + } } } - # Build ports - only the explicitly required ones, since the - # 'already installed' test may give false negatives (most commonly - # XFree86) + # Step 9: build ports - only the explicitly required ones, since + # some dependencies (most commonly XFree86) may be bogus. if ($build || $packages) { foreach $port (keys(%reqd)) { if (!($reqd{$port} & &REQ_IMPLICIT)) { diff --git a/ports-mgmt/porteasy/Makefile b/ports-mgmt/porteasy/Makefile index 640577dc2672..ea9d553030ed 100644 --- a/ports-mgmt/porteasy/Makefile +++ b/ports-mgmt/porteasy/Makefile @@ -8,7 +8,7 @@ # PORTNAME= porteasy -PORTVERSION= 1.7 +PORTVERSION= 2.0 CATEGORIES= misc MASTER_SITES= # none DISTFILES= # none diff --git a/ports-mgmt/porteasy/src/porteasy.8 b/ports-mgmt/porteasy/src/porteasy.8 index f142e13e0bab..33b70ca77abe 100644 --- a/ports-mgmt/porteasy/src/porteasy.8 +++ b/ports-mgmt/porteasy/src/porteasy.8 @@ -35,7 +35,7 @@ .Nd fetch and build ports .Sh SYNOPSIS .Nm porteasy -.Op Fl abcefhikluVv +.Op Fl abCceFfhikluVv .Op Fl D Ar date .Op Fl d Ar dir .Op Fl p Ar dir @@ -50,18 +50,25 @@ automatically, keeping track of dependencies. The following options are available: .Bl -tag -width Fl .It Fl a -Use the FreeBSD project's anoncvs server as CVS root. +Use the FreeBSD project's anoncvs server as CVS root directory. .It Fl b Build the selected ports. +.It Fl C +Don't clean port directories after building. .It Fl c Clean the selected ports. .It Fl D Ar date -Specify a date to use for CVS operations. +Specify a date to use for +.Xr cvs 1 +operations. .It Fl d Ar dir Specify the package database directory (normally .Pa /var/db/pkg ) . .It Fl e Deselect ports that are already installed. +.It Fl F +Force installation and registration, even if the port is already +installed. .It Fl f Fetch the selected ports. .It Fl h @@ -78,9 +85,12 @@ Specify the ports directory (normally .It Fl r Ar dir Specify the CVS root directory. .It Fl t Ar tag -Specify a tag to use for CVS operations. +Specify a tag to use for +.Xr cvs 1 +operations. .It Fl u -Update all necessary files using CVS. +Update all necessary files using +.Xr cvs 1 . .It Fl V Show the .Nm @@ -88,6 +98,18 @@ version number and exit. .It Fl v Verbose mode: show more information about what is being done. .El +.Ss Port names +The port names listed on the command line may be either unqualified or +fully qualified. +A fully qualified port name is the path to the port directory relative +to the root of the ports tree (i.e. the port's category and name +separated by a slash). +An unqualified port name is the name of the package built by the +intended port, or part of that name. +.Pp +Unqualified names need to be looked up in the ports index, which is +usually slightly out of date, so fully qualified names should be used +whenever possible. .Ss Sequence of operation This section describes the operations performed by .Nm @@ -96,42 +118,45 @@ and the order in which they are performed. .It Update index If the .Fl u -option was specified, the index file is updated from CVS. +option was specified and some unqualified port names were listed on +the command line, the index file is updated using +.Xr cvs 1 . .It Select ports -The ports listed on the command line are looked up in the index, using -simple heuristics to identify incompletely named ports. +The selection list is initialized with the ports listed on the command +line, marked as explicit dependencies. +Any unqualified names are looked up in the index, using simple +heuristics to identify incompletely named ports. If a certain match is not found, .Nm prints a list of possible matches and exits. .Pp All direct and indirect dependencies of the ports listed on the command line are also selected and marked as dependencies. +.It Update ports tree and discover dependencies +If the +.Fl u +option was specified, the port directories for all selected ports are +updated using +.Xr cvs 1 . +Each selected port's Makefile is scanned to discover dependencies, +which are in turn selected and marked as implicit dependencies. +This process is repeated until no new dependencies are found. +.It Deselect installed ports .Pp If the .Fl e option was specified, .Nm -then checks to see if any of the required ports are already installed; +checks to see if any of the selected ports are already installed; those that are are deselected. -.Pp -Selected ports that were specified on the command line are marked as -explicitly selected, while dependencies are marked as implicitly -selected. +This process is not very accurate, as it will not detect if an older +or alternate version of a selected port is installed. .It List selected ports If the .Fl l -option was specified, all selected ports are listed. +option was specified, the fully qualified name and package name of all +selected ports are listed. Explicitly selected ports are indicated with a star. -.It Update ports tree -If the -.Fl u -option and at least one of the -.Fl b , -.Fl f , -.Fl i -or -.Fl k -options was specified, all selected ports are updated from CVS. .It Describe selected ports If the .Fl i @@ -168,7 +193,7 @@ options was specified, runs the .Sq fetch target on every selected port. -.It Install ports +.It Build, install, package, clean ports If one or both of the .Fl f or @@ -177,45 +202,36 @@ options were specified, .Nm runs the .Sq install -target on every explicitly selected port. -Dependencies are handled by the ports system. -.It Build packages -If the -.Fl k -option was specified, -.Nm -runs the -.Sq package -target on every explicitly selected port. -Dependencies are handled by the ports system. -.It Clean the tree (again) -If one or both of the -.Fl f or -.Fl k -options were specified, -.Nm -finally runs the +.Sq package +target, followed by the .Sq clean -target on every selected port once it is -installed and (optionally) its package has been built. +target (unless the +.Fl C +option was specified), on every explicitly selected port. +.Nm +lets the ports system handle dependencies on its own, since the +reported dependencies are sometimes too inclusive. .El .Sh IMPLEMENTATION NOTES There may be a significant difference between what ports are selected (and listed if the .Fl l option is specified) and what ports are actually installed and/or have -packages built for them, since: -.Bl -bullet -.It -the index file lists all dependencies, including ones that are -conditional on system configuration or build-time options. -.It -implicitly selected ports that are already installed, or somehow pass -the dependency check (e.g. because an alternate, equivalent port has -been installed) will be passed over by the ports system, as indeed -they should. -.El +packages built for them, since implicitly selected ports that are +already installed, or somehow pass the dependency check (e.g. because +an alternate, equivalent port has been installed) will be passed over +by the ports system, as indeed they should. +.Pp +.Nm +tries to minimize the number of times +.Xr cvs 1 +is invoked, since the overhead involved in connecting to a remote +server is usually quite high (and the user might have to type a +password every time), but prefers correctness to performance. +The maximum number of invocations is (2 + NC + NP), where NC and NP +are the number of distinct categories and ports (including master +directories and dependencies). .Sh FILES .Nm maintains and operates on a ports tree, normally diff --git a/ports-mgmt/porteasy/src/porteasy.pl b/ports-mgmt/porteasy/src/porteasy.pl index eb83b9d631d3..4800cdb13b9c 100644 --- a/ports-mgmt/porteasy/src/porteasy.pl +++ b/ports-mgmt/porteasy/src/porteasy.pl @@ -30,19 +30,22 @@ # use strict; -use Data::Dumper; use Fcntl; use Getopt::Long; -my $VERSION = "1.7"; +my $VERSION = "2.0"; # Constants sub ANONCVS_ROOT { ":pserver:anoncvs\@anoncvs.FreeBSD.org:/home/ncvs" } sub REQ_EXPLICIT { 1 } sub REQ_IMPLICIT { 2 } +sub REQ_MASTER { 4 } + +sub PATH_CVS { "/usr/bin/cvs" } +sub PATH_LDCONFIG { "/sbin/ldconfig" } +sub PATH_MAKE { "/usr/bin/make" } # Global parameters -my $cvs = "/usr/bin/cvs"; # CVS command my $dbdir = "/var/db/pkg"; # Package database directory my $index = undef; # Index file my $portsdir = "/usr/ports"; # Ports directory @@ -55,8 +58,10 @@ my $clean = 0; # Clean ports my $cvsroot = 0; # CVS root directory my $exclude = 0; # Do not list installed ports my $fetch = 0; # Fetch ports +my $force = 0; # Force package registration my $help = 0; # Show help text my $info = 0; # Show port info +my $dontclean = 0; # Don't clean after build my $packages = 0; # Build packages my $list = 0; # List ports my $build = 0; # Build ports @@ -68,8 +73,9 @@ my $ecks = 0; # The undocumented option. # Global variables my %ports; # Maps ports to their directory. my %strop; # Inverse of the above map -my %dependencies; # Maps ports to their dependency lists. my %reqd; # Ports that need to be installed +my %installed; # Ports that are already installed +my $suppressed; # Suppress output # # Shortcut for 'print STDERR' @@ -81,7 +87,7 @@ sub stderr(@) { # # Similar to err(3) # -sub err($$@) { +sub bsd::err($$@) { my $code = shift; # Return code my $fmt = shift; # Format string my @args = @_; # Arguments @@ -96,7 +102,7 @@ sub err($$@) { # # Similar to errx(3) # -sub errx($$@) { +sub bsd::errx($$@) { my $code = shift; # Return code my $fmt = shift; # Format string my @args = @_; # Arguments @@ -109,33 +115,121 @@ sub errx($$@) { } # +# Similar to warn(3) +# +sub bsd::warn($@) { + my $fmt = shift; # Format string + my @args = @_; # Arguments + + my $msg; # Error message + + $msg = sprintf($fmt, @args); + stderr("$msg: $!\n"); +} + +# +# Similar to warnx(3) +# +sub bsd::warnx($@) { + my $fmt = shift; # Format string + my @args = @_; # Arguments + + my $msg; # Error message + + $msg = sprintf($fmt, @args); + stderr("$msg\n"); +} + +# +# Call the specified sub with output suppressed +# +sub suppress($@) { + my $subr = shift; # Subroutine to call + my @args = @_; # Arguments + + my $oldsuppressed; # Old suppress flag + my $rtn; # Return value + + if (!$verbose) { + $oldsuppressed = $suppressed; + $suppressed = 1; + } + $rtn = &{$subr}(@args); + if (!$verbose) { + $suppressed = $oldsuppressed; + } + return $rtn; +} + + +# # Print an info message # -sub info($) { +sub info(@) { + + my $msg; # Message + if ($verbose) { - chomp($_[0]); - stderr(">>> $_[0]\n"); + $msg = join(' ', @_); + chomp($msg); + stderr("$msg\n"); } } # +# Print an info message about a subprocess +# +sub cmdinfo(@) { + info(">>>", @_); +} + +# # Change working directory # sub cd($) { my $dir = shift; # Directory to change to - info("cd $dir"); + cmdinfo("cd $dir"); chdir($dir) - or err(1, "unable to chdir to %s", $dir); + or bsd::err(1, "unable to chdir to %s", $dir); } # -# Run a command using system() +# Run a command and return its output # -sub cmd(@) { - - info(join(" ", @_)); - return (system(@_) == 0); +sub cmd($@) { + my $cmd = shift; # Command to run + my @args = @_; # Arguments + + my $pid; # Child pid + local *PIPE; # Pipe + my $output; # Output + + cmdinfo(join(" ", $cmd, @args)); + if (!defined($pid = open(PIPE, "-|"))) { + bsd::err(1, "open()"); + } elsif ($pid == 0) { + exec($cmd, @args); + die("child: exec(): $!\n"); + } + $output = ""; + while (<PIPE>) { + $output .= $_; + if (!$suppressed) { + stderr($_); + } + } + if (!close(PIPE)) { + if ($? & 0xff) { + bsd::warnx("%s caught signal %d", $cmd, $? & 0x7f); + } elsif ($? >> 8) { + bsd::warnx("%s returned exit code %d", $cmd, $? >> 8); + } else { + bsd::warn("close()"); + } + return undef; + } + return $output || "\n"; } # @@ -146,6 +240,9 @@ sub cvs($;@) { my @args; # Arguments to CVS + if (!$update) { + return "\n"; + } push(@args, "-f", "-z3", "-R", "-d$cvsroot", $verbose ? "-q" : "-Q", $cmd, "-A"); if ($cmd eq "checkout") { @@ -160,7 +257,7 @@ sub cvs($;@) { push(@args, "-D$date"); } push(@args, @_); - return cmd($cvs, @args); + return cmd(&PATH_CVS, @args); } # @@ -172,8 +269,8 @@ sub make($@) { push(@args, "PORTSDIR=$portsdir") unless ($portsdir eq "/usr/ports"); - cd("$portsdir/$ports{$port}"); - cmd("make", @args); + cd("$portsdir/$port"); + return cmd(&PATH_MAKE, @args); } # @@ -184,7 +281,7 @@ sub ecks() { local *FILE; # File handle sysopen(FILE, "/var/db/port.mkversion", O_RDWR|O_CREAT|O_TRUNC, 0644) - or err(1, "open()"); + or bsd::err(1, "open()"); print(FILE "20380119\n"); close(FILE); } @@ -201,13 +298,13 @@ sub update_index() { cd($parent); if (-f "ports/INDEX" || (-d "ports" && -d "ports/CVS")) { cvs("update", "ports/INDEX") - or errx(1, "error updating the index file"); + or bsd::errx(1, "error updating the index file"); } else { cvs("checkout", "-l", "ports") - or errx(1, "error checking out the index file"); + or bsd::errx(1, "error checking out the index file"); } cvs("update", "-l", "ports/Mk") - or errx(1, "error updating the Makefiles"); + or bsd::errx(1, "error updating the Makefiles"); } # @@ -218,81 +315,103 @@ sub read_index() { local *INDEX; # File handle my $line; # Line from file + info("Reading index file"); sysopen(INDEX, $index, O_RDONLY) - or err(1, "can't open $index"); + or bsd::err(1, "can't open $index"); while ($line = <INDEX>) { my @port; # Port info - my @depend; # Dependencies - @port = split(/\|/, $line); + @port = split(/\|/, $line, 3); $port[1] =~ s|^/usr/ports/*||; $ports{$port[0]} = $port[1]; $strop{$port[1]} = $port[0]; - @depend = split(' ', "$port[7] $port[8]"); - $dependencies{$port[0]} = \@depend; } close(INDEX); info(keys(%ports) . " ports in index"); } # +# Find a port by a portion of it's package name +# +sub find_port($) { + my $port = shift; # Port to find + + my @suggest; # Suggestions + + stderr("can't find required port '$port'"); + @suggest = grep(/^$port/i, keys(%ports)); + if (@suggest == 1 && $suggest[0] =~ m/^$port[0-9.-]/) { + $port = $ports{$suggest[0]}; + stderr(", assuming you mean '$port'.\n"); + return $port; + } elsif (@suggest) { + stderr(", maybe you mean:\n " . (join("\n ", @suggest))); + } + stderr("\n"); + return undef; +} + +# # Add a port to the list of required ports # -sub add_port($$); # Prototype to silence warning sub add_port($$) { my $port = shift; # Port to add my $req = shift; # Requirement (explicit or implicit) - my $err = 0; # Error count - - if (exists($reqd{$port})) { - $reqd{$port} |= $req; - return 0; - } - if (!exists($ports{$port})) { - my @suggest; # Suggestions - - stderr("can't find required port '$port'"); - @suggest = grep(/^$port/i, keys(%ports)); - if (@suggest == 1 && $suggest[0] =~ m/^$port[0-9.-]/) { - $port = shift(@suggest); - stderr(", assuming you mean '$port'.\n"); - } elsif (@suggest) { - stderr(", maybe you mean:\n " . (join("\n ", @suggest)) . "\n"); - return 1; + my $realport; # Real port name + my $pkgname; # Package name + + if ($port =~ m|^([^/]+/[^/]+)$|) { + $realport = $1; + } else { + if (exists($ports{$port})) { + $realport = $ports{$port}; } else { - stderr("\n"); - return 1; + $realport = find_port($port); } } - $reqd{$port} = $req; - foreach $port (@{$dependencies{$port}}) { - $err += add_port($port, &REQ_IMPLICIT); + if (!$realport) { + return 1; + } + if (!exists($reqd{$realport})) { + $reqd{$realport} = 0; } - return $err; + $reqd{$realport} |= $req; + return 0; } # # Find master directory for a port # -sub find_master($$) { - my $category = shift; # Category +sub find_master($) { my $port = shift; # Port local *FILE; # File handle my $master; # Master directory - open(FILE, "$portsdir/$category/$port/Makefile") - or err(1, "unable to read Makefile for $category/$port"); + # Look for MASTERDIR in the Makefile. We can't use 'make -V' + # because the Makefile might try to include the master port's + # Makefile, which might not be checked out yet. + open(FILE, "$portsdir/$port/Makefile") + or bsd::err(1, "unable to read Makefile for $port"); while (<FILE>) { if (/^MASTERDIR\s*=\s*(\S+)\s*$/) { $master = $1; + } elsif (/^\.?include \"([^\"]+)\/Makefile\"\s*$/) { + $master = $1; + } + if (defined($master)) { $master =~ s/^\$\{.CURDIR\}//; - $master = "/$category/$port/$master"; + $master = "/$port/$master"; $master =~ s|/+|/|g; 1 while ($master =~ s|/[^\./]*/\.\./|/|); $master =~ s|^/||; + if ($master !~ m|^[^/]+/[^/]+$|) { + bsd::warn("invalid master for %s: %s", $port, $master); + next; + } close(FILE); + info("$master is master for $port\n"); return $master; } } @@ -301,6 +420,88 @@ sub find_master($$) { } # +# Find a dynamic library +# +sub find_library($) { + my $library = shift; # Library to find + + my $ldconfig; # Output from ldconfig(8) + + $ldconfig = suppress(\&cmd, (&PATH_LDCONFIG, "-r")) + or errx(1, "unable to run ldconfig"); + if ($ldconfig =~ m/^\s*\d+:-l$library => (.*)$/m) { + info("The $library library is installed as $1"); + return 1; + } + return 0; +} + +# +# Find a binary +# +sub find_binary($) { + my $binary = shift; # Binary to find + + my $dir; # Directory + + if ($binary =~ m|^/|) { + info("$binary is installed as $binary"); + return (-x $binary); + } + foreach $dir (split(/:/, $ENV{'PATH'})) { + if (-x "$dir/$binary") { + info("$binary is installed as $dir/$binary"); + return 1; + } + } + return 0; +} + +# +# Find a port's dependencies +# +sub find_dependencies($) { + my $port = shift; # Port + + my $dependvars; # Dependency variables + my $item; # Iterator + my %depends; # Hash of dependencies + my ($lhs, $rhs); # Left, right hand side of dependency spec + + $dependvars = suppress(\&make, ($port, + "-VFETCH_DEPENDS", + "-VBUILD_DEPENDS", + "-VRUN_DEPENDS", + "-VLIB_DEPENDS", + "-VDEPENDS")) + or bsd::errx(1, "failed to obtain dependency list"); + %depends = (); + foreach $item (split(' ', $dependvars)) { + if ($item !~ m|^([^:]+):$portsdir/([^/]+/[^/]+)(:[^:]+)?$|) { + bsd::warnx("invalid dependency: %s", $item); + } + ($lhs, $rhs) = ($1, $2); + if ($exclude) { + if ($installed{$rhs}) { + next; + } + info("Verifying status of $rhs ($lhs)"); + if (($lhs =~ m|^/| && -f $lhs) || + ($lhs =~ m/\.\d+$/ && find_library($lhs)) || + find_binary($lhs)) { + info("$rhs seems to be installed"); + $installed{$rhs} = 1; + next; + } + $installed{$rhs} = -1; + } + info("Adding $rhs as a dependency for $port"); + $depends{$rhs} = 1; + } + return keys(%depends); +} + +# # Update all necessary files to build the specified ports # sub update_ports_tree(@) { @@ -308,39 +509,92 @@ sub update_ports_tree(@) { my $port; # Port name my $category; # Category name - my %updated; # Hash of updated ports - my %additional; # Additional dependencies - my $todo; # Have something to do + my %upd_cat; # Hash of updated categories + my %upd_port; # Hash of updated ports + my %processed; # Hash of processed ports + my @additional; # Additional dependencies + my $n; # Pass count foreach $port (@ports) { - $additional{$ports{$port}} = 1; + push(@additional, $port); } - for (;;) { - my %update_now; # Ports that need updating now + for ($n = 0; ; ++$n) { + my @update_now; # Ports that need updating now + my $item; # Iterator my $master; # Master port + my $dependency; # Dependency - foreach $port (keys(%additional)) { - ($category, $port) = split(/\//, $port); - if (!$updated{$category}->{$port}) { - $update_now{$category}->{$port} = 1; - $updated{$category}->{$port} = 1; + # Determine which ports need updating + foreach $item (@additional) { + next if $processed{$item}; + ($category, $port) = split(/\//, $item); + if (!exists($upd_port{$category})) { + $upd_port{$category} = {}; } + if (!exists($upd_port{$category}->{$port})) { + $upd_port{$category}->{$port} = 0; + } + push(@update_now, $item); } - last unless keys(%update_now); - cd($portsdir); - cvs("update", "-l", keys(%update_now)) - or errx(1, "error updating the category directories"); - foreach $category (keys(%update_now)) { - cd("$portsdir/$category"); - cvs("update", keys(%{$update_now{$category}})) - or errx(1, "error updating the '$category' category"); + last unless @update_now; + info("Pass $n:", @update_now); + + # Update the relevant sections of the ports tree + foreach $category (keys(%upd_port)) { + my @ports; # Ports to update + + if (!$upd_cat{$category}) { + cd($portsdir); + cvs("update", "-l", $category) + or bsd::errx(1, "error updating the '$category' category"); + $upd_cat{$category} = 1; + } + foreach $port (keys(%{$upd_port{$category}})) { + next if ($upd_port{$category}->{$port}); + push(@ports, $port); + $upd_port{$category}->{$port} = 1; + } + if (@ports) { + cd("$portsdir/$category"); + cvs("update", @ports) + or bsd::errx(1, "error updating the '$category' category"); + } } - foreach $category (keys(%update_now)) { - foreach $port (keys(%{$update_now{$category}})) { - if ($master = find_master($category, $port)) { - $additional{$master} = 1; + + # Process all unprocessed ports we know of so far + foreach $port (@update_now) { + # See if the port has an unprocessed master port + if (($master = find_master($port)) && !$processed{$master}) { + add_port($master, &REQ_MASTER); + info("Adding $master to head of line\n"); + unshift(@additional, $master); + # Need to process master before we continue + next; + } + + # Find the port's package name + if (!exists($strop{$port})) { + if ($strop{$port} = suppress(\&make, ($port, "-VPKGNAME"))) { + chomp($strop{$port}); + } else { + warnx("failed to obtain package name for $port"); + } + } + + # Find the port's dependencies + foreach $dependency (find_dependencies($port)) { + next if ($processed{$dependency}); + if ($reqd{$port} == &REQ_MASTER) { + add_port($dependency, &REQ_MASTER); + } else { + add_port($dependency, &REQ_IMPLICIT); } + info("Adding $dependency to back of line\n"); + push(@additional, $dependency); } + + # Mark port as processed + $processed{$port} = 1; } } } @@ -354,8 +608,8 @@ sub show_port_info($) { local *FILE; # File handle my $info; # Port info - sysopen(FILE, "$portsdir/$ports{$port}/pkg-descr", O_RDONLY) - or err(1, "can't read description for $port"); + sysopen(FILE, "$portsdir/$port/pkg-descr", O_RDONLY) + or bsd::err(1, "can't read description for $port"); $info = join("| ", <FILE>); close(FILE); print("+--- $port:\n| ${info}+---\n"); @@ -371,7 +625,7 @@ sub list_installed() { my $unknown; # Unknown ports opendir(DIR, $dbdir) - or err(1, "can't read database directory"); + or bsd::err(1, "can't read database directory"); print("Installed ports:\n"); foreach $port (readdir(DIR)) { next if ($port eq "." || $port eq ".." || ! -d "$dbdir/$port"); @@ -393,7 +647,8 @@ sub list_installed() { sub clean_port($) { my $port = shift; # Port to clean - make($port, "clean"); + make($port, "clean") + or bsd::warnx("failed to clean %s", $port); } # @@ -405,10 +660,10 @@ sub clean_tree() { # We could just cd to $portsdir and 'make clean', but it'd # be extremely noisy due to only having a partial tree - #errx(1, Dumper(\%strop)); foreach $port (keys(%ports)) { - if (-d "$portsdir/$ports{$port}") { - make($port, "clean", "NO_DEPENDS=yes"); + if (-d "$portsdir/$port") { + make($port, "clean", "NO_DEPENDS=yes") + or bsd::warnx("failed to clean %s", $port); } } } @@ -419,7 +674,8 @@ sub clean_tree() { sub fetch_port($) { my $port = shift; # Port to fetch - make($port, "fetch"); + make($port, "fetch") + or bsd::errx(1, "failed to fetch %s", $port); } # @@ -428,9 +684,21 @@ sub fetch_port($) { sub build_port($) { my $port = shift; # Port to build - make($port, - $packages ? ("package", "DEPENDS_TARGET=package") : "install", - "clean"); + my @makeargs; # Arguments to make() + + if ($packages) { + push(@makeargs, "package", "DEPENDS_TARGET=package"); + } else { + push(@makeargs, "install"); + } + if ($force) { + push(@makeargs, "-DFORCE_PKG_REGISTER"); + } + if (!$dontclean) { + push(@makeargs, "clean"); + } + make($port, @makeargs) + or bsd::errx(1, "failed to %s %s", $packages ? "package" : "build", $port); } # @@ -438,7 +706,7 @@ sub build_port($) { # sub usage() { - stderr("Usage: porteasy [-abcefhikluVv] [-d dir] [-D date]\n" . + stderr("Usage: porteasy [-abCceFfhikluVv] [-d dir] [-D date]\n" . " [-p dir] [-r dir] [-t tag] [port ...]\n"); exit(1); } @@ -463,6 +731,7 @@ Options: -a, --anoncvs Use the FreeBSD project's anoncvs server -b, --build Build required ports -c, --clean Clean the specified ports + -C, --dontclean Don't clean after build -e, --exclude-installed Exclude installed ports -f, --fetch Fetch distfiles -h, --help Show this information @@ -488,6 +757,7 @@ Report bugs to <des\@freebsd.org>. MAIN:{ my $port; # Port name my $err = 0; # Error count + my $need_index; # Need the index # Scan command line options Getopt::Long::Configure("auto_abbrev", "bundling"); @@ -495,9 +765,11 @@ MAIN:{ "a|anoncvs" => \$anoncvs, "b|build" => \$build, "c|clean" => \$clean, + "C|dontclean" => \$dontclean, "D|date=s" => \$date, "d|dbdir=s" => \$dbdir, "e|exclude-installed" => \$exclude, + "F|force-pkg-register" => \$force, "f|fetch" => \$fetch, "h|help" => \$help, "i|info" => \$info, @@ -528,25 +800,22 @@ MAIN:{ if (!@ARGV && (!$clean && !$info || $build || $fetch)) { usage(); } - + if ($portsdir !~ m/^\//) { $portsdir = `pwd` . $portsdir; $portsdir =~ s/\n/\//s; } if ($portsdir !~ m/\/ports\/?$/) { - errx(1, "ports directory must be named 'ports'"); + bsd::errx(1, "ports directory must be named 'ports'"); } $index = "$portsdir/INDEX"; - # 'package' implies 'build', which implies 'fetch'. + # 'package' implies 'build' if ($packages) { $build = 1; } - if ($build) { - $fetch = 1; - } # Set and check CVS root if ($anoncvs) { @@ -556,47 +825,60 @@ MAIN:{ $cvsroot = $ENV{'CVSROOT'}; } if (!$cvsroot) { - errx(1, "No CVS root, please use the -r option or set \$CVSROOT"); + bsd::errx(1, "No CVS root, please use the -r option or set \$CVSROOT"); + } + + # Check if we need the index + if (!@ARGV && $info) { + $need_index = 1; + } + foreach $port (@ARGV) { + if ($port !~ m/\//) { + $need_index = 1; + } } - # Read the ports index - if ($update) { + # Step 1: read the ports index + if ($need_index) { update_index(); + read_index(); } - read_index(); - # Read list of explicitly required ports + # Step 2: build list of explicitly required ports foreach $port (@ARGV) { $err += add_port($port, &REQ_EXPLICIT); } if ($err) { - errx(1, "some required ports were not found."); + bsd::errx(1, "some required ports were not found."); + } + + # Step 3: update port directories and discover dependencies + if (!($build || $fetch || ($info && @ARGV) || $list)) { + $update = 0; } + update_ports_tree(keys(%reqd)); - # Deselect ports which are already installed + # Step 4: deselect ports which are already installed if ($exclude) { foreach $port (keys(%reqd)) { - if (-d "$dbdir/$port") { + if ((exists($installed{$port}) && $installed{$port} > 0) || + -d "$dbdir/$strop{$port}") { info("$port is already installed"); delete $reqd{$port}; } } } - # List required packages + # Step 5: list selected ports if ($list) { foreach $port (sort(keys(%reqd))) { + next if ($reqd{$port} == &REQ_MASTER); print((($reqd{$port} & &REQ_EXPLICIT) ? " * " : " "), - "$port\n"); + "$port ($strop{$port})\n"); } } - # Update port directories - if ($update && ($build || $fetch || $info)) { - update_ports_tree(keys(%reqd)); - } - - # Show info + # Step 6: show info (or list installed packages) if ($info) { if (!@ARGV) { list_installed(); @@ -609,7 +891,7 @@ MAIN:{ } } - # Clean + # Step 7: clean the ports directories (or the entire tree) if ($clean) { if (!@ARGV) { clean_tree(); @@ -622,16 +904,17 @@ MAIN:{ } } - # Fetch ports + # Step 8: fetch distfiles if ($fetch) { foreach $port (keys(%reqd)) { - fetch_port($port); + if ($reqd{$port} != &REQ_MASTER) { + fetch_port($port); + } } } - # Build ports - only the explicitly required ones, since the - # 'already installed' test may give false negatives (most commonly - # XFree86) + # Step 9: build ports - only the explicitly required ones, since + # some dependencies (most commonly XFree86) may be bogus. if ($build || $packages) { foreach $port (keys(%reqd)) { if (!($reqd{$port} & &REQ_IMPLICIT)) { |