Set the encoding to 'utf_8', if the encoding returned by
[gentoolkit.git] / bin / euse
1 #!/bin/bash
2
3 # $Header$
4
5 # bash replacement for the original euse by Arun Bhanu
6 # Author: Marius Mauch <genone@gentoo.org>
7 # Licensed under the GPL v2
8
9 PROGRAM_NAME=euse
10 VERSION="svn"
11
12 EPREFIX=${EPREFIX:-$(portageq envvar EPREFIX)}
13 ETC="${EPREFIX}/etc"
14 USR_SHARE_PORTAGE="${EPREFIX}/usr/share/portage"
15
16 # define error function so it can be used immediately
17 error() {
18         echo "ERROR: ${1}"
19         set +f
20         exit 1
21 }
22
23 # /etc/make.conf can now exist in /etc/portage/make.conf, prefer it over /etc/make.conf for changes
24 if [ -e "${ETC}/portage/make.conf" ]; then
25         MAKE_CONF_PATH="${ETC}/portage/make.conf"
26 elif [ -e "${ETC}/make.conf" ]; then
27         MAKE_CONF_PATH="${ETC}/make.conf"
28 else
29         error "make.conf does not exist"
30 fi
31 MAKE_CONF_BACKUP_PATH="${MAKE_CONF_PATH}.euse_backup"
32
33 # /etc/make.globals has been moved to /usr/share/portage/config/make.globals
34 if [ -e "${USR_SHARE_PORTAGE}/config/make.globals" ]; then
35         MAKE_GLOBALS_PATH="${USR_SHARE_PORTAGE}/config/make.globals"
36 else
37         MAKE_GLOBALS_PATH="${ETC}/make.globals"
38 fi
39
40 # /etc/make.profile or /etc/portage/make.profile, if /etc/make.profile exists, it will be used
41 if [ -e "${ETC}/make.profile" ]; then
42         MAKE_PROFILE_PATH="${ETC}/make.profile"
43 elif [ -e "${ETC}/portage/make.profile" ]; then
44         MAKE_PROFILE_PATH="${ETC}/portage/make.profile"
45 else
46         error "make.profile does not exist"
47 fi
48
49 [ -z "${MODE}" ] && MODE="showhelp"             # available operation modes: showhelp, showversion, showdesc, showflags, modify
50
51 parse_arguments() {
52         if [ -z "${1}" ]; then
53                 return
54         fi
55         while [ -n "${1}" ]; do
56                 case "${1}" in
57                         -h | --help)           MODE="showhelp";;
58                         -V | -v | --version)   MODE="showversion";;
59                         -i | --info)           MODE="showdesc";;
60                         -I | --info-installed) MODE="showinstdesc";;
61                         -l | --local)          SCOPE="local";;
62                         -g | --global)         SCOPE="global";;
63                         -a | --active)         MODE="showflags";;
64                         -E | --enable)         MODE="modify"; ACTION="add";;
65                         -D | --disable)        MODE="modify"; ACTION="remove";;
66                         -P | --prune)          MODE="modify"; ACTION="prune";;
67                         -*)
68                                 echo "ERROR: unknown option ${1} specified."
69                                 echo
70                                 MODE="showhelp"
71                                 ;;
72                         "%active")
73                                 get_useflags
74                                 ARGUMENTS="${ARGUMENTS} ${ACTIVE_FLAGS[9]}"
75                                 ;;
76                         *)
77                                 ARGUMENTS="${ARGUMENTS} ${1}"
78                                 ;;
79                 esac
80                 shift
81         done
82 }
83
84 get_real_path() {
85         set -P
86         cd "$1"
87         pwd
88         cd "$OLDPWD"
89         set +P
90 }
91
92 check_sanity() {
93         # file permission tests
94         local descdir
95         local make_defaults
96         local make_conf
97
98         [[ ! -d "${MAKE_PROFILE_PATH}" || ! -r "${MAKE_PROFILE_PATH}" ]] && error "${MAKE_PROFILE_PATH} is not readable"
99         # 
100         for make_conf in $(get_all_make_conf); do
101                 [ ! -r "${make_conf}" ] && error "${make_conf} is not readable"
102         done
103
104         descdir="$(get_portdir)/profiles"
105
106         [ ! -r "${MAKE_GLOBALS_PATH}" ] && error "${MAKE_GLOBALS_PATH} is not readable"
107         [ -z "$(get_portdir)" ] && error "\$PORTDIR couldn't be determined"
108         [ ! -d "${descdir}" ] && error "${descdir} does not exist or is not a directory"
109         [ ! -r "${descdir}/use.desc" ] && error "${descdir}/use.desc is not readable"
110         [ ! -r "${descdir}/use.local.desc" ] && error "${descdir}/use.local.desc is not readable"
111         for make_defaults in $(get_all_make_defaults); do
112                 [ ! -r "$make_defaults" ]  && error "$_make_defaults is not readable"
113         done
114 #       [ ! -r "$(get_make_defaults)" ] && error "$(get_make_defaults) is not readable"
115         [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && error ""${MAKE_CONF_PATH}" is not writable"
116 }
117
118 showhelp() {
119 cat << HELP
120 ${PROGRAM_NAME} (${VERSION})
121
122 Syntax: ${PROGRAM_NAME} <option> [suboptions] [useflaglist]
123
124 Options: -h, --help           - show this message
125          -V, --version        - show version information
126          -i, --info           - show descriptions for the given useflags
127          -I, --info-installed - show descriptions for the given useflags and
128                                 their current impact on the installed system
129          -g, --global         - show only global use flags (suboption)
130          -l, --local          - show only local use flags (suboption)
131          -a, --active         - show currently active useflags and their origin
132          -E, --enable         - enable the given useflags
133          -D, --disable        - disable the given useflags
134          -P, --prune          - remove all references to the given flags from
135                                 make.conf to revert to default settings
136
137 Notes: ${PROGRAM_NAME} currently only works for global flags defined
138        in make.globals, make.defaults or make.conf, it doesn't handle
139        use.defaults, use.mask or package.use yet (see portage(5) for details on
140        these files). It also might have issues with cascaded profiles.
141        If multiple options are specified only the last one will be used.
142 HELP
143 }
144
145 showversion() {
146 cat << VER
147 ${PROGRAM_NAME} (${VERSION})
148 Written by Marius Mauch
149
150 Copyright (C) 2004-2009 Gentoo Foundation, Inc.
151 This is free software; see the source for copying conditions.
152 VER
153 }
154
155 # remove duplicate flags from the given list in both positive and negative forms
156 # (but unlike portage always keep the last value even if it's negative)
157 # Otherwise the status flags could be incorrect if a flag appers multiple times in
158 # one location (like make.conf).
159 # Using python here as bash sucks for list handling.
160 # NOTE: bash isn't actually that bad at handling lists -- sh is. This may be
161 #       worth another look to avoid calling python unnecessariy. Or we could
162 #       just write the whole thing in python. ;)
163 reduce_incrementals() {
164         echo $@ | python -c "import sys
165 r=[]
166 for x in sys.stdin.read().split():
167         if x[0] == '-' and x[1:] in r:
168                 r.remove(x[1:])
169                 r.append(x)
170         elif x[0] != '-' and '-'+x in r:
171                 r.remove('-'+x)
172                 r.append(x)
173         elif x == '-*':
174                 r = []
175                 r.append(x)
176         elif x not in r:
177                 r.append(x)
178 print(' '.join(r))"
179 }
180
181 # the following function creates a bash array ACTIVE_FLAGS that contains the
182 # global use flags, indexed by origin: 0: environment, 1: make.conf,
183 # 2: make.defaults, 3: make.globals
184 get_useflags() {
185         # only calculate once as calling emerge is painfully slow
186         [ -n "${USE_FLAGS_CALCULATED}" ] && return
187
188         # backup portdir so get_portdir() doesn't give false results later
189         portdir_backup="${PORTDIR}"
190
191         ACTIVE_FLAGS[0]="$(reduce_incrementals ${USE})"
192         USE=""
193         for x in $(get_all_make_conf); do
194                 source "${x}"
195                 ACTIVE_FLAGS[1]="$(reduce_incrementals ${ACTIVE_FLAGS[1]} ${USE})"
196         done
197         USE=""
198         for x in $(get_all_make_defaults); do
199                 source "${x}"
200                 ACTIVE_FLAGS[2]="$(reduce_incrementals ${ACTIVE_FLAGS[2]} ${USE})"
201         done
202         USE=""
203         source "${MAKE_GLOBALS_PATH}"
204         ACTIVE_FLAGS[3]="$(reduce_incrementals ${USE})"
205
206         # restore saved env variables
207         USE="${ACTIVE_FLAGS[0]}"
208         PORTDIR="${portdir_backup}"
209
210         # get the currently active USE flags as seen by portage, this has to be after
211         # restoring USE or portage won't see the original environment
212         ACTIVE_FLAGS[9]="$(portageq envvar USE)" #'
213         USE_FLAGS_CALCULATED=1
214 }
215
216 # get the list of all known USE flags by reading use.desc and/or use.local.desc
217 # (depending on the value of $SCOPE)
218 get_useflaglist() {
219         local descdir
220
221         descdir="$(get_portdir)/profiles"
222
223         if [ -z "${SCOPE}" -o "${SCOPE}" == "global" ]; then
224                 egrep "^[^# ]+ +-" "${descdir}/use.desc" | cut -d\  -f 1
225         fi
226         if [ -z "${SCOPE}" -o "${SCOPE}" == "local" ]; then
227                 egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" | cut -d: -f 2 | cut -d\  -f 1
228         fi
229 }
230
231 # get all make.conf files that exist on the system
232 get_all_make_conf() {
233         # At least one of the files exists or we would not have made it this far
234         for x in ${ETC}/make.conf ${ETC}/portage/make.conf; do
235                 [ -e "${x}" ] && echo "${x}"
236         done
237 }
238 # get all make.defaults by traversing the cascaded profile directories
239 get_all_make_defaults() {
240         local curdir
241         local parent
242         local rvalue
243
244         curdir="${1:-$(get_real_path ${MAKE_PROFILE_PATH})}"
245
246         [ -f "${curdir}/make.defaults" ] && rvalue="${curdir}/make.defaults ${rvalue}"
247         if [ -f "${curdir}/parent" ]; then
248                 for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do
249                         pdir="$(get_real_path ${curdir}/${parent})"
250                         rvalue="$(get_all_make_defaults ${pdir}) ${rvalue}"
251                 done
252         fi
253
254         echo "${rvalue}"
255 }
256
257 # get the path to make.defaults by traversing the cascaded profile directories
258 get_make_defaults() {
259         local curdir
260         local parent
261
262         curdir="${1:-$(get_real_path ${MAKE_PROFILE_PATH})}"
263
264         if [ ! -f "${curdir}/make.defaults" -a -f "${curdir}/parent" ]; then
265                 for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do
266                         if [ -f "$(get_make_defaults ${curdir}/${parent})" ]; then
267                                 curdir="${curdir}/${parent}"
268                                 break
269                         fi
270                 done
271         fi
272
273         echo "${curdir}/make.defaults"
274 }
275
276 # little helper function to get the status of a given flag in one of the
277 # ACTIVE_FLAGS elements. Arguments are 1: flag to test, 2: index of ACTIVE_FLAGS,
278 # 3: echo value for positive (and as lowercase for negative) test result,
279 # 4 (optional): echo value for "missing" test result, defaults to blank
280 get_flagstatus_helper() {
281         if echo " ${ACTIVE_FLAGS[${2}]} " | grep " ${1} " > /dev/null; then
282                 echo -n "${3}"
283         elif echo " ${ACTIVE_FLAGS[${2}]} " | grep " -${1} " > /dev/null; then
284                 echo -n "$(echo ${3} | tr [[:upper:]] [[:lower:]])"
285         else
286                 echo -n "${4:- }"
287         fi
288 }
289
290 # prints a status string for the given flag, each column indicating the presence
291 # for portage, in the environment, in make.conf, in make.defaults and in make.globals.
292 # full positive value would be "[+ECDG]", full negative value would be [-ecdg],
293 # full missing value would be "[-    ]" (portage only sees present or not present)
294 get_flagstatus() {
295         get_useflags
296
297         echo -n '['
298         get_flagstatus_helper "${1}" 9 "+" "-"
299         get_flagstatus_helper "${1}" 0 "E"
300         get_flagstatus_helper "${1}" 1 "C"
301         get_flagstatus_helper "${1}" 2 "D"
302         get_flagstatus_helper "${1}" 3 "G"
303         echo -n '] '
304 }
305
306 # faster replacement to `portageq portdir`
307 get_portdir() {
308         if [ -z "${PORTDIR}" ]; then
309                 use_backup="${USE}"
310                 source "${MAKE_GLOBALS_PATH}"
311                 for x in $(get_all_make_defaults); do
312                         source "${x}"
313                 done
314                 for x in $(get_all_make_conf); do
315                         source "${x}"
316                 done
317                 USE="${use_backup}"
318         fi
319         echo "${PORTDIR}"
320 }
321
322 # This function takes a list of use flags and shows the status and
323 # the description for each one, honoring $SCOPE
324 showdesc() {
325         local descdir
326         local current_desc
327         local found_one
328         local args
329
330         args="${*:-*}"
331
332         if [ -z "${SCOPE}" ]; then
333                 SCOPE="global" showdesc ${args}
334                 echo
335                 SCOPE="local" showdesc ${args}
336                 return
337         fi
338
339         descdir="$(get_portdir)/profiles"
340
341         [ "${SCOPE}" == "global" ] && echo "global use flags (searching: ${args})"
342         [ "${SCOPE}" == "local" ] && echo "local use flags (searching: ${args})"
343         echo "************************************************************"
344
345         if [ "${args}" == "*" ]; then
346                 args="$(get_useflaglist | sort -u)"
347         fi
348
349         set ${args}
350
351         foundone=0
352         while [ -n "${1}" ]; do
353                 if [ "${SCOPE}" == "global" ]; then
354                         if grep "^${1}  *-" "${descdir}/use.desc" > /dev/null; then
355                                 get_flagstatus "${1}"
356                                 foundone=1
357                         fi
358                         grep "^${1}  *-" "${descdir}/use.desc"
359                 fi
360                 # local flags are a bit more complicated as there can be multiple
361                 # entries per flag and we can't pipe into printf
362                 if [ "${SCOPE}" == "local" ]; then
363                         if grep ":${1}  *-" "${descdir}/use.local.desc" > /dev/null; then
364                                 foundone=1
365                         fi
366                         grep ":${1}  *-" "${descdir}/use.local.desc" \
367                                 | sed -e "s/^\([^:]\+\):\(${1}\) *- *\(.\+\)/\1|\2|\3/g" \
368                                 | while read line; do
369                                         pkg="$(echo $line | cut -d\| -f 1)"
370                                         flag="$(echo $line | cut -d\| -f 2)"
371                                         desc="$(echo $line | cut -d\| -f 3)"
372                                         get_flagstatus "${flag}"
373                                         printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc}"
374                                 done
375                 fi
376                 shift
377         done
378
379         if [ ${foundone} == 0 ]; then
380                 echo "no matching entries found"
381         fi
382 }
383
384 # Works like showdesc() but displays only descriptions of which the appropriate
385 # ebuild is installed and prints the name of those packages.
386 showinstdesc() {
387         local descdir
388         local current_desc
389         local args
390         local -i foundone=0
391         local OIFS="$IFS"
392
393         args=("${@:-*}")
394
395         case "${SCOPE}" in
396                 "global") echo "global use flags (searching: ${args})";;
397                  "local") echo "local use flags (searching: ${args})";;
398                        *) SCOPE="global" showinstdesc "${args[@]}"
399                           echo
400                           SCOPE="local" showinstdesc "${args[@]}"
401                           return;;
402         esac
403
404         descdir="$(get_portdir)/profiles"
405         echo "************************************************************"
406
407         if [ "${args}" = "*" ]; then
408                 args="$(get_useflaglist | sort -u)"
409         fi
410
411         set "${args[@]}"
412
413         while [ -n "${1}" ]; do
414                 case "${SCOPE}" in
415                         "global")
416                                 if desc=$(grep "^${1}  *-" "${descdir}/use.desc"); then
417                                         get_flagstatus "${1}"
418                                         echo "$desc"
419                                         # get list of installed packages matching this USE flag.
420                                         IFS=$'\n'
421                                         packages=($(equery -q -C hasuse -i "${1}" | awk '{ print $(NF-1) }' | sort))
422                                         foundone+=${#packages[@]}
423                                         printf "\nInstalled packages matching this USE flag: "
424                                         if [ ${foundone} -gt 0 ]; then
425                                                 echo $'\n'"${packages[*]}"
426                                         else
427                                                 echo "none"
428                                         fi
429                                 fi
430                         ;;
431                         "local")
432                                 # local flags are a bit more complicated as there can be multiple
433                                 # entries per flag and we can't pipe into printf
434                                 IFS=': ' # Use a space instead of a dash because dashes occur in cat/pkg
435                                 while read pkg flag desc; do
436                                         # print name only if package is installed
437                                         # NOTE: If we implement bug #114086 's enhancement we can just use the
438                                         #       exit status of equery instead of a subshell and pipe to wc -l
439                                         if [ $(equery -q -C list -i -e "${pkg}" | wc -l) -gt 0 ]; then
440                                                 foundone=1
441                                                 IFS="$OIFS"
442                                                 get_flagstatus "${flag}"
443                                                 IFS=': '
444                                                 printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }"
445                                         fi
446                                 done < <(grep ":${1}  *-" "${descdir}/use.local.desc")
447                         ;;
448                 esac
449                 shift
450         done
451
452         if [ ${foundone} -lt 1 ]; then
453                 echo "no matching entries found"
454         fi
455         IFS="$OIFS"
456 }
457
458 # show a list of all currently active flags and where they are activated
459 showflags() {
460         local args
461
462         get_useflags
463
464         args="${*:-*}"
465
466         if [ "${args}" == "*" ]; then
467                 args="$(get_useflaglist | sort -u)"
468         fi
469
470         set ${args}
471
472         while [ -n "${1}" ]; do
473                 if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then
474                         printf "%-20s" ${1}
475                         get_flagstatus ${1}
476                         echo
477                 fi
478                 shift
479         done
480 }
481
482 # two small helpers to add or remove a flag from a USE string
483 add_flag() {
484         NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE} ${1}"
485 }
486
487 remove_flag() {
488         NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }"
489 }
490
491 # USE flag modification function. Mainly a loop with calls to add_flag and
492 # remove_flag to create a new USE string which is then inserted into make.conf.
493 modify() {
494         if [ -z "${*}" ]; then
495                 if [ "${ACTION}" != "prune" ]; then
496                         echo "WARNING: no USE flags listed for modification, do you really"
497                         echo "         want to ${ACTION} *all* known USE flags?"
498                         echo "         If you don't please press Ctrl-C NOW!!!"
499                         sleep 5
500                         set $(get_useflaglist | sort -u)
501                 fi
502         fi
503
504         get_useflags
505
506         NEW_MAKE_CONF_USE=" ${ACTIVE_FLAGS[1]} "
507
508         while [ -n "${1}" ]; do
509                 if [ "${ACTION}" == "add" ]; then
510                         if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
511                                 shift
512                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
513                                 remove_flag "-${1}"
514                         else
515                                 add_flag "${1}"
516                                 shift
517                         fi
518                 elif [ "${ACTION}" == "remove" ]; then
519                         if echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
520                                 shift
521                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
522                                 remove_flag "${1}"
523                         else
524                                 add_flag "-${1}"
525                                 shift
526                         fi
527                 elif [ "${ACTION}" == "prune" ]; then
528                         if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
529                                 remove_flag "${1}"
530                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
531                                 remove_flag "-${1}"
532                         fi
533                         shift
534                 fi
535         done
536
537         #echo "old flags:"
538         #echo ${ACTIVE_FLAGS[1]}
539         #echo
540         #echo "new flags:"
541         #echo ${NEW_MAKE_CONF_USE}
542
543         # a little loop to add linebreaks so we don't end with one ultra-long line
544         NEW_MAKE_CONF_USE_2=""
545         for x in ${NEW_MAKE_CONF_USE}; do
546                 if [ $(((${#NEW_MAKE_CONF_USE_2}%70)+${#x}+2)) -gt 70 ]; then
547                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}\\ \\n     $x "
548                 else
549                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}${x} "
550                 fi
551         done
552
553         # make a backup just in case the user doesn't like the new make.conf
554         cp -p "${MAKE_CONF_PATH}" "${MAKE_CONF_BACKUP_PATH}"
555
556         # as sed doesn't really work with multi-line patterns we have to replace USE
557         # on our own here. Basically just skip everything between USE=" and the
558         # closing ", printing our new USE line there instead.
559         inuse=0
560         had_use=0
561         x=0
562         (while [ "$x" -eq "0" ]; do
563                 read -r line
564                 x="$?"
565                 [[ "${x}" -ne "0" ]] && break
566                 [ "${line:0:4}" == "USE=" ] && inuse=1
567                 [ "${inuse}" == "0" ] && echo -E "${line}"
568                 if [ "${inuse}" == "1" ] && echo "${line}" | egrep '" *(#.*)?$' > /dev/null; then
569                         echo -n 'USE="'
570                         echo -ne "${NEW_MAKE_CONF_USE_2%% }"
571                         echo '"'
572                         inuse=0
573                         had_use=1
574                 fi
575         done
576         if [ ${had_use} -eq 0 ]; then
577                 echo -n 'USE="'
578                 echo -ne "${NEW_MAKE_CONF_USE_2%% }"
579                 echo '"'
580         fi ) < "${MAKE_CONF_BACKUP_PATH}" | sed -e 's:\\ $:\\:' > "${MAKE_CONF_PATH}"
581
582         echo "${MAKE_CONF_PATH} was modified, a backup copy has been placed at ${MAKE_CONF_BACKUP_PATH}"
583 }
584
585 ##### main program comes now #####
586
587 # disable globbing as it fucks up with args=*
588 set -f
589 parse_arguments "$@"
590 check_sanity
591
592 eval ${MODE} ${ARGUMENTS}
593 set +f