#!/bin/bash # # pt -- the MacPorts port tool" # Copyright (c)2007-2009 Alexander Barton, alex@barton.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # Please read the file COPYING, README and AUTHORS for more information. # NAME=`basename "$0"` VERSION="0.2.0" DATE="2008-12-14" QUIET= PORT_VERBOSE= VERBOSE= DEBUG= UPGRADE_REMOVE_INACTIVE=1 CLEAN_WORKDIR=1 declare -i WIDTH=${COLUMNS:-80} LINE="-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------" Version() { # Display version information. echo "$NAME $VERSION ($DATE) -- the MacPorts port tool" echo "Copyright (c)2007-2009 Alexander Barton, alex@barton.de" echo echo "This is free software; see the source for copying conditions. There is NO" echo "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." echo } Usage() { # Display some usage information and exit. Version echo "Usage: $0 [ ...]" echo echo "Available commands:" echo " deps [ [...]] -- examine dependencies" echo " install [ [...]] -- install port(s)" echo " remove [ [...]] -- remove port(s)" echo " clean [ [...]] -- clean port build files" echo " update -- update port database" echo " outdated -- list all outdated ports" echo " upgrade -- upgrade outdated port(s)" echo " freshup -- update port database & list outdated ports" echo " search -- search for port(s)" echo " list -- list port(s)" echo " info -- display details of a port" echo " status -- show MacPorts status" echo echo "The following global options are available:" echo " -q: quiet mode, little output +q: no quiet mode, normal output" echo " -v: enable verbose mode +v: disable verbose mode" echo " -V: verbose port(1) messages +V: no verbose port(1) messages" echo " -d: enable debug mode +d: disable verbose mode" echo Clean_Exit 1 } Init() { # Initialize "state machine", clear temporary files. Verbose_Msg "Initializing port states ..." port list >"$TMPDIR/available" Debug_Msg "Listing installed ports ..." port installed | grep "^ " >"$TMPDIR/installed" Debug_Msg "Listing inactive ports ..." port list inactive >"$TMPDIR/inactive" echo -n '' >"$TMPDIR/required.all" echo -n '' >"$TMPDIR/missing.all" echo -n "" >"$TMPDIR/outdated" echo -n "" >"$TMPDIR/outdated.info" declare -i c_avail=`cat "$TMPDIR/available" | wc -l` declare -i c_inst=`cat "$TMPDIR/installed" | wc -l` declare -i c_inact=`cat "$TMPDIR/inactive" | wc -l` [ $c_inst -eq $c_avail ] && c_inst=0 [ $c_inact -eq $c_avail ] && c_inact=0 [ $c_inact -eq 0 ] && s="" || s=" ($c_inact inactive)" Verbose_Msg "$c_avail ports available, $c_inst installed$s." } Reset() { # Reset "required" and "missing" package lists, move saved # packages to "required.all"/"missing.all" lists for later # processing. Debug_Msg "Resetting internal state ..." rm -f "$TMPDIR/tmp"* [ -r "$TMPDIR/required" ] \ && cat "$TMPDIR/required" >>"$TMPDIR/required.all" echo -n '' >"$TMPDIR/required" [ -r "$TMPDIR/missing" ] \ && cat "$TMPDIR/missing" >>"$TMPDIR/missing.all" echo -n '' >"$TMPDIR/missing" } Check_Root_Perm() { # Check, if this script is running with root privileges. [ "$UID" -eq 0 -o "$EUID" -eq 0 ] && return 0 Error_Msg "This function must be executed with root privileges!" Clean_Exit 1 } Debug_Tmpdir() { # List contents of temporary directory to console. echo "--- $TMPDIR ---"; ls -al "$TMPDIR"; echo "---" } Debug_Dump_File() { # Dump contents of a file to console. echo "--- $1 ---"; cat "$1"; echo "---" } Clean_Exit() { # Exit this scripts after cleaning up. # $1: exit code Debug_Msg "Cleaning up ..." [ -n "$TMPDIR" ] && rm -rf "$TMPDIR" Debug_Msg "Done. Exit code: $1" exit $1 } Clean_Exit_Signal() { # Exit this script with exit code 127, used when an interup signal # (SIGINT) has been catched. echo Clean_Exit 127 } Msg() { # Display message, if not running in "quiet" mode. # $*: message to display. [ -n "$QUIET" ] && return if [ "$1" = "-n" ]; then shift echo -n "$*" else echo "$*" fi } Msg_NoQuiet() { # Display message, even when running in "quiet" mode. # $*: message to display. if [ "$1" = "-n" ]; then shift echo -n "$*" else echo "$*" fi } Verbose_Msg() { # Display message when running in "debug" or "verbose" mode. # $*: message to display. [ -z "$QUIET" -a -n "$VERBOSE" ] && echo $* } Debug_Msg() { # Display message when running in "debug" mode. # $*: message to display. [ -n "$DEBUG" ] && echo $* } Warn_Msg() { # Display warning message. # $*: message to display. echo "Warning: $*" } Error_Msg() { # Display error message. # $*: message to display. echo "Error: $*" } Prompt() { tty >/dev/null 2>&1 [ $? -ne 0 ] && return 0 x="_" Msg_NoQuiet -n "$* [Y/n] " while [ "$x" != "" -a "$x" != "n" -a "$x" != "N" -a "$x" != "y" -a "$x" != "Y" ]; do read -s -n 1 x done [ -z "$x" ] && x="yes" [ "$x" = "n" ] && x="no" [ "$x" = "y" ] && x="yes" Msg_NoQuiet "- $x" [ "$x" = "no" ] && return 1 || return 0 } PortCmd() { # Call the real "port(1)" command, show and hide output as configured. # $*: arguments for port(1). local args="-q " [ -n "$VERBOSE" ] && args="" [ -n "$PORT_VERBOSE" ] && args="-v " [ -n "$DEBUG" ] && args="-v -d " local cmd="port $args$*" declare -i a=${#cmd}+4; a=$WIDTH-$a; a=$a/2 local bottom="$LINE" local top="${LINE:0:$a}[ $cmd ]${LINE}" local r=1 if [ "$args" = "" ]; then $cmd | while read x; do msg=$(echo "$x"|sed -e 's/--->//g'|sed -e 's/^ //g') msg=" - $msg" if [ ${#msg} -ge $WIDTH ]; then declare -i w=$WIDTH-3 echo "${msg:0:$w}..." else echo "$msg" fi done; r=$? elif [ "$args" = "-v " -o "$args" = "-v -d " ]; then Msg_NoQuiet "${top:0:$WIDTH}" $cmd; r=$? Msg_NoQuiet "${bottom:0:$WIDTH}" else $cmd >/dev/null 2>&1 ; r=$? fi return $r } Port_Exists() { # Check, if the given port exists # $1: port port info "$1" 2>/dev/null | grep 'Maintainers: ' >/dev/null; local r=$? if [ $r -ne 0 ]; then Error_Msg "Port \"$1\" does not exist!" return $r fi Debug_Msg "Port \"$1\" exists. Ok." return 0 } Port_Installed() { # Check, if the given port is installed # $1: port Port_Exists "$1" [ $? -eq 0 ] || return 1 grep "^ $1 " "$TMPDIR/installed" >/dev/null 2>&1; local r=$? if [ $r -ne 0 ]; then Error_Msg "Port \"$1\" is not installed!" return $r fi Debug_Msg "Port \"$1\" is installed. Ok." return 0 } Dump_Package_File() { # Display contents of a "pt package-list file", like "required[.all]". # $1: title text # $2: package-list file name # $3: "u": show only un-installed ports pkgs=$(cat "$2" | sort -u) [ -z "$pkgs" ] && return 0 Msg_NoQuiet "$1" Msg_NoQuiet -n " " declare -i x=2 declare -i count=0 for d in $pkgs; do grep "^ $d " "$TMPDIR/installed" >/dev/null 2>&1; local r=$? if [ "$3" = "u" ]; then [ $r -eq 0 ] && continue else [ $r -eq 0 ] && d="$d[i]" fi x=$x+${#d}+1 if [ $x -gt $WIDTH ]; then Msg_NoQuiet; Msg_NoQuiet -n " " x=${#d}+3 fi Msg_NoQuiet -n "$d " count=$count+1 done Msg_NoQuiet return $count } Count_Package_File() { # Return number of packages listed in a "pt package-list file". # $1: package-list file name # $2: "u": show only un-installed ports pkgs=$(cat "$1" | sort -u) declare -i count=0 for d in $pkgs; do grep "^ $d " "$TMPDIR/installed" >/dev/null 2>&1; local r=$? if [ "$2" = "u" ]; then [ $r -eq 0 ] && continue fi count=$count+1 done return $count } Deps_r() { local phase="" grep "^$1\$" "$TMP4" >/dev/null 2>&1 if [ $? -eq 0 ]; then Debug_Msg "Dependencies for \"$1\" already checked. Skipping." return fi Debug_Msg "Checking dependencies for \"$1\" ..." echo "$1" >>"$TMP4" port deps "$1" | while read x; do if [ "$x" = "$1 has no dependencies" ]; then phase=""; continue; fi if [ "$x" = "$1 has build dependencies on:" ]; then phase="build"; continue fi if [ "$x" = "$1 has library dependencies on:" ]; then phase="lib"; continue fi if [ "$x" = "$1 has runtime dependencies on:" ]; then phase="runtime"; continue fi case "$phase" in "build") echo "$1:$x" >>"$TMP1" Deps_r "$x" ;; "lib") echo "$1:$x" >>"$TMP2" Deps_r "$x" ;; "runtime") echo "$1:$x" >>"$TMP3" Deps_r "$x" ;; *) Warn_Msg "Unknown phase, token \"$x\"!?" esac done } Deps_a() { # 1: level # 2: file # 3: type # 4: root-element declare -i level=$1 local deps="" local t="" [ -z "$4" ] && t=$(head -n 1 "$2" | cut -d':' -f1) || t="$4" while [ -n "$t" ]; do deps=$(grep "^$t:" "$2" 2>/dev/null | sort -u) grep -v "^$t:" "$2" >"$TMP" 2>/dev/null cat "$TMP" >"$2" for d in $deps; do port=$(echo "$d" | cut -d':' -f1) req=$(echo "$d" | cut -d':' -f2) declare -i x=0 while [ $x -lt $level ]; do [ -n "$VERBOSE" -a -z "$QUIET" ] && echo -n ' ' x=$x+1 done Verbose_Msg "Port \"$t\" depends on $req" echo "$req" >>"$TMPDIR/required" Deps_a $level+1 "$2" "$3" "$req" done [ "$level" -le 1 ] && t=$(head -n 1 "$2" | cut -d':' -f1) || t="" done } Deps() { # Display dependancies of a port # $1: port Port_Exists "$1"; local r=$? [ $r -ne 0 ] && return $r touch "$TMP1" "$TMP2" "$TMP3" "$TMP4" Msg "Analyzing port \"$1\" ..." Deps_r "$1" build_deps=$(cat "$TMP1") lib_deps=$(cat "$TMP2") runtime_deps=$(cat "$TMP3") if [ -z "$build_deps" -a -z "$lib_deps" -a -z "$runtime_deps" ]; then Msg "Port \"$1\" has no dependencies." return 0 fi if [ -n "$build_deps" ]; then Verbose_Msg; Verbose_Msg "Build dependencies:" Deps_a 1 "$TMP1" build fi if [ -n "$lib_deps" ]; then Verbose_Msg; Verbose_Msg "Library dependencies:" Deps_a 1 "$TMP2" lib fi if [ -n "$runtime_deps" ]; then Verbose_Msg; Verbose_Msg "Runtime dependencies:" Deps_a 1 "$TMP3" runtime fi Msg deps=$(cat "$TMPDIR/required" | sort -u) declare -i count=$(cat "$TMPDIR/required" | sort -u | wc -l ) not_inst="" [ "$count" -eq 1 ] \ && Msg_NoQuiet "The following port is required by \"$1\":" \ || Msg_NoQuiet "The following $count ports are required by \"$1\":" Msg_NoQuiet -n " " declare -i count=0 declare -i x=2 for d in $deps; do grep "^ $d " "$TMPDIR/installed" >/dev/null 2>&1; local r=$? if [ $r -eq 0 ]; then d="$d[i]" else count=$count+1 not_inst="$not_inst $d" fi x=$x+${#d}+1 if [ $x -gt $WIDTH ]; then Msg_NoQuiet; Msg_NoQuiet -n " " declare -i x=${#d}+3 fi Msg_NoQuiet -n "$d " done Msg_NoQuiet if [ -n "$not_inst" ]; then [ "$count" -eq 1 ] \ && Msg_NoQuiet "The following port (probably) needs to be installed:" \ || Msg_NoQuiet "The following $count ports (probably!) need to be installed:" Msg_NoQuiet -n " " declare -i x=2 for d in $not_inst; do x=$x+${#d}+1 if [ $x -gt $WIDTH ]; then Msg_NoQuiet; Msg_NoQuiet -n " " declare -i x=${#d}+3 fi Msg_NoQuiet -n "$d " echo "$d" >>"$TMPDIR/missing" done Msg_NoQuiet else Msg_NoQuiet "All required ports are already installed." fi Msg return 0 } Port_Install() { # Install a given port, and its dependencies. # $*: port, including version number, variant(s), ... Msg_NoQuiet "Installing port \"$p\" (and dependencies) ..." local args="" [ -n "$CLEAN_WORKDIR" ] && args="$args -c" PortCmd $args install $* if [ $? -ne 0 ]; then Error_Msg "Installation of \"$1\" failed!" return 1 fi return 0 } Port_Upgrade() { # Clean build directory of a port # $1: port # $2: old version # $3: new version Msg_NoQuiet "Upgrading port \"$1\" (and dependencies): $2 -> $3 ..." local args="" [ -n "$CLEAN_WORKDIR" ] && args="$args -c" [ -n "$UPGRADE_REMOVE_INACTIVE" ] && args="$args -fu" PortCmd $args upgrade -n $port if [ $? -ne 0 ]; then Error_Msg "Failed to upgrade port \"$1\"!" return 1 fi return 0 } Port_Clean() { # Clean build directory of a port # $1: port Msg_NoQuiet "Cleaning build directory of port \"$1\" ..." PortCmd clean --all "$1" if [ $? -ne 0 ]; then Error_Msg "Failed to clean port \"$1\"!" return 1 fi return 0 } Port_Remove() { # Remove a given port # $1: port Msg_NoQuiet "Removing port \"$1\" ..." PortCmd uninstall "$1" if [ $? -ne 0 ]; then Error_Msg "Failed to remove port \"$1\"!" return 1 fi return 0 } Install() { if [ $# -lt 1 ]; then Error_Msg "No ports requested!?" return 1 fi deps=$(cat "$TMPDIR/required.all" | sort -u) not_inst="" cat "$TMPDIR/missing.all" >"$TMPDIR/install.all" for p in $*; do echo "$p" >>"$TMPDIR/install.all" done for p in $*; do echo "$p" >>"$TMPDIR/requested.all" done Verbose_Msg if [ -z "$QUIET" ]; then Count_Package_File "$TMPDIR/requested.all"; c=$? [ $c -eq 1 ] && s=" has" || s="s have" Dump_Package_File \ "The following $c port$s been requested:" \ "$TMPDIR/requested.all" fi Count_Package_File "$TMPDIR/required.all"; c_req=$? [ $c_req -eq 1 ] && s=" is" || s="s are" Dump_Package_File \ "The following $c_req port$s required to fulfil dependencies:" \ "$TMPDIR/required.all" Count_Package_File "$TMPDIR/missing.all"; c_add=$? [ $c_add -eq 1 ] && s=" (probably!) needs" || s="s (probably!) need" Dump_Package_File \ "The following $c_add additional port$s to be installed:" \ "$TMPDIR/missing.all" Count_Package_File "$TMPDIR/install.all" "u"; c_all=$? if [ $c_all -lt 1 ]; then Msg_NoQuiet "No new ports to install."; Msg return 0 fi [ $c_all -eq 1 ] && s="" || s="s" Dump_Package_File \ "The following $c_all NEW port$s will be installed (if required):" \ "$TMPDIR/install.all" "u" c_inst=$? ports=$(tail -r "$TMPDIR/missing.all" | uniq ) ports="$ports $*" if [ -n "$DEBUG" ]; then Debug_Msg "Order of installation:" Debug_Msg " " $ports fi [ "$#" -eq 1 ] && s1=" has" || s1="s have" [ "$c_inst" -eq 1 ] && s2="" || s2="s" Msg_NoQuiet "$# port$s1 been requested, $c_inst port$s2 will be installed." if [ $# -ne $c_inst ]; then Prompt "Do you want to continue?" if [ $? -ne 0 ]; then Msg "Aborting."; Msg return 1 fi fi Msg declare -i errors=0 declare -i count=0 for p in $*; do Port_Install "$p" [ $? -eq 0 ] || errors=$errors+1 count=$count+1 done Msg [ $count -eq 1 ] && s=" has" || s="s have" Msg "$count requested port$s been installed." if [ $errors -gt 0 ]; then [ $errors -gt 1 ] && s="s" || s="" Error_Msg "There have been failures, see above!"; Msg local r=1 else local r=0 fi Init return $r } Init_Outdated() { Msg "Searching for outdated ports ..." echo -n "" >"$TMPDIR/outdated" echo -n "" >"$TMPDIR/outdated.info" port outdated | \ grep -v "No installed ports are outdated" | \ grep -v "The following installed ports are outdated" | \ awk "{ print \$1, \$2, \$3, \$4 }" | while read port v1 a v2; do echo "$port" >>"$TMPDIR/outdated" echo "$port $v1 $a $v2" >>"$TMPDIR/outdated.info" done } List_Outdated() { Init_Outdated Count_Package_File "$TMPDIR/outdated" if [ $? -lt 1 ]; then Msg "No installed ports are outdated." return 0 fi Dump_Package_File \ "The following installed ports are outdated and will be UPGRADED:" \ "$TMPDIR/outdated" c_out=$? if [ -n "$VERBOSE" ]; then echo "Upgrade details:" cat "$TMPDIR/outdated.info" | while read port v1 a v2; do echo " $port $v1 -> $v2" done fi [ $c_out -gt 100 ] && c_out=100 return $c_out; } Upgrade() { List_Outdated && return 0 Prompt "Do you want to continue?" if [ $? -ne 0 ]; then Msg "Aborting."; Msg return 1 fi Msg declare -i errors=0 cat "$TMPDIR/outdated.info" | while read port v1 a v2; do Port_Upgrade "$port" "$v1" "$v2" [ $? -eq 0 ] || errors=$errors+1 done Msg if [ $errors -gt 0 ]; then [ $errors -gt 1 ] && s="s" || s="" Warn_Msg "$errors error$s occured during upgrading!" Msg local r=1 else local r=0 fi Init_Outdated Count_Package_File "$TMPDIR/outdated"; c=$? if [ $c -gt 0 ]; then if [ $c -eq 1 ]; then s1="is"; s2="" else s1="are"; s2="s" fi Warn_Msg "There $s1 still $c outdated port$s2 installed!" fi Init return $r } Update() { Status "Actual ports base version" -q Msg "Updating installed port software and database ..." port -q selfupdate; local r=$? if [ $r -eq 0 ]; then Status "New ports base version" else Error_Msg "Failed to update port archive!" fi return $r } Clean() { local r=0 declare -i errors=0 for p in $*; do Port_Exists "$p" if [ $? -eq 0 ]; then Port_Clean "$p" [ $? -ne 0 ] && errors=$errors+1 else errors=$errors+1 fi done if [ $errors -gt 0 ]; then Msg_NoQuiet [ $errors -ne 1 ] && s="s" || s="" Error_Msg "$errors failure$s while cleaning ports!" r=1 fi return $r } Remove() { local r=0 declare -i errors=0 for p in $*; do Port_Installed "$p" if [ $? -eq 0 ]; then Port_Remove "$p" [ $? -ne 0 ] && errors=$errors+1 else errors=$errors+1 fi done if [ $errors -gt 0 ]; then [ $errors -ne 1 ] && s="s" || s="" Error_Msg "$errors failure$s while removing ports!" r=1 fi return $r } Status() { local r=0 Init; r=$? q=$QUIET; [ "$2" != '-q' ] && QUIET= [ "$1" = "" ] \ && Msg -n "Ports base version: " \ || Msg -n "$1: " version=$( port --version 2>/dev/null | grep "MacPorts" ) [ $? -eq 0 ] || version=$( port version | cut -d' ' -f2 ) Msg "$version" QUIET=$q return $r } trap Clean_Exit_Signal SIGINT # read in defaults, if available [ -r "/etc/$NAME.conf" ] && . "/etc/$NAME.conf" while true; do case "$1" in "-v") VERBOSE=1; QUIET= shift ;; "+v") VERBOSE= shift ;; "-V") PORT_VERBOSE=1; VERBOSE=1 shift ;; "+V") PORT_VERBOSE= shift ;; "-q") QUIET=1; VERBOSE=; DEBUG= shift ;; "+q") QUIET= shift ;; "-d") DEBUG=1; VERBOSE=1; QUIET= shift ;; "+d") DEBUG= shift ;; "--usage"|"--help"|"-*"|"+*") Usage ;; "--version") Version Clean_Exit 0 ;; *) break; esac done TMPDIR=`mktemp -d /tmp/${NAME}.XXXXXX` || exit 2 Debug_Msg "Temporary directory is \"$TMPDIR\"." TMP="$TMPDIR/tmp" TMP1="$TMPDIR/tmp.1" TMP2="$TMPDIR/tmp.2" TMP3="$TMPDIR/tmp.3" TMP4="$TMPDIR/tmp.4" r=0 case "$1" in "clean") shift Check_Root_Perm [ $# -lt 1 ] && Usage Init Clean $* Clean_Exit $? ;; "deps") shift [ $# -lt 1 ] && Usage Init for p in $*; do Reset Deps "$p"; r=$? [ $r -ne 0 ] && Clean_Exit $r done Clean_Exit $r ;; "install") shift Check_Root_Perm [ $# -lt 1 ] && Usage Init Verbose_Msg "Building port dependencies ..." for p in $*; do Port_Exists "$p" [ $? -ne 0 ] && Clean_Exit 1 Reset Deps "$p" >/dev/null; r=$? [ $r -ne 0 ] && Clean_Exit $r done Reset Install $* Clean_Exit $? ;; "list") shift [ $# -lt 1 ] && Usage port list $* Clean_Exit $? ;; "outdated"|"list-outdated") List_Outdated Clean_Exit $? ;; "update"|"selfupdate") shift Check_Root_Perm [ $# -ne 0 ] && Usage Update Clean_Exit $? ;; "upgrade"|"dist-upgrade") shift Check_Root_Perm [ $# -gt 0 ] && Usage Init Upgrade Clean_Exit $? ;; "freshup"|"fresh") shift Check_Root_Perm [ $# -ne 0 ] && Usage Update List_Outdated Clean_Exit $? ;; "go"|"up") shift Check_Root_Perm [ $# -ne 0 ] && Usage Update Upgrade Clean_Exit $? ;; "remove"|"uninstall") shift Check_Root_Perm [ $# -lt 1 ] && Usage Init Remove $* Clean_Exit $? ;; "search") shift [ $# -lt 1 ] && Usage port search $* Clean_Exit $? ;; "info") shift [ $# -lt 1 ] && Usage port info $* Clean_Exit $? ;; "status") shift [ $# -ne 0 ] && Usage VERBOSE=1; Status Clean_Exit $? ;; *) Usage esac # -eof-