aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--misc/porteasy/Makefile2
-rw-r--r--misc/porteasy/src/porteasy.8128
-rw-r--r--misc/porteasy/src/porteasy.pl523
-rw-r--r--ports-mgmt/porteasy/Makefile2
-rw-r--r--ports-mgmt/porteasy/src/porteasy.8128
-rw-r--r--ports-mgmt/porteasy/src/porteasy.pl523
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)) {