--- /usr/bin/euse 2009-05-08 15:03:39.000000000 -0500 +++ /usr/bin/euse 2009-06-11 21:05:34.000000000 -0500 @@ -13,6 +13,7 @@ MAKE_GLOBALS_PATH=/etc/make.globals MAKE_PROFILE_PATH=/etc/make.profile MAKE_CONF_BACKUP_PATH=/etc/make.conf.euse_backup +PACKAGE_USE_PATH=/etc/portage/package.use [ -z "${MODE}" ] && MODE="showhelp" # available operation modes: showhelp, showversion, showdesc, showflags, modify @@ -32,13 +33,14 @@ -E | --enable) MODE="modify"; ACTION="add";; -D | --disable) MODE="modify"; ACTION="remove";; -P | --prune) MODE="modify"; ACTION="prune";; + -p | --package) MODE="modify"; shift; PACKAGE=${1};; -*) echo "ERROR: unknown option ${1} specified." echo MODE="showhelp" ;; "%active") - get_useflags + get_portageuseflags ARGUMENTS="${ARGUMENTS} ${ACTIVE_FLAGS[9]}" ;; *) @@ -55,6 +57,10 @@ exit 1 } +warn() { + echo "WARNING: ${1}" +} + get_real_path() { set -P cd "$1" @@ -82,6 +88,7 @@ done # [ ! -r "$(get_make_defaults)" ] && error "$(get_make_defaults) is not readable" [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && error ""${MAKE_CONF_PATH}" is not writable" + [ "${MODE}" == "modify" -a -s "${PACKAGE_USE_PATH}" -a ! -w "${PACKAGE_USE_PATH}" ] && error ""${PACKAGE_USE_PATH}" is not writable" } showhelp() { @@ -101,13 +108,17 @@ -E, --enable - enable the given useflags -D, --disable - disable the given useflags -P, --prune - remove all references to the given flags from - make.conf to revert to default settings + make.conf and package.use to revert to default + settings + -p, --package - used with -E, -D, to apply to a specific + package only Notes: ${PROGRAM_NAME} currently only works for global flags defined - in make.globals, make.defaults or make.conf, it doesn't handle - use.defaults, use.mask or package.use yet (see portage(5) for details on - these files). It also might have issues with cascaded profiles. - If multiple options are specified only the last one will be used. + in make.globals, make.defaults or make.conf, and local flags defined + in package.use and individual package ebuilds, it doesn't handle + use.defaults or use.mask yet (see portage(5) for details on these + files). It also might have issues with cascaded profiles. If + multiple options are specified only the last one will be used. HELP } @@ -147,13 +158,44 @@ print ' '.join(r)" } +# Similar to reduce_incrementals except converts lines from package atoms +# in /etc/portage/package.use files to lines of "pkg {[-]flag}*" +reduce_package_use() { + echo "${@}" | python -c "import sys,re +h={}; atom=re.compile(r'[<>]?=?([a-z][\w/-]+[a-z])(-[\dpr._*-]+)?', re.I); +getflags=re.compile(r'(-?[\w*-]+)') +for x in sys.stdin.read().split('\n'): + if not x: continue + parts = x.lstrip().split(' ',1) + if len(parts)==1: continue + try: + pkg = atom.match(parts[0]).group(1) + except: + continue + flags = getflags.findall(parts[1]) + if not pkg in h: h[pkg]=[] + r=h[pkg] + for x in flags: + if x[0] == '-' and x[1:] in r: + r.remove(x[1:]) + r.append(x) + elif x[0] != '-' and '-'+x in r: + r.remove('-'+x) + r.append(x) + elif x == '-*': + r = h[pkg] = ['-*'] + elif x not in r: + r.append(x) +print '\n'.join([' %s %s ' % (pkg,' '.join(flgs)) for pkg,flgs in h.iteritems() if len(flgs)])" +} + # the following function creates a bash array ACTIVE_FLAGS that contains the # global use flags, indexed by origin: 0: environment, 1: make.conf, -# 2: make.defaults, 3: make.globals +# 2: make.defaults, 3: make.globals, 4: package.use, 5: ebuild IUSE get_useflags() { - # only calculate once as calling emerge is painfully slow + # only calculate once [ -n "${USE_FLAGS_CALCULATED}" ] && return - + # backup portdir so get_portdir() doesn't give false results later portdir_backup="${PORTDIR}" @@ -174,10 +216,31 @@ USE="${ACTIVE_FLAGS[0]}" PORTDIR="${portdir_backup}" + # Parse through /etc/portage/package.use + if [[ -d ${PACKAGE_USE_PATH} ]]; then + ACTIVE_FLAGS[4]="$( find ${PACKAGE_USE_PATH} -type f -exec cat {} \; \ + -exec echo \; | sed -re "s/#.*$//" )" + elif [[ -e ${PACKAGE_USE_PATH} ]]; then + ACTIVE_FLAGS[4]="$( cat ${PACKAGE_USE_PATH} | sed -re "s/#.*$//")" + fi + # Simplify ACTIVE_FLAGS[4] to be lines of pkg {[-]flag}* + ACTIVE_FLAGS[4]="$(reduce_package_use "${ACTIVE_FLAGS[4]}")" + # + # ACTIVE_FLAGS[5] reserved for USE flags defined in ebuilds and + # is generated/maintained in the get_useflaglist_ebuild() function + # + + USE_FLAGS_CALCULATED=1 +} + +# Fetch USE flags reported active by Portage +get_portageuseflags() { + # only calculate once as calling emerge is painfully slow + [ -n "${_PORTAGE_USE_FLAGS_CALCULATED}" ] && return # get the currently active USE flags as seen by portage, this has to be after # restoring USE or portage won't see the original environment ACTIVE_FLAGS[9]="$(emerge --info | grep 'USE=' | cut -b 5- | sed -e 's:"::g')" #' - USE_FLAGS_CALCULATED=1 + _PORTAGE_USE_FLAGS_CALCULATED=1 } # get the list of all known USE flags by reading use.desc and/or use.local.desc @@ -194,6 +257,39 @@ egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" | cut -d: -f 2 | cut -d\ -f 1 fi } +get_useflaglist_ebuild() { + # $1 package atom (app-editor/vim) + # + local known=$(echo "${ACTIVE_FLAGS[5]}" | egrep ^${1} | sed -e s:^${1}\s::) + if [[ ! -z $known ]]; then + echo $known + return + fi + # Fetch list of package ebuilds matching the query from equery. + # Since determining the current version of a package requires + # scanning for available ones, inpecting the KEYWORDS variable + # and then checking in a heap of other places for exceptions, + # equery is a simple alternative + if [[ "$MODE" != "showinstdesc" ]]; then + local exclude_installed="--exclude-installed" + fi + # The last one in the list would be the one to be installed. + local ebuild + for T in $(equery -N list --portage-tree $exclude_installed \ + ${1} | grep "${1}" | egrep -v "\[M.\]" | \ + cut -d\ -f 5 | sed -e s:^[^/]*/::); do + ebuild=${T} + done + # Open the ebuild file and retrieve defined USE flags + if [[ ! -e "$(get_portdir)/${1}/${ebuild}.ebuild" ]]; then + return 1 + fi + local flags="$(cat $(get_portdir)/${1}/${ebuild}.ebuild | \ + python -c "import re,sys; print re.compile(r'^IUSE=\"[^\"]*\"', re.M)\ + .search(sys.stdin.read()).group(0)")" + ACTIVE_FLAGS[5]="$(echo -ne ${ACTIVE_FLAGS[5]}$'\n'${1} ${flags})" + echo ${flags} +} # get all make.defaults by traversing the cascaded profile directories get_all_make_defaults() { @@ -238,29 +334,68 @@ # 3: echo value for positive (and as lowercase for negative) test result, # 4 (optional): echo value for "missing" test result, defaults to blank get_flagstatus_helper() { - if echo " ${ACTIVE_FLAGS[${2}]} " | grep " ${1} " > /dev/null; then - echo -n "${3}" - elif echo " ${ACTIVE_FLAGS[${2}]} " | grep " -${1} " > /dev/null; then - echo -n "$(echo ${3} | tr [[:upper:]] [[:lower:]])" + if [[ -z ${flags} ]]; then + local flags=${ACTIVE_FLAGS[${2}]} + fi + if echo " $flags " | egrep " \+?${1} " > /dev/null; then + echo -e -n "${3}" + return 0 + elif echo " $flags " | grep " -${1} " > /dev/null; then + echo -e -n "$(echo ${3} | tr [:upper:]+ [:lower:]-)" + return 0 else echo -n "${4:- }" + return 1 fi } +get_flagstatus_helper_pkg() { + if [[ -z ${flags} ]]; then + local flags=$(echo " ${ACTIVE_FLAGS[${2}]} " | grep "${5} "); + fi; + if [[ -z ${5} || -z ${flags} ]]; then + echo -e -n "${4:- }" + return 1; + fi + get_flagstatus_helper "$@" + return $? +} +get_flagstatus_helper_ebuild() { + if [[ -z ${5} ]]; then + echo -e -n "${4:- }" + return 1 + fi + local flags=$( get_useflaglist_ebuild "${5}" ) + get_flagstatus_helper_pkg "$@" + return $? +} # prints a status string for the given flag, each column indicating the presence # for portage, in the environment, in make.conf, in make.defaults and in make.globals. -# full positive value would be "[+ECDG]", full negative value would be [-ecdg], -# full missing value would be "[- ]" (portage only sees present or not present) +# full positive value would be "[+ECDGPB]", full negative value would be [-ecdgpb], +# full missing value would be "[- ]" (portage only sees present or not present) get_flagstatus() { get_useflags + [[ ! -z ${2} ]] && get_useflaglist_ebuild "${2}" > /dev/null - echo -n '[' - get_flagstatus_helper "${1}" 9 "+" "-" - get_flagstatus_helper "${1}" 0 "E" - get_flagstatus_helper "${1}" 1 "C" - get_flagstatus_helper "${1}" 2 "D" - get_flagstatus_helper "${1}" 3 "G" - echo -n '] ' + local E=$(get_flagstatus_helper "${1}" 0 "E") + local C=$(get_flagstatus_helper "${1}" 1 "C") + local D=$(get_flagstatus_helper "${1}" 2 "D") + local G=$(get_flagstatus_helper "${1}" 3 "G") + local P=$(get_flagstatus_helper_pkg "${1}" 4 "P" "" "${2}") + local B=$(get_flagstatus_helper_ebuild "${1}" 4 "B" "" "${2}") + # + # Use flag precedence is defined (at least) at: + # http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=2 + for location in "E" "P" "C" "D" "G" "B"; do + if [[ ${!location} == $location ]]; then + local ACTIVE="+" + break + elif [[ ${!location} != " " ]]; then + local ACTIVE="-" + break + fi + done + echo -n "[${ACTIVE:--}$E$C$D$G$P$B] " } # faster replacement to `portageq portdir` @@ -327,7 +462,7 @@ pkg="$(echo $line | cut -d\| -f 1)" flag="$(echo $line | cut -d\| -f 2)" desc="$(echo $line | cut -d\| -f 3)" - get_flagstatus "${flag}" + get_flagstatus "${flag}" "${pkg}" printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc}" done fi @@ -351,8 +486,8 @@ args=("${@:-*}") case "${SCOPE}" in - "global") echo "global use flags (searching: ${args})";; - "local") echo "local use flags (searching: ${args})";; + "global") echo "global use flags (searching: ${args[@]})";; + "local") echo "local use flags (searching: ${args[@]})";; *) SCOPE="global" showinstdesc "${args[@]}" echo SCOPE="local" showinstdesc "${args[@]}" @@ -366,7 +501,7 @@ args="$(get_useflaglist | sort -u)" fi - set "${args[@]}" + set ${args[@]} while [ -n "${1}" ]; do case "${SCOPE}" in @@ -394,10 +529,10 @@ # print name only if package is installed # NOTE: If we implement bug #114086 's enhancement we can just use the # exit status of equery instead of a subshell and pipe to wc -l - if [ $(equery -q -C list -i -e "${pkg}" | wc -l) -gt 0 ]; then + if [ $(equery -q -C list -i "${pkg}" | wc -l) -gt 0 ]; then foundone=1 IFS="$OIFS" - get_flagstatus "${flag}" + get_flagstatus "${flag}" "${pkg}" IFS=': ' printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }" fi @@ -428,11 +563,20 @@ set ${args} while [ -n "${1}" ]; do + get_portageuseflags if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then printf "%-20s" ${1} get_flagstatus ${1} echo fi + if echo " ${ACTIVE_FLAGS[4]} " | egrep -e " -?${1} " > /dev/null; then + for pkg in $( echo "${ACTIVE_FLAGS[4]}" | \ + egrep " -?${1} " | cut -d " " -f 2); do + printf "%-20s" ${1} + SCOPE="local" get_flagstatus ${1} "${pkg}" + printf "(%s)\n" ${pkg} + done; + fi shift done } @@ -446,9 +590,162 @@ NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }" } +# Simple utility to remove empty files from package.use +clean_package_use() { +if [[ -d ${PACKAGE_USE_PATH} ]]; then + for f in $(find ${PACKAGE_USE_PATH} -size 0); do + echo "Removing empty file ""${f}""" + rm ${f} + done; +fi +} + +# Utility to remove a use flag from a file in package.use[/] +# Args: (1) File, (2) Use flag +scrub_use_flag() { + local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?" + local filename=${1} + # Ignore leading - on flag + local flag=${2#*-} + local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/") + local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?" + + while read line; do + # Skip (preserve) comments on their own lines + if [[ -z $(echo "${line}" | sed -re "s/^ *#.*$//") ]]; then + echo "${line}" + # Detect if requested package is defined on this line + elif [[ -n ${PACKAGE} ]]; then + if [[ -n $(echo ${line} | egrep -E "[^#]*${pkg_re}") ]]; then + # If this is the only (remaining) use flag defined + # for this package, then remove the whole line + if [[ -z $(echo ${line} | \ + egrep -E "[^#]*${pkg_re}\s*-?${flag}\s*$") ]]; then + # Remove flag from this line + echo "${line}" | sed -re "s/-?\b${flag}\b//" + fi + else + # Passthru + echo "${line}" + fi + # If line only has this use flag, let it be removed + # (used if PACKAGE is not defined -- from pruning) + elif [[ -z $(echo ${line} | \ + grep -re "[^#]*${atom_re}.*-?${flag}") ]]; then + # Remove flag from this line + echo "${line}" | sed -re "s/-?\b${flag}\b//" + else + # Passthru + echo "${line}" + fi; + done < <(cat "${filename}") > "${filename}"; +} + +modify_package() { + get_useflags + + local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?" + local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/") + local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?" + + while [[ -n ${1} ]]; do + local flag=${1} + # + # --- Sanity checks + # (1) make sure ${pkg} exists in portdir + if [[ ! -d "$(get_portdir)/${pkg}" ]]; then + error "Package ""${pkg}"" does not exist" + # + # (2) make sure ${flag} is defined in get_useflaglist + elif [[ ! -n $(echo " $(get_useflaglist) " | grep "${flag}") ]]; then + warn "USE flag ""${flag}"" does not exist" + # Don't bail just because of this, just warn + fi; + # If flag is enabled in portage USE flags (emerge --info), + # then "remove"ing the flag should be replaced with adding + # the negative flag instead + if [[ "${ACTION}" == "remove" && \ + $( get_flagstatus ${flag} ${pkg} | grep "+" ) ]]; then + flag="-${flag}" + ACTION="add" + # If flag is currently disabled for the package requested + # to be enabled in, then "remove" the negative + elif [[ "${ACTION}" == "add" && + -n $( echo " ${ACTIVE_FLAGS[4]} " | egrep -e " ${PACKAGE}.*-${flag}\b" ) ]]; then + flag="-${flag}" + ACTION="remove" + fi; + case "${ACTION}" in + "add") + local filename + if [[ -d ${PACKAGE_USE_PATH} ]]; then + # Use naming convention of package.use/package + filename="${PACKAGE_USE_PATH}/${pkg#*/}" + if [[ ! -s "${filename}" ]]; then + # Create new file to contain flag + echo "${PACKAGE} ${flag}" > "${filename}" + echo "Adding ""${PACKAGE}:${flag}"" use flag to new file ""${filename}""" + return + fi; + else + # Add to package.use file instead + filename=${PACKAGE_USE_PATH} + # Create as necessary + touch "${filename}" + fi; + # Walk through the file and add the flag manually + echo "Adding ""${PACKAGE}:${flag}"" use flag in ""${filename}""" + local added=0 + while read line; do + if [[ -n $(echo "${line}" | egrep -e "^[^#]*${pkg_re}") ]]; then + echo "$(reduce_package_use "${line} ${flag}" | cut -d " " -f 2-)" + added=1 + else + # Passthru + echo "${line}" + fi; + done < <(cat "${filename}") > ${filename} + if [[ ${added} == 0 ]]; then + echo "${PACKAGE} ${flag}" >> "${filename}" + fi + ;; + "remove") + local filename + if [[ -d ${PACKAGE_USE_PATH} ]]; then + # Scan for file containing named package and use flag + filename=$( egrep -rle "${pkg_re}.*[^-]${flag}( |$)" "${PACKAGE_USE_PATH}") + if [[ -z "${filename}" ]]; then + error ""${flag}" is not defined for package "${PACKAGE}"" + return + fi; + else + # Remove from package.use instead + filename=${PACKAGE_USE_PATH} + # Create as necessary + touch "${filename}" + fi; + # Scrub use flag from matched files + for f in ${filename}; do + # Remove current flags in file + echo "Removing ""${PACKAGE}:${flag}"" use flag in ""${f}""" + scrub_use_flag ${f} ${flag} + done; + # Remove empty files + clean_package_use + ;; + esac; + shift; + done; +} + # USE flag modification function. Mainly a loop with calls to add_flag and # remove_flag to create a new USE string which is then inserted into make.conf. modify() { + if [[ -n ${PACKAGE} ]]; then + modify_package ${*} + return; + fi; + if [ -z "${*}" ]; then if [ "${ACTION}" != "prune" ]; then echo "WARNING: no USE flags listed for modification, do you really" @@ -488,6 +785,22 @@ elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then remove_flag "-${1}" fi + # Locate use flag in package.use + local filename + if [[ -d ${PACKAGE_USE_PATH} ]]; then + filename=$( egrep -rle "-?\b${1}\b" "${PACKAGE_USE_PATH}") + else + # Scrub from package.use file + filename=${PACKAGE_USE_PATH} + fi + # Scrub use flag from matched files + for f in ${filename}; do + # Remove current flags in file + echo "Disabling ""${1}"" use flag in ""${f}""" + scrub_use_flag ${f} ${1} + done; + # Remove empty files from package.use + clean_package_use shift fi done