Fix euse to always prefer /etc/portage/make.conf over /etc/make.conf. Bug 434672
[gentoolkit.git] / bin / euse
1 #!/bin/bash
2 # Disable globbing because "-*" and such is valid as a use flag. 
3 set -f
4
5 # $Header$
6
7 # bash replacement for the original euse by Arun Bhanu
8 # Author: Marius Mauch <genone@gentoo.org>
9 #         Jared Hancock (Signigicant rewrite for package.use support)
10 # Licensed under the GPL v2
11
12 PROGRAM_NAME=euse
13 VERSION="svn"
14
15 EPREFIX=${EPREFIX:-$(portageq envvar EPREFIX)}
16 ETC="${EPREFIX}/etc"
17 USR_SHARE_PORTAGE="${EPREFIX}/usr/share/portage"
18
19 # Arrays containing the known repository names and repository profile paths
20 PORTAGE_REPOS=( $(portageq get_repos ${EPREFIX}/) )
21 PORTAGE_REPO_PATHS=( $(portageq get_repo_path ${EPREFIX}/ ${PORTAGE_REPOS[@]}) )
22
23 # define error functions so they can be used immediately
24 fatal() {
25         echo -e "ERROR: ${*}"
26         set +f
27         exit 1
28 }
29
30 error() {
31         echo -e "ERROR: ${*}"
32 }
33
34 warn() {
35         echo -e "WARNING: ${*}"
36 }
37
38 # /etc/make.conf now exists as /etc/portage/make.conf by default, prefer
39 # it over /etc/make.conf for changes.
40 if [[ -e "${ETC}/portage/make.conf" ]]; then
41         MAKE_CONF_PATH="${ETC}/portage/make.conf"
42 elif [[ -e "${ETC}/make.conf" ]]; then
43         MAKE_CONF_PATH="${ETC}/make.conf"
44 else
45         fatal "make.conf does not exist"
46 fi
47 MAKE_CONF_BACKUP_PATH="${MAKE_CONF_PATH}.euse_backup"
48
49 # /etc/make.globals has been moved to /usr/share/portage/config/make.globals
50 if [ -e "${USR_SHARE_PORTAGE}/config/make.globals" ]; then
51         MAKE_GLOBALS_PATH="${USR_SHARE_PORTAGE}/config/make.globals"
52 else
53         MAKE_GLOBALS_PATH="${ETC}/make.globals"
54 fi
55
56 # /etc/make.profile or /etc/portage/make.profile, if /etc/make.profile exists, it will be used
57 if [ -e "${ETC}/make.profile" ]; then
58         MAKE_PROFILE_PATH="${ETC}/make.profile"
59 elif [ -e "${ETC}/portage/make.profile" ]; then
60         MAKE_PROFILE_PATH="${ETC}/portage/make.profile"
61 else
62         fatal "make.profile does not exist"
63 fi
64 PACKAGE_USE_PATH=${ETC}/portage/package.use
65
66 [ -z "${MODE:-}" ] && MODE="showhelp"           # available operation modes: showhelp, showversion, showdesc, showflags, modify
67
68 parse_arguments() {
69         if [ -z "${1}" ]; then
70                 return
71         fi
72         while [ -n "${1}" ]; do
73                 case "${1}" in
74                         -h | --help)           MODE="showhelp";;
75                         -V | -v | --version)   MODE="showversion";;
76                         -i | --info)           MODE="showdesc";;
77                         -I | --info-installed) MODE="showinstdesc";;
78                         -l | --local)          SCOPE="local";;
79                         -g | --global)         SCOPE="global";;
80                         -a | --active)         MODE="showflags";;
81                         -E | --enable)         MODE="modify"; ACTION="add";;
82                         -D | --disable)        MODE="modify"; ACTION="remove";;
83                         -P | --prune | -R | --remove)
84                                                MODE="modify"; ACTION="prune";;
85                         -p | --package)        MODE="modify"; shift; PACKAGE=${1}; SCOPE="local";;
86                         -*)
87                                 echo "ERROR: unknown option ${1} specified."
88                                 echo
89                                 MODE="showhelp"
90                                 ;;
91                         "%active")
92                                 get_portageuseflags
93                                 ARGUMENTS="${ARGUMENTS:-} ${ACTIVE_FLAGS[9]}"
94                                 ;;
95                         *)
96                                 ARGUMENTS="${ARGUMENTS:-} ${1}"
97                                 ;;
98                 esac
99                 shift
100         done
101 }
102
103 get_real_path() {
104         set -P
105         cd "$1"
106         pwd
107         cd "$OLDPWD"
108         set +P
109 }
110
111 # Function: check_sanity {{{
112 # Performs some basic system sanity checks
113 check_sanity() {
114         # file permission tests
115         local descdir
116         local make_defaults
117         local make_conf
118
119         [[ ! -d "${MAKE_PROFILE_PATH}" || ! -r "${MAKE_PROFILE_PATH}" ]] && error "${MAKE_PROFILE_PATH} is not readable"
120         # 
121         for make_conf in $(get_all_make_conf); do
122                 [ ! -r "${make_conf}" ] && fatal "${make_conf} is not readable"
123         done
124
125         descdir="$(get_portdir)/profiles"
126         
127         [ ! -r "${MAKE_GLOBALS_PATH}" ] && fatal "${MAKE_GLOBALS_PATH} is not readable"
128         [ -z "$(get_portdir)" ] && fatal "\$PORTDIR couldn't be determined"
129         [ ! -d "${descdir}" ] && fatal "${descdir} does not exist or is not a directory"
130         [ ! -r "${descdir}/use.desc" ] && fatal "${descdir}/use.desc is not readable"
131         [ ! -r "${descdir}/use.local.desc" ] && fatal "${descdir}/use.local.desc is not readable"
132
133         for make_defaults in $(get_all_make_defaults); do
134                 [ ! -r "$make_defaults" ]  && fatal "$_make_defaults is not readable"
135         done
136         [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && fatal ""${MAKE_CONF_PATH}" is not writable"
137         [ "${MODE}" == "modify" -a -s "${PACKAGE_USE_PATH}" -a ! -w "${PACKAGE_USE_PATH}" ] && fatal ""${PACKAGE_USE_PATH}" is not writable"
138         return 0
139 } # }}}
140
141 showhelp() {
142 cat << HELP
143 ${PROGRAM_NAME} (${VERSION})
144
145 Syntax: ${PROGRAM_NAME} <option> [suboptions] [useflaglist]
146
147 Options: -h, --help           - show this message
148          -V, --version        - show version information
149          -i, --info           - show descriptions for the given useflags
150          -I, --info-installed - show descriptions for the given useflags and
151                                 their current impact on the installed system
152          -g, --global         - show only global use flags (suboption)
153          -l, --local          - show only local use flags (suboption)
154          -a, --active         - show currently active useflags and their origin
155          -E, --enable         - enable the given useflags
156          -D, --disable        - disable the given useflags
157          -R, --remove         - remove all references to the given flags from
158                                 make.conf and package.use to revert to default 
159                                 settings
160          -P, --prune          - alias for --remove
161          -p, --package        - used with -E, -D, and -R to apply to a
162                                 specific package only
163
164 Notes: ${PROGRAM_NAME} currently works for global flags defined
165        in make.globals, make.defaults, make.conf, use.force, and use.mask 
166        and local flags defined in package.use and individual package ebuilds.
167        It might have issues with cascaded profiles. If multiple options are 
168        specified only the last one will be used.
169 HELP
170 }
171
172 showversion() {
173 cat << VER
174 ${PROGRAM_NAME} (${VERSION})
175 Written by Marius Mauch
176
177 Copyright (C) 2004-2009 Gentoo Foundation, Inc.
178 This is free software; see the source for copying conditions.
179 VER
180 }
181
182 # Function: reduce_incrementals {{{
183 # remove duplicate flags from the given list in both positive and negative forms
184 # (but unlike portage always keep the last value even if it's negative)
185 # Otherwise the status flags could be incorrect if a flag appers multiple times in
186 # one location (like make.conf).
187 # Using python here as bash sucks for list handling.
188 # NOTE: bash isn't actually that bad at handling lists -- sh is. This may be
189 #       worth another look to avoid calling python unnecessariy. Or we could
190 #       just write the whole thing in python. ;)
191 reduce_incrementals() {
192         echo $@ | python -c "from __future__ import print_function;import sys
193 r=[]
194 for x in sys.stdin.read().split():
195         if x[0] == '-' and x[1:] in r:
196                 r.remove(x[1:])
197                 r.append(x)
198         elif x[0] != '-' and '-'+x in r:
199                 r.remove('-'+x)
200                 r.append(x)
201         elif x == '-*': r = ['-*']
202         elif x not in r: r.append(x)
203 print(' '.join(r))" 
204 } # }}}
205
206 # Function: reduce_incrementals_trump {{{
207 # Similar to reduce_incrementals but negative flags trump positive
208 # flags, regardless of which follows which
209 reduce_incrementals_trump() {
210         echo $@ | python -c "from __future__ import print_function;import sys
211 r=[]
212 for x in sys.stdin.read().split():
213         if x[0] == '-' and x[1:] in r:
214                 r.remove(x[1:])
215                 r.append(x)
216         elif x == '-*': r = ['-*']
217         elif x not in r and not '-'+x in r: r.append(x)
218 print(' '.join(r))" 
219 } # }}}
220
221 # Function: reduce_package_use {{{
222 # Similar to reduce_incrementals except converts lines from package atoms
223 # in /etc/portage/package.use files to lines of "pkg {[-]flag}*"
224 #
225 # Arguments:
226 # * - Lines of package atom followed by flags
227 #     (app-editors/vim flag1 flag2 -flag3)
228 reduce_package_use() {
229         echo "${@}" | python -c "from __future__ import print_function;import sys,re
230 h={}; getflags=re.compile(r'(-?[\w*-]+)')
231 for x in sys.stdin.read().split('\n'):
232         if not x: continue
233         parts = x.lstrip().split(' ',1)
234         if len(parts)==1: continue
235         pkg=parts[0]
236         flags = getflags.findall(parts[1])
237         if not pkg in h: h[pkg]=[]
238         r=h[pkg]
239         for x in flags:
240                 if x[0] == '-' and x[1:] in r:
241                         r.remove(x[1:])
242                         r.append(x)
243                 elif x[0] != '-' and '-'+x in r:
244                         r.remove('-'+x)
245                         r.append(x)
246                 elif x == '-*': r = h[pkg] = ['-*']
247                 elif x not in r:
248                         r.append(x)
249 print('\n'.join(['%s %s' % (pkg,' '.join(flgs)) for pkg,flgs in h.items() if len(flgs)]))"
250 } # }}}
251
252 # Function: get_useflags {{{
253 # Creates a bash array ACTIVE_FLAGS that contains the global use flags, 
254 # indexed by origin: 0: environment, 1: make.conf, 2: make.defaults, 
255 # 3: make.globals, and local use flags, indexed by origin: 4: package.use, 
256 # 5: ebuild IUSE, 6: use.mask, 7: use.force, 
257 # 9: flags indicated active by emerge --info (get_portageuseflags)
258 get_useflags() {
259         set +f
260         if [[ -z ${ACTIVE_FLAGS[4]} && ( $SCOPE == "local" || -z $SCOPE ) ]]; then
261                 # Parse through /etc/portage/package.use
262                 if [[ -d ${PACKAGE_USE_PATH} ]]; then
263                         ACTIVE_FLAGS[4]="$( cat ${PACKAGE_USE_PATH}/* \
264                                 | sed -re "s/ *#.*$//g" -e "s/^ *$//g" )"
265                 elif [[ -e ${PACKAGE_USE_PATH} ]]; then
266                         # JWM, 23/12/2009: I edited this following line but I'm not sure if it's 100% correct.
267                         ACTIVE_FLAGS[4]="$( sed -re "s/ *#.*$//g" -e "s/^ *$//g" \
268                                 ${PACKAGE_USE_PATH})"
269                 fi
270                 # Simplify ACTIVE_FLAGS[4] to be lines of pkg {[-]flag}*
271                 ACTIVE_FLAGS[4]="$(reduce_package_use "${ACTIVE_FLAGS[4]}")"
272                 #
273                 # ACTIVE_FLAGS[5] reserved for USE flags defined in ebuilds and
274                 # is generated/maintained in the get_useflaglist_ebuild() function
275         fi
276
277         # only calculate once as calling emerge is painfully slow
278         [ -n "${USE_FLAGS_CALCULATED}" ] && return
279
280         # backup portdir so get_portdir() doesn't give false results later
281         portdir_backup="${PORTDIR}"
282         
283         ACTIVE_FLAGS[0]="$(reduce_incrementals "${USE}")"
284         USE=""
285         for x in $(get_all_make_conf); do
286                 source "${x}"
287                 ACTIVE_FLAGS[1]="$(reduce_incrementals "${ACTIVE_FLAGS[1]}" "${USE}")"
288         done
289         USE=""
290         for x in $(get_all_make_defaults); do
291                 source "${x}"
292                 ACTIVE_FLAGS[2]="${ACTIVE_FLAGS[2]} ${USE}"
293         done
294         ACTIVE_FLAGS[2]="$(reduce_incrementals "${ACTIVE_FLAGS[2]}")"
295         USE=""
296         source "${MAKE_GLOBALS_PATH}"
297         ACTIVE_FLAGS[3]="$(reduce_incrementals "${USE}")"
298
299         # restore saved env variables
300         USE="${ACTIVE_FLAGS[0]}"
301         PORTDIR="${portdir_backup}"
302
303         #
304         # Traverse through use.mask and use.force (0.5s)
305         # Flip signs of use.mask (it's interpreted oppositely), 
306         ACTIVE_FLAGS[6]=$(reduce_incrementals_trump \
307                 $(cat $(traverse_profile "use.mask") | sed -re "/^#.*$/{d}") \
308                         | sed -re "s/(^| )-[^ ]*//g" -e "s/(^| )([a-z0-9])/ -\2/g")
309         ACTIVE_FLAGS[7]=$(reduce_incrementals \
310                 $(cat $(traverse_profile "use.force") \
311                         | sed -re "/^#.*$/ {d}"))
312
313         USE_FLAGS_CALCULATED=1
314         set -f
315 } # }}}
316
317 # Function: get_portageuseflags # {{{
318 # Fetch USE flags reported active by Portage
319 get_portageuseflags() {
320         # only calculate once as calling emerge is painfully slow
321         [ -n "${_PORTAGE_USE_FLAGS_CALCULATED}" ] && return
322         # get the currently active USE flags as seen by portage, this has to be after
323         # restoring USE or portage won't see the original environment
324         # Bug 181309, emerge may complain if EMERGE_DEFAULT_OPTS="--ask" is set
325         ACTIVE_FLAGS[9]="$(portageq envvar USE)" #'
326         _PORTAGE_USE_FLAGS_CALCULATED=1
327 } # }}}
328
329 # Function: get_useflaglist {{{
330 # Get the list of all known USE flags by reading use.desc and/or 
331 # use.local.desc (depending on the value of $SCOPE). Also searches any 
332 # registered overlays after searching the main portage tree first.
333 # Use flags visible in both the main tree and overlays are trumped by
334 # the main tree. Overlays are indicated by brackets [xxx] at the 
335 # beginning of the description. 
336 #
337 # Returns:
338 # (written to stdout) Sorted, unique list of system-wide USE flags and
339 # descriptions. Flags defined in overlays have the overlay in brackets
340 # prepended to the descriptions.
341 #
342 # Environment:
343 # SCOPE - [local|global] constrain search to local (use.local.desc) or
344 #                 global (use.desc)
345 get_useflaglist() {
346         local descdir
347         local overlay
348         for profiledir in ${ALL_PORTDIRS[@]}; do
349                 descdir="${profiledir}/profiles"
350                 if [[ -z ${SCOPE} || ${SCOPE} == "global" ]]; then
351                         [[ ! -s "${descdir}/use.desc" ]] && continue
352                         egrep "^[^# ]+ +-" "${descdir}/use.desc" 
353                 fi
354                 if [[ -z ${SCOPE} || ${SCOPE} == "local" ]]; then
355                         [[ ! -s "${descdir}/use.local.desc" ]] && continue
356                         egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" \
357                                 | cut -d: -f 2 
358                 fi
359         done | cut -d " "  -f1 | sort --field=":" --key=1,1 --unique
360 } # }}}
361
362 # Function: get_useflaglist_ebuild {{{
363 # Builds USE flag information for specified package atom into 
364 # ACTIVE_FLAGS[5]. For the atom, the versions available are found, and 
365 # for each, the corresponding SLOT, IUSE are stored along with which 
366 # overlay the ebuild lives in. Considering that the pieces of information 
367 # may be required in any order or any subsets, it is intended for the 
368 # function to cache the information and it be retrieved from 
369 # ACTIVE_FLAGS[5]. So the format of ACTIVE_FLAGS[5] is newline-separated 
370 # list of:
371 #
372 # category/packge;version;SLOT;IUSE;overlay
373 #
374 # Arguments:
375 # $1 - Package atom to lookup (app-editor/vim)
376 #
377 # Returns:
378 # Nothing significant
379 #
380 # Environment:
381 # PORTDIR - Root of portage tree
382 # ACTIVE_FLAGS - Array of current use flag info
383 #
384 get_useflaglist_ebuild() {
385         local known=$(echo "${ACTIVE_FLAGS[5]}" | egrep "^${1}")
386         local cacheformat
387         if [[ -n $known ]]; then
388                 # No need to recache
389                 return
390         fi
391         local pkg=$(echo ${1} | cut -d/ -f2)
392         declare append
393         for portdir in ${ALL_PORTDIRS[@]}; do
394                 if [[ -s "${portdir}/profiles/repo_name" ]]; then
395                         overlay="$(cat "${portdir}/profiles/repo_name")"
396                 else
397                         # XXX: Portage uses "x-<basename of the overlay>
398                         overlay="x-$(basename "${portdir}")"
399                 fi
400                 # Open the ebuild file and retrieve defined USE flags
401                 [[ ! -d "$portdir/${1}" ]] && continue
402                 cacheformat="unknown"
403                 [[ -d "$portdir/metadata/cache" ]] && cacheformat="cache" # format is pms
404                 [[ -d "$portdir/metadata/md5-cache" ]] && cacheformat="md5-cache" # format is md5-cache
405                 if [[ "$cacheformat" == "unknown" ]]; then
406                         echo "!!! Metadata cache not found. You need to run " >&2
407                         echo "!!! 'egencache --repo=$overlay --update'" >&2
408                         echo "!!! to generate metadata for your overlays" >&2
409                         return 1
410                 fi
411                 append=$(set +f; ls ${portdir}/metadata/${cacheformat}/${1}-* \
412                         | egrep "${1}-[0-9.]+" \
413                         | sed -e "s:${portdir}/metadata/${cacheformat}/${1}-::g" \
414                         | while read -d $'\n' version; do
415                                 IFS=$'\n'
416                                 if [[ ! -e "${portdir}/metadata/${cacheformat}/${1}-$version" ]]; then
417                                         # Repo does not have this particular package
418                                         continue
419                                 fi
420                                 if [[ "${cacheformat}" == "cache" ]]; then
421                                         iuse=$(head -n 11 "${portdir}/metadata/${cacheformat}/${1}-$version"|tail -n 1)
422                                         slot=$(head -n 3 "${portdir}/metadata/${cacheformat}/${1}-$version"|tail -n 1)
423                                 elif [[ "${cacheformat}" == "md5-cache" ]]; then
424                                         iuse=$(grep "^IUSE=" "${portdir}/metadata/${cacheformat}/${1}-$version" | sed 's/^IUSE=//')
425                                         slot=$(grep "^SLOT=" "${portdir}/metadata/${cacheformat}/${1}-$version" | sed 's/^SLOT=//')
426                                 else
427                                         # This is a bug, we should have already returned
428                                         return 1
429                                 fi
430                                 echo "${1};${version};${slot};${iuse};${overlay}"
431                         done
432                 )
433                 if [[ -z ${ACTIVE_FLAGS[5]} ]]; then ACTIVE_FLAGS[5]="$append"
434                 else ACTIVE_FLAGS[5]="${ACTIVE_FLAGS[5]}"$'\n'"$append"
435                 fi
436         done
437 } # }}}
438
439 # get all make.conf files that exist on the system
440 get_all_make_conf() {
441         # At least one of the files exists or we would not have made it this far
442         for x in ${ETC}/make.conf ${ETC}/portage/make.conf; do
443                 [ -e "${x}" ] && echo "${x}"
444         done
445 }
446 # Function: traverse_profile {{{
447 # General method of collecting the contents of a profile
448 # component by traversing through the cascading profile
449 #
450 # Bug #414961 allows ':' shorthand to resolve to the ${PORTDIR}/profiles
451 # A value before the ':' references the repository, no value means gentoo
452 #
453 # Example: local:base would refer to the profiles directory in the repository
454 # path owned by the 'local' repository
455 #
456 # Arguments:
457 # $1 - Filename (make.profile)
458 # [$2] - Current directory (unspecified means to start at the top)
459 traverse_profile() {
460         local curdir
461         local parent
462         local rvalue=""
463         
464         curdir="${2:-$(get_real_path ${MAKE_PROFILE_PATH})}"
465         
466         if [[ -f "${curdir}/parent" ]]; then
467                 for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do
468                         # Bug 231394, handle parent path being absolute
469                         index=$(expr index "${parent}" :)
470                         if [[ ${parent:0:1} == "/" ]]; then
471                                 pdir="$(get_real_path ${parent})"
472                         elif [[ $index -eq 0 ]]; then
473                                 pdir="$(get_real_path ${curdir}/${parent})"
474                         else
475                                 # We have a path with a colon shortcut
476                                 let i=$index-1
477                                 repo="${parent:0:${i}}"
478                                 [[ -z "${repo}" ]] && repo="gentoo"
479                                 parent="${parent:$index}"
480                                 limit=${#PORTAGE_REPOS[@]}
481                                 for ((i=0; i < limit ; i++)); do
482                                         if [[ ${repo} == ${PORTAGE_REPOS[i]} ]]; then
483                                                 parent="${PORTAGE_REPO_PATHS[i]}/profiles/${parent}"
484                                                 break
485                                         fi
486                                 done
487                                 pdir="$(get_real_path ${parent})"
488                         fi
489                         rvalue="${rvalue} $(traverse_profile ${1} ${pdir})"
490                 done
491         fi
492         [[ -f "${curdir}/${1}" ]] && rvalue="${rvalue} ${curdir}/${1}"
493
494         echo "${rvalue}"
495 } # }}}
496
497 # Function: get_all_make_defaults {{{
498 # Det all make.defaults by traversing the cascaded profile directories
499 get_all_make_defaults() {
500         if [[ -z ${MAKE_DEFAULTS:-} ]]; then
501                 MAKE_DEFAULTS=$(traverse_profile "make.defaults")
502         fi
503         echo $MAKE_DEFAULTS
504 } # }}}
505 MAKE_DEFAULTS=$(get_all_make_defaults)
506
507 # Function: get_flagstatus_helper # {{{
508 # Little helper function to get the status of a given flag in one of the 
509 # ACTIVE_FLAGS elements. 
510 #
511 # Returns:
512 # (Written to STDOUT) Flag active status (+/-) or default string given
513 # in argument 4 or an empty space
514
515 # Arguments:
516 # 1 - flag to test
517 # 2 - index of ACTIVE_FLAGS
518 # 3 - echo value for positive (and as lowercase for negative) test result
519 # 4 - (optional) echo value for "missing" test result, defaults to blank
520 get_flagstatus_helper() {
521         if [[ -z ${flags} ]]; then 
522                 local flags=${ACTIVE_FLAGS[${2}]}
523         fi
524         local flag=$(echo " $flags " | grep -Eo " [+-]?${1} ")
525         if [[ ${flag:1:1} == "-" ]]; then
526                 echo -e -n "${3}" | tr [:upper:]+ [:lower:]-
527         elif [[ -n ${flag} ]]; then
528                 echo -e -n "${3}"
529         else
530                 echo -n "${4:- }"
531         fi
532 } # }}}
533
534 # Function: get_flagstatus_helper_pkg # {{{
535 # Entry to get_flagstatus_helper for packages which will fetch use
536 # flags set in package.use for the package and pass them on to
537 # get_flagstatus_helper. Also correcly handles lines in package.use
538 # specified for individual package versions
539 get_flagstatus_helper_pkg() {
540         if [[ -z ${2} ]]; then
541                 echo -ne "${4:- }"
542                 return
543         elif [[ -z ${flags} ]]; then
544                 # If no atoms are matchers (start with >,<,=, then they all match
545                 atoms=($2)
546                 if [[ -z "${atoms[@]/[<>=]*/}" ]]; then
547                         atoms=($(
548                                 echo "${atoms[@]}" | python -c "
549 from __future__ import print_function;import portage.dep as dep, sys
550 print(' '.join(dep.match_to_list('$5-$6',sys.stdin.read().split())))"))
551                 fi
552                 flags=$(for atom in ${atoms[@]}; do
553                         [[ -z $atom ]] && continue
554                         echo "${ACTIVE_FLAGS[4]}" | \
555                                 grep "^ *$atom" | cut -d\  -f2-
556                 done)
557         fi
558         if [[ -z ${5} || -z ${flags} ]]; then
559                 echo -e -n "${4:- }"
560                 return
561         fi
562         get_flagstatus_helper "$@"
563 } # }}}
564
565 # Function: get_flagstatus_helper_ebuild {{{
566 # get_flagstatus_helper replacement for packages to fetch ebuild USE flag
567 # activation status.
568 #
569 # Returns:
570 # (Written to STDOUT) Flag active status (+/-) or default string given
571 # in argument 4 or an empty space. If USE flag is not defined in the list
572 # of flags (2), an '!' is written
573 #
574 # Arguments:
575 # 1 - flag to test
576 # 2 - IUSE line from ebuild file
577 # 3 - echo value for positive (and as lowercase for negative) test result
578 # 4 - (optional) echo value for "missing" test result, defaults to blank space
579 get_flagstatus_helper_ebuild() {
580         local flags=$(echo $2 | cut -d\" -f2) 
581         local flag=$(echo " $flags " | grep -Eo " [+-]?$1 ")
582         if [[ ${flag:1:1} == "+" ]]; then
583                 echo -en "${3}"
584         elif [[ ${flag:1:1} == "-" ]]; then
585                 echo -en "${3}" | tr [:upper:]+ [:lower:]-
586         elif [[ -z $flag ]]; then
587                 echo -en "!"
588         else
589                 echo -en "${4:- }"
590         fi
591 } # }}}
592
593 # Function: get_flagstatus {{{
594 # Prints a status string for the given flag, each column indicating the presence
595 # for portage, in the environment, in make.conf, in make.defaults, in 
596 # make.globals, and in use.force and flipped in use.mask.
597 #
598 # Arguments:
599 # 1 - use flag for which to retrieve status
600 #
601 # Returns:
602 # 0 (True) if flag is active, 1 (False) if not active
603 #
604 # Outputs:
605 # Full positive value would be "[+ECDGFm] ", full negative value would be [-ecdgfM],
606 # full missing value would be "[-      ] " (portage only sees present or not present)
607 get_flagstatus() {
608         get_useflags
609
610         local E=$(get_flagstatus_helper "${1}" 0 "E")
611         local C=$(get_flagstatus_helper "${1}" 1 "C")
612         local D=$(get_flagstatus_helper "${1}" 2 "D")
613         local G=$(get_flagstatus_helper "${1}" 3 "G")
614         local M=$(get_flagstatus_helper "${1}" 6 "M")
615         local F=$(get_flagstatus_helper "${1}" 7 "F")
616         # Use flags are disabled by default
617         ACTIVE="-"
618         #
619         # Use flag precedence is defined (at least) at:
620         # http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=2
621         for location in "E" "C" "D" "G" "F" "M"; do
622                 if [[ ${!location} == $location ]]; then
623                         ACTIVE="+"
624                         break
625                 elif [[ ${!location} != " " ]]; then 
626                         ACTIVE="-"
627                         break
628                 fi
629         done
630         echo -n "[${ACTIVE:--}$E$C$D$G$F$M] "
631         [[ $ACTIVE == "+" ]]; return $?
632 } # }}}
633
634 # Function: get_flagstatus_pkg {{{
635 # Outputs local flag status information for a specific package and perhaps
636 # specific package version.
637 #
638 # Arguments:
639 # 1 - flag (gtk)
640 # 2 - package atom (www-client/elinks)
641 # 3 - +/- whether flag is enabled by global configuration files
642 # 4 - (Optional) version of package to evaluate (empty means all versions)
643 #
644 # Outputs:
645 # Flag status for package.use and ebuild, slot and version, and overlay
646 # the version lives is if not PORTDIR
647 #
648 # Full positive would be "[+PB]", full negative would be "[-pb]", and full 
649 # missing would be "[?  ]", question because the sign will default to the 
650 # sign of the global status of the flag
651 get_flagstatus_pkg() {
652         #
653         # Pre-cache the use flags declared in ebuilds first.
654         # This is required as calling it inside a $() seems to
655         # prevent caching of results into $ACTIVE_FLAGS array
656         get_useflaglist_ebuild ${2} 
657         #
658         # Get list of ebuilds available for this package. The 
659         # get_useflaglist_ebuild() function stores them in $ACTIVE_FLAGS[5]
660         # with the package name leading the lines. The other information
661         # is in the same line, semi-colon (;) separated. The fields are
662         # package;version;SLOT;IUSE;overlay
663         #
664         # Fetch package atoms and flags from package.use only once
665         local atoms=$(echo "${ACTIVE_FLAGS[4]}" | \
666                         grep -Eo "^ *[<>]?=?${2}(-[0-9rp._*-]*)?")
667         local IFS=$'\n'; for pkgline in $(echo "${ACTIVE_FLAGS[5]}" | grep "^$2" \
668                         | sort -t \; -k2,2 -V); do
669                 OIFS=$IFS; IFS=";"; INFO=($pkgline); IFS=$OIFS;
670                 local version=${INFO[1]}
671                 [[ -n $4 && $4 != $version ]] && continue
672                 local slot=${INFO[2]}
673                 local iuse=${INFO[3]}
674                 if [[ $slot == '0' || $slot == "" ]]; then 
675                         slot=""; 
676                 else
677                         slot="($(echo ${slot} | cut -d\" -f2)) "
678                 fi
679                 local overlay=${INFO[4]}
680                 [[ -n $overlay ]] && overlay="[$overlay]"
681                 # 
682                 # Fetch enabled status for this version
683                 local P=$(get_flagstatus_helper_pkg "${1}" "${atoms}" "P" "" "${2}" "${version}")
684                 local B=$(get_flagstatus_helper_ebuild "${1}" "${iuse}" "B" "" "${2}" "${version}")
685                 UNUSED=0
686                 ACTIVE=${3}
687                 for location in "P" "B"; do
688                         if [[ ${!location} == $location ]]; then
689                                 ACTIVE="+"
690                         elif [[ ${!location} == "!" ]]; then 
691                                 UNUSED=1
692                                 break
693                         elif [[ ${!location} != " " ]]; then 
694                                 ACTIVE="-"
695                                 break
696                         fi
697                 done
698                 if [[ $UNUSED == 1 ]]; then
699                         echo "              ${slot}${version} ${overlay}"
700                 else
701                         echo "        [${ACTIVE:-${3:- }}$P$B] ${slot}${version} ${overlay}"
702                 fi
703         done
704         echo
705 } # }}}
706
707 # Function: get_portdir {{{
708 # faster replacement to `portageq portdir`
709 #
710 # Outputs:
711 # Location of portage tree root
712 get_portdir() {
713         # Use a subshell so we don't have to protect the variables in
714         # the current scope
715         (
716         if [ -z "${PORTDIR:-}" ]; then
717                 source "${MAKE_GLOBALS_PATH}"
718                 for x in $(get_all_make_defaults); do
719                         source "${x}"
720                 done
721                 for x in $(get_all_make_conf); do
722                         source "${x}"
723                 done
724         fi
725         echo "${PORTDIR}"
726         )
727 } # }}}
728 # This won't change while the script is running, so cache it
729 PORTDIR="$(get_portdir)"
730
731 # Function: get_all_overlays {{{
732 # Outputs list of portage overlays as defined in the PORTDIR_OVERLAY
733 # variable defined in make.conf 
734 get_all_overlays() {
735         # Use a subshell so we don't have to protect the variables in
736         # the current scope
737         ( 
738                 for x in $(get_all_make_conf); do
739                         [[ -r "${x}" ]] && source "${x}"
740                 done
741                 echo ${PORTDIR_OVERLAY}
742         )
743 } # }}}
744 ALL_PORTDIRS=( "$PORTDIR" $(get_all_overlays) )
745
746 # Function: array_contains {{{
747 # PHP-style array_contains function.
748 #
749 # Arguments:
750 # 1 - haystack
751 # 2 - needle
752 #
753 # Returns:
754 # 0 (True) if needle in haystack, null (False) otherwise
755 array_contains() {
756         for i in $1; do [[ $i == $2 ]] && return 0; done
757         return
758 } # }}}
759
760 # Function: showdesc {{{
761 # This function takes a list of use flags and shows the status and 
762 # the description for each one, honoring $SCOPE
763 #
764 # Arguments:
765 # * - USE flags for which to display descriptions. Undefined means to
766 #         display descriptions for all known USE flags
767 #
768 # Environment:
769 # SCOPE - defines whether to output local or global USE flag descriptions
770 #                 Empty means to display both
771 #
772 # Outputs:
773 # (STDOUT) Flag description(s) for given USE flags
774 #
775 showdesc() {
776         local descdir
777         local current_desc
778         local found_one
779         local args
780         
781         args="${*:-*}"
782
783         if [ -z "${SCOPE}" ]; then
784                 SCOPE="global" showdesc ${args}
785                 echo
786                 SCOPE="local" showdesc ${args}
787                 return
788         fi
789         
790         local useflags=( $(echo "$(get_useflaglist)") )
791         
792         [ "${SCOPE}" == "global" ] && echo "global use flags (searching: ${args})"
793         [ "${SCOPE}" == "local" ] && echo "local use flags (searching: ${args})"
794         echo "************************************************************"
795         if [ "${args}" == "*" ]; then
796                 args="${useflags[*]}"
797         fi
798
799         set ${args}
800
801         foundone=0
802         while [[ -n "${1}" ]]; do
803                 if [[ "${SCOPE}" == "global" ]]; then
804                         if array_contains "${useflags[*]}" "$1"; then
805                                 get_flagstatus "${1}"
806                                 # XXX: Handle overlay
807                                 grep -h "^${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.desc} 2> /dev/null
808                                 foundone=1
809                         fi
810                 fi
811                 # local flags are a bit more complicated as there can be multiple
812                 # entries per flag and we can't pipe into printf
813                 if [[ "${SCOPE}" == "local" ]]; then
814                         if array_contains "${useflags[*]}" "$1"; then
815                                 foundone=1
816                         fi
817                         # Fetch all the packages data using this flag
818                         infos=$( grep -h ":${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.local.desc} 2> /dev/null \
819                                 | sed -re "s/^([^:]+):(.*)  *- *(.+)/\1|\2|\3/g")
820                         OIFS=$IFS; IFS=$'\n'; infos=($infos); IFS=$OIFS;
821                         for line in "${infos[@]}"; do
822                                 OIFS=$IFS; IFS="|"; line=($line); IFS=$OIFS
823                                 pkg=${line[0]} 
824                                 flag=${line[1]}
825                                 desc=${line[2]}
826                                 if get_flagstatus "${flag}"; then
827                                         ACTIVE="+"
828                                 else
829                                         ACTIVE="-"
830                                 fi
831                                 printf "%s\n" "${flag}"
832                                 printf "%s: %s\n" "${pkg}" "${desc}" \
833                                         | fold --width=$((${COLUMNS:-80}-10)) -s | sed -e "s/^/    /g"
834                                 get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}"
835                         done
836                 fi
837                 shift
838         done
839         
840         if [[ ${foundone} == 0 ]]; then
841                 echo "no matching entries found"
842         fi
843 } # }}}
844
845 # Function: showinstdesc {{{
846 # Works like showdesc() but displays only descriptions of which the appropriate
847 # ebuild is installed and prints the name of those packages.
848 #
849 # Arguments:
850 # * - USE flags for which to display descriptions. Undefined means to
851 #         display descriptions for all known USE flags
852 #
853 # Environment:
854 # SCOPE - defines whether to output local or global USE flag descriptions
855 #                 Empty means to display both
856 #
857 # Outputs:
858 # (STDOUT) Flag description(s) for given USE flags along with installed 
859 # packages
860 #
861 showinstdesc() {
862         local descdir
863         local current_desc
864         local args
865         local -i foundone=0
866         local OIFS="$IFS"
867
868         args=("${@:-*}")
869
870         case "${SCOPE}" in
871                 "global") echo "global use flags (searching: ${args[@]})";;
872                  "local") echo "local use flags (searching: ${args[@]})";;
873                        *) SCOPE="global" showinstdesc "${args[@]}"
874                           echo
875                           SCOPE="local" showinstdesc "${args[@]}"
876                           return;;
877         esac
878
879         descdir="$(get_portdir)/profiles"
880         echo "************************************************************"
881
882         if [ "${args}" = "*" ]; then
883                 args="$(get_useflaglist | sort -u)"
884         fi
885
886         set "${args[@]}"
887
888         while [ -n "${1}" ]; do
889                 case "${SCOPE}" in
890                         "global")
891                                 if desc=$(grep "^${1}  *-" "${descdir}/use.desc"); then
892                                         get_flagstatus "${1}"
893                                         echo "$desc"
894                                         # get list of installed packages matching this USE flag.
895                                         IFS=$'\n'
896                                         packages=($(equery -q -C hasuse -i "${1}" | awk '{ print $(NF-1) }' | sort))
897                                         foundone+=${#packages[@]}
898                                         printf "\nInstalled packages matching this USE flag: "
899                                         if [ ${foundone} -gt 0 ]; then
900                                                 echo $'\n'"${packages[*]}"
901                                         else
902                                                 echo "none"
903                                         fi
904                                 fi
905                         ;;
906                         "local")
907                                 # local flags are a bit more complicated as there can be multiple
908                                 # entries per flag and we can't pipe into printf
909                                 IFS=': ' # Use a space instead of a dash because dashes occur in cat/pkg
910                                 while read pkg flag desc; do
911                                         # print name only if package is installed
912                                         # NOTE: If we implement bug #114086 's enhancement we can just use the
913                                         #       exit status of equery instead of a subshell and pipe to wc -l
914                                         # Bug 274472, -e is the default
915                                         if [ $(equery -q -C list -i "${pkg}" | wc -l) -gt 0 ]; then
916                                                 foundone=1
917                                                 IFS="$OIFS"
918                                                 get_flagstatus "${flag}" "${pkg}"
919                                                 IFS=': '
920                                                 printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }"
921                                         fi
922                                 done < <(grep ":${1}  *-" "${descdir}/use.local.desc")
923                         ;;
924                 esac
925                 shift
926         done
927
928         if [ ${foundone} -lt 1 ]; then
929                 echo "no matching entries found"
930         fi
931         IFS="$OIFS"
932 } # }}}
933
934 # Function: showflags {{{
935 # show a list of all currently active flags and where they are activated
936 showflags() {
937         local args
938
939         get_useflags
940
941         args="${*:-*}"
942
943         if [ "${args}" == "*" ]; then
944                 args="$(get_useflaglist | sort -u)"
945         fi
946
947         set ${args}
948         get_portageuseflags
949         
950         while [ -n "${1}" ]; do
951                 if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then
952                         printf "%-20s" ${1}
953                         get_flagstatus ${1}
954                         echo
955                 fi
956                 if echo " ${ACTIVE_FLAGS[4]} " | egrep -e " -?${1} " > /dev/null; then
957                         for pkg in $( echo "${ACTIVE_FLAGS[4]}" | \
958                                         egrep " -?${1} " | cut -d " " -f 2); do
959                                 printf "%-20s" ${1}
960                                 SCOPE="local" get_flagstatus ${1} "${pkg}"
961                                 printf "(%s)\n" ${pkg}
962                         done;
963                 fi
964                 shift
965         done
966 } # }}}
967
968 # two small helpers to add or remove a flag from a USE string
969 add_flag() {
970         # Remove leading '-' from flag if found
971         local flag=$1
972         [[ ${flag:0:1} == "-" ]] && flag=${1:1}
973
974         if [[ -n $(grep " -${flag} " <<< " ${ACTIVE_FLAGS[6]} ") ]]; then
975                 error "Use flag \"${flag}\" is masked and should not be added" \
976                           "to make.conf."
977                 return 1
978         # Bug #104396 -- Only add use flags defined in use.desc and use.local.desc
979         elif [[ -z $(grep "^${flag}$" <<< "$(get_useflaglist)") ]]; then
980                 error "Use flag \"${flag}\" is not defined in use.desc and should" \
981                           "not be added\nto make.conf."
982                 return 1
983         else
984                 NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE} ${1}"
985         echo "Adding flag \"${1}\" to make.conf" >&2
986         fi
987 }
988
989 remove_flag() {
990         NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }"
991         echo "Removing flag \"${1}\" from make.conf" >&2
992 }
993
994 # Function: clean_package_use {{{
995 # Simple utility to remove empty files from package.use
996 clean_package_use() {
997 if [[ -d ${PACKAGE_USE_PATH} ]]; then
998         for f in $(find ${PACKAGE_USE_PATH} -size 0); do
999                 echo "Removing empty file ""${f}"""
1000                 rm ${f}
1001         done;
1002 fi
1003 } # }}}
1004
1005 # Function: scrub_use_flag {{{
1006 # Utility to remove a use flag from a file in package.use[/]
1007
1008 # Arguments:
1009 # 1 - File
1010 # 2 - Use flag
1011 #
1012 # Environment:
1013 # PACKAGE - Package atom for which to remove flag
1014 scrub_use_flag() {
1015         local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?"
1016         local filename=${1}
1017         # Ignore leading - on flag
1018         local flag=${2#*-}
1019         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
1020         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
1021
1022         while read line; do
1023                 # Skip (preserve) comments on their own lines
1024                 if [[ -z $(echo "${line}" | sed -re "s/^ *#.*$//") ]]; then
1025                         echo "${line}"
1026                 # Detect if requested package is defined on this line
1027                 elif [[ -n "${PACKAGE}" ]]; then
1028                         if [[ -n $(echo "${line}" | grep -Ee "${pkg_re}") ]]; then      
1029                                 # If this is the only (remaining) use flag defined
1030                 # for this package, then remove the whole line
1031                 if [[ -z $(echo "${line}" | grep -Ee "${pkg_re} *-?${flag} *$") ]]; then
1032                                         # Remove flag from this line
1033                                         echo "${line}" | sed -re "s/ *-?\b${flag}\b//"
1034                                 fi
1035                         else
1036                                 # Passthru
1037                                 echo "${line}"
1038                         fi
1039                 # If line only has this use flag, let it be removed 
1040                 # (used if PACKAGE is not defined -- from pruning)
1041                 elif [[ -n $(echo "${line}" | \
1042                                 egrep "^[^#]*${atom_re}.*-?${flag}") ]]; then
1043                         echo "Removing use flag from ${line}" >&2
1044                         if [[ -z $(echo "${line}" | \
1045                                         grep -Ee "${atom_re} *-?${flag} *$") ]]; then
1046                                 # Remove flag from this line
1047                                 echo "${line}" | sed -re "s/-?\b${flag}\b//"
1048                         fi
1049                 else
1050                         # Passthru
1051                         echo "${line}"
1052                 fi
1053         done > "${filename}.new" < "${filename}" 
1054         mv "${filename}.new" "${filename}"
1055 } # }}}
1056
1057 # Function: modify_package {{{
1058 # Adds and removes USE flags from individual packages by modifying
1059 # files in package.use. It supports package.use both as a file and
1060 # and as a folder. Also handles "enabling" as removing a disabled flag from
1061 # a file, and "disabling" a globally enabled flag by adding a negative to 
1062 # a file. Also warns about unused and unknown flags, and about flags
1063 # already enabled, disabled, or masked.
1064 #
1065 # Arguments:
1066 # * - USE flag(s) to add or remove
1067 #
1068 # Environment:
1069 # PACKAGE - Package for which to add (-E) or remove (-D) the USE
1070 #           flag(s)
1071 modify_package() {
1072         get_useflags
1073
1074         local atom_re="^[<>]?=?([a-z][0-9a-z/-]+[a-z])(-[0-9pr._*-]+)?"
1075         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
1076         local V=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\2/")
1077         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
1078
1079         local all_flags=$(SCOPE= get_useflaglist)
1080         # Shift at top rather than bottom to support 'continue'
1081         set " " ${*}
1082         while [[ -n ${2} ]]; do
1083         shift
1084         local flag=${1}
1085         local method="add"
1086         ACTIVE="-"
1087         #
1088         # Fetch flag ACTIVE status (+,-,null)
1089         get_flagstatus "${flag}" "${pkg}" > /dev/null
1090         GLOBAL_ACTIVE="$ACTIVE"
1091         # XXX: If V is not given, is this necessary? Should it use the version
1092         #      that would be installed by emerge?
1093         get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}" "${V}" > /dev/null
1094         #
1095         # --- Sanity checks
1096         # (1) make sure ${pkg} exists in portdir or an overlay
1097         local exists=1
1098         for portdir in ${ALL_PORTDIRS[@]}; do
1099                 if [[ -d "${portdir}/${pkg}" ]]; then
1100                         exists=0
1101                         break
1102                 fi
1103         done
1104         if [[ $exists == 1 ]]; then
1105                 fatal "Package \"${pkg}\" does not exist"
1106         #
1107         # (2) make sure ${flag} is defined in get_useflaglist
1108         elif ! array_contains "$all_flags" ${flag}; then
1109                 error "USE flag \"${flag}\" does not exist"
1110                 continue
1111                 # Don't bail just because of this, just warn
1112         # (3) make sure use flag is valid for the package
1113         elif [[ -z $(echo "${ACTIVE_FLAGS[5]} " | grep -Ee "^${pkg_re}" \
1114                         | grep -Ee "[; ][+-]?${flag}") ]]; then
1115                 # XXX: Handle version or version wildcard?
1116                 warn "USE flag \"${flag}\" is not used by $PACKAGE"
1117                 # Don't necessarily bail for this, just warn
1118         elif [[ -n "${V}" && -z "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1119                 error "Invalid package atom. Did you forget the leading '='?"
1120                 continue
1121         elif [[ -z "${V}" && -n "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1122                 error "Invalid package atom. Did you forget the version?"
1123                 continue
1124         fi
1125         # If removing a disabled flag, or adding an enabled one, emit a warning,
1126         # indicating a likely misunderstanding
1127         if [[ "${ACTION}" == "remove" ]]; then
1128                 if [[ "${ACTIVE:-${GLOBAL_ACTIVE}}" == "-" ]]; then
1129                         warn "USE flag \"$flag\" is already disabled for $PACKAGE"
1130                 fi
1131                 flag="-${flag}"
1132         elif [[ "${ACTION}" == "prune" ]]; then
1133                 # Just remove the flag below
1134                 [[ "${ACTIVE}" == "-" ]] && flag="-${flag}"
1135                 method="remove"
1136         elif [[ "${ACTION}" == "add" ]]; then
1137                 if [[ "${ACTIVE:-${GLOBAL_ACTIVE:--}}" == "+" ]]; then
1138                         # XXX: Perhaps look at indicating where it is enabled
1139                         warn "USE flag \"$flag\" is already enabled for $PACKAGE"
1140                 fi
1141         fi
1142         case "${method}" in
1143                 "add")
1144                         local filename
1145                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1146                                 # Use naming convention of package.use/package
1147                                 filename="${PACKAGE_USE_PATH}/${pkg#*/}"
1148                                 if [[ ! -s "${filename}" ]]; then
1149                                         # Create new file to contain flag
1150                                         echo "${PACKAGE} ${flag}" > "${filename}"
1151                                         echo "Adding \"${PACKAGE}[${flag}]\" use flag to new file ""${filename}"""
1152                                         continue
1153                                 fi
1154                         else    
1155                                 # Add to package.use file instead
1156                                 filename="${PACKAGE_USE_PATH}"
1157                                 # Create as necessary
1158                                 touch "${filename}"
1159                         fi
1160                         # Walk through the file and add the flag manually
1161                         echo "Adding \"${PACKAGE}[${flag}]\" use flag in \"${filename}\""
1162                         local added=0
1163                         while read line; do
1164                                 if [[ -n $(echo "${line}" | egrep -e "^[^#]*${PACKAGE} ") ]]; then
1165                                         echo $(reduce_package_use "${line} ${flag}")
1166                                         added=1
1167                                 else
1168                                         # Passthru
1169                                         echo "${line}"
1170                                 fi
1171                         done < "${filename}" > "${filename}.new"
1172                         mv "${filename}.new" "${filename}"
1173                         if [[ ${added} -eq 0 ]]; then
1174                                 echo "${PACKAGE} ${flag}" >> "${filename}"
1175                         fi
1176                         ;;
1177                 "remove")
1178                         local filename
1179                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1180                                 # Scan for file containing named package and use flag
1181                                 filename=$(egrep -rle "${pkg_re}.*[^-]${flag}( |$)" "${PACKAGE_USE_PATH}")
1182                                 if [[ -z "${filename}" ]]; then
1183                                         error ""${flag}" is not defined for package "${PACKAGE}""
1184                                         continue
1185                                 fi
1186                         else
1187                                 # Remove from package.use instead
1188                                 filename=${PACKAGE_USE_PATH}
1189                                 # Create as necessary
1190                                 touch "${filename}"
1191                         fi
1192                         # Scrub use flag from matched files
1193                         for f in ${filename}; do
1194                                 # Remove current flags in file
1195                                 echo "Removing \"${PACKAGE}[${flag}]\" use flag in \"${f}\""
1196                                 scrub_use_flag ${f} ${flag}
1197                         done
1198                         # Remove empty files
1199                         clean_package_use
1200                         ;;
1201         esac
1202         done
1203 } # }}}
1204
1205 # Function: modify {{{
1206 # USE flag modification function. Mainly a loop with calls to add_flag and 
1207 # remove_flag to create a new USE string which is then inserted into make.conf.
1208 modify() {
1209         if [[ -n "${PACKAGE}" ]]; then
1210                 modify_package "${*}"
1211                 return;
1212         fi;
1213
1214         local make_conf_modified=0
1215
1216         if [ -z "${*}" ]; then
1217                 if [ "${ACTION}" != "prune" ]; then
1218                         echo "WARNING: no USE flags listed for modification, do you really"
1219                         echo "         want to ${ACTION} *all* known USE flags?"
1220                         echo "         If you don't please press Ctrl-C NOW!!!"
1221                         sleep 5
1222                         set $(get_useflaglist | sort -u)
1223                 fi
1224         fi
1225
1226         get_useflags
1227
1228         NEW_MAKE_CONF_USE=" ${ACTIVE_FLAGS[1]} "
1229
1230         while [[ -n "${1}" ]]; do
1231                 if [[ "${ACTION}" == "add" ]]; then
1232                         if  [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1233                                 warn "Use flag \"${1}\" is already enabled globally"
1234                                 shift
1235                         elif [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1236                                 remove_flag "-${1}" || exit
1237                                 make_conf_modified=1
1238                         else
1239                                 add_flag "${1}" || exit
1240                                 make_conf_modified=1
1241                                 shift
1242                         fi
1243                 elif [[ "${ACTION}" == "remove" ]]; then
1244                         if [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1245                                 warn "Use flag \"${1}\" is already disabled globally"
1246                                 shift
1247                         elif [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1248                                 remove_flag "${1}" || exit
1249                                 make_conf_modified=1
1250                         else
1251                                 add_flag "-${1}" || exit
1252                                 make_conf_modified=1
1253                                 shift
1254                         fi
1255                 elif [[ "${ACTION}" == "prune" ]]; then
1256                         if [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1257                                 remove_flag "${1}" || exit
1258                                 make_conf_modified=1
1259                         elif [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1260                                 remove_flag "-${1}" || exit
1261                                 make_conf_modified=1
1262                         else
1263                                 warn "Use flag \"${1}\" is not set globally"
1264                         fi
1265                         shift
1266                 fi
1267         done
1268
1269         # a little loop to add linebreaks so we don't end with one ultra-long line
1270         NEW_MAKE_CONF_USE_2=""
1271         for x in ${NEW_MAKE_CONF_USE}; do
1272                 if [ $(((${#NEW_MAKE_CONF_USE_2}%70)+${#x}+2)) -gt 70 ]; then
1273                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}\\ \\n     $x "
1274                 else
1275                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}${x} "
1276                 fi
1277         done
1278
1279         # Bail if there is no need to modify make.conf
1280         [[ ${make_conf_modified} == 1 ]] || return
1281         # make a backup just in case the user doesn't like the new make.conf
1282         cp -p "${MAKE_CONF_PATH}" "${MAKE_CONF_BACKUP_PATH}"
1283
1284         # as sed doesn't really work with multi-line patterns we have to replace USE
1285         # on our own here. Basically just skip everything between USE=" and the
1286         # closing ", printing our new USE line there instead.
1287         inuse=0
1288         had_use=0
1289         x=0
1290         (while [ "$x" -eq "0" ]; do
1291                 read -r line || break
1292                 x="$?"
1293                 # Bug 275362 - Handle the case where make.conf includes:
1294                 # USE="
1295                 # a b
1296                 # "
1297                 # Consume USE=" when detected so the quote won't be detected
1298                 # as the ending quote
1299                 if [ "${line:0:4}" == "USE=" ]; then inuse=1; line=${line:5}; fi
1300                 [ "${inuse}" == "0" ] && echo -E "${line}"
1301                 if [ "${inuse}" == "1" ] && echo "${line}" | egrep '" *(#.*)?$' > /dev/null; then
1302                         echo -n 'USE="'
1303                         echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1304                         echo '"'
1305                         inuse=0
1306                         had_use=1
1307                 fi
1308         done
1309         if [ ${had_use} -eq 0 ]; then
1310                 echo -n 'USE="'
1311                 echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1312                 echo '"'
1313         fi ) < "${MAKE_CONF_BACKUP_PATH}" | sed -e 's:\\ $:\\:' > "${MAKE_CONF_PATH}"
1314
1315         echo "${MAKE_CONF_PATH} was modified, a backup copy has been placed at ${MAKE_CONF_BACKUP_PATH}"
1316 } # }}}
1317
1318 ##### main program comes now #####
1319
1320 parse_arguments "$@"
1321 check_sanity
1322
1323 eval ${MODE} ${ARGUMENTS}
1324
1325 # vim: set tabstop=4 shiftwidth=4: