Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@whitslack
Last active March 2, 2024 07:55
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whitslack/7a831faf3c6b0abf64077faeaafed015 to your computer and use it in GitHub Desktop.
Save whitslack/7a831faf3c6b0abf64077faeaafed015 to your computer and use it in GitHub Desktop.
Prints diagnostics pertaining to redundant USE flags specified in /etc/portage/make.conf and /etc/portage/package.use/*.
# Accumulates flags in the associative array named by ${1}.
accumulate() {
declare -Ag ${1?}
local -n var=${1} ; shift
local flag ; for flag in "${@}" ; do
case ${flag} in
'-*') var=( ['*']=0 ) ;;
-*) var[${flag:1}]=0 ;;
*) var[${flag}]=1 ;;
esac
done
}
# Outputs a pre-order traversal of profile dirs starting at ${1}.
collect_profile_dirs() {
local dir ; for dir in "${@}" ; do
dir=$(readlink -e -- "${dir}")
if [[ -s ${dir}/parent ]] ; then
local parent ; while read -r parent ; do
collect_profile_dirs "${dir}/${parent}"
done < "${dir}/parent"
fi
echo "${dir}"
done
}
# Outputs a pre-order traversal of active profile files named in ${@}.
collect_profile_files() {
local -a dirs ; mapfile -t dirs <<< "$(collect_profile_dirs "${PORTAGE_CONFIGROOT%/}/etc/portage/"{make.,}profile)"
local file ; for file in "${@}" ; do
local dir ; for dir in "${dirs[@]}" ; do
[[ -e ${dir}/${file} ]] && echo "${dir}/${file}"
done
done
}
# Parses and splits a package atom into its constituent components.
#
# split_atom '>=dev-lang/python-3.4.3-r7:3.4/3.4m::gentoo'
# OP='>='
# CATEGORY=dev-lang
# PF=python-3.4.3-r7
# P=python-3.4.3
# PN=python
# PV=3.4.3
# PVR=3.4.3-r7
# PR=r7
# SLOT=3.4
# SUBSLOT=3.4m
# REPO=gentoo
split_atom() {
PF=${1?}
case ${PF} in
'>='*) OP='>=' ; PF=${PF#'>='} ;;
'>'*) OP='>' ; PF=${PF#'>'} ;;
'~'*) OP='~' ; PF=${PF#'~'} ;;
'='*) OP='=' ; PF=${PF#'='} ;;
'<='*) OP='<=' ; PF=${PF#'<='} ;;
'<'*) OP='<' ; PF=${PF#'<'} ;;
*) OP= ;;
esac
if [[ ${PF} == *::* ]] ; then
REPO=${PF##*::} ; PF=${PF%::*}
else
REPO=
fi
if [[ ${PF} == *:* ]] ; then
SLOT=${PF##*:} ; PF=${PF%:*}
if [[ ${SLOT} == */* ]] ; then
SUBSLOT=${SLOT#*/} ; SLOT=${SLOT%%/*}
else
SUBSLOT=
fi
else
SLOT= ; SUBSLOT=
fi
if [[ ${PF} == */* ]] ; then
CATEGORY=${PF%/*} ; PF=${PF##*/}
else
CATEGORY=
fi
PR=${PF##*-}
if [[ ${PR} == r[0-9]* ]] ; then
P=${PF%-*} ; PVR=${PR}
else
PR=r0 ; P=${PF} ; PVR=
fi
PV=${P##*-}
if [[ ${PV} == [0-9]* ]] ; then
PN=${P%-*}
else
PV= ; PR= ; PN=${P}
fi
PVR=${PV}${PVR:+-${PVR}}
}
# Compares two versions according to PMS rules. Outputs -1, 0, or 1.
compare_versions() {
local shopt=$(shopt -p extglob) ; shopt -s extglob
local v1=${1?} v2=${2?} r1 r2 p1 p2 ret=0
while [[ ${v1} || ${v2} ]] ; do
r1=${v1##*([[:digit:]])} ; p1=${v1%${r1}}
r2=${v2##*([[:digit:]])} ; p2=${v2%${r2}}
if [[ ${p1} == 0* || ${p2} == 0* ]] ; then
p1=${p1%%*(0)} ; p2=${p2%%*(0)}
if [[ ${p1} > ${p2} ]] ; then
ret=1 ; break
elif [[ ${p1} < ${p2} ]] ; then
ret=-1 ; break
fi
else
if (( p1 > p2 )) ; then
ret=1 ; break
elif (( p1 < p2 )) ; then
ret=-1 ; break
fi
fi
v1=${r1##*([[:alpha:][:punct:]])} ; p1=${r1%${v1}}
v2=${r2##*([[:alpha:][:punct:]])} ; p2=${r2%${v2}}
case ${p1} in
_alpha) p1='!0' ;; _beta) p1='!1' ;; _pre) p1='!2' ;; _rc) p1='!3' ;;
esac
case ${p2} in
_alpha) p2='!0' ;; _beta) p2='!1' ;; _pre) p2='!2' ;; _rc) p2='!3' ;;
esac
[[ ! ${p1} && ${p2} == '!'* ]] && p1='!9'
[[ ! ${p2} && ${p1} == '!'* ]] && p2='!9'
if [[ ${p1} > ${p2} ]] ; then
ret=1 ; break
elif [[ ${p1} < ${p2} ]] ; then
ret=-1 ; break
fi
done
echo "${ret}"
eval "${shopt}"
}
# Returns whether package atom ${2} is a subset of package atom ${1}.
atom_matches() {
local atom1=${1?} atom2=${2?} OP CATEGORY PF P PN PV PVR_ PVR PR SLOT SUBSLOT REPO
split_atom "${atom1}" ; PV=${PV%'*'} ; PVR_=${PVR} ; PVR=${PVR%'*'}
local OP1=${OP} CATEGORY1=${CATEGORY} PN1=${PN} PV1=${PV} PVR1_=${PVR_} PVR1=${PVR} SLOT1=${SLOT} SUBSLOT1=${SUBSLOT} REPO1=${REPO}
split_atom "${atom2}" ; PV=${PV%'*'} ; PVR_=${PVR} ; PVR=${PVR%'*'}
[[ ( ${CATEGORY} == '*' || ${CATEGORY1} == '*' || ${CATEGORY} == ${CATEGORY1} ) &&
( ${PN} == '*' || ${PN1} == '*' || ${PN} == ${PN1} ) &&
( ! ${SLOT1} || ${SLOT:-0} == ${SLOT1} ) && ( ! ${SUBSLOT1} || ${SUBSLOT:-0} == ${SUBSLOT1} ) &&
( ! ${REPO1} || ${REPO} == ${REPO1} ) ]] || return
if [[ ${OP1} == '' ]] ; then
return
elif [[ ${OP1} == '~' ]] ; then
[[ ${OP} == @(''|'~'|'=') ]] && (( $(compare_versions "${PV}" "${PV1}") == 0 )) ; return
elif [[ ${OP1} == '=' && ${PVR1_} == *'*' ]] ; then
[[ ${OP} == @(''|'~'|'=') ]] && [[ ${PVR} == ${PVR1}* ]] ; return
fi
local cmp=$(compare_versions "${PVR}" "${PVR1}")
case ${OP1} in
'>=') (( cmp >= 0 )) && [[ ${OP} == @(''|'>='|'>'|'~'|'=') ]] ;;
'>') (( cmp > 0 )) && [[ ${OP} == @(''|'>='|'>'|'~'|'=') ]] || { (( cmp == 0 )) && [[ ${OP} == '>' ]] ; } ;;
'=') (( cmp == 0 )) && [[ ${OP} == @(''|'=') && ${PVR_} != *'*' ]] ;;
'<=') (( cmp <= 0 )) && [[ ${OP} == @(''|'<='|'<'|'~'|'=') ]] ;;
'<') (( cmp < 0 )) && [[ ${OP} == @(''|'<='|'<'|'~'|'=') ]] || { (( cmp == 0 )) && [[ ${OP} == '<' ]] ; } ;;
esac
}
#!/bin/bash
#
# Prints diagnostics pertaining to redundant USE flags specified in
# /etc/portage/make.conf and /etc/portage/package.use/*.
shopt -s nullglob
. "$(dirname -- "$(readlink -e -- "${0}")")"/portage-utils.bash
grep_non_comments() {
grep --no-filename --dereference-recursive '^[[:space:]]*[^#[:space:]]' -- "${@}"
}
# gather profile-default global USE flags
accumulate PROFILE_USE $(
while read -r file ; do
( unset USE ; . "${file}" ; echo "${USE}" )
done <<< "$(collect_profile_files make.defaults)"
)
# gather profile-forced global USE flags
accumulate FORCED_USE $(
mapfile -t files <<< "$(collect_profile_files use{,.stable}.force)"
grep_non_comments "${files[@]}"
)
# gather profile-masked global USE flags
accumulate MASKED_USE $(
mapfile -t files <<< "$(collect_profile_files use{,.stable}.mask)"
grep_non_comments "${files[@]}"
)
# gather and check configured global USE flags
accumulate GLOBAL_USE $(
for file in "${PORTAGE_CONFIGROOT}/etc/portage/make."{globals,conf}{,/*} ; do
[[ -f ${file} ]] && (
echo "${file}:" >&2
unset USE ; . "${file}" ; echo "${USE}"
unset _USE ; accumulate _USE ${USE}
for flag in ${!_USE[@]} ; do
if (( _USE[${flag}] )) ; then
(( !PROFILE_USE[${flag}] )) || echo " redundant USE=\"${flag}\" (profile default)"
(( !FORCED_USE[${flag}] )) || echo " redundant USE=\"${flag}\" (forced by profile)"
(( !MASKED_USE[${flag}] )) || echo " defunct USE=\"${flag}\" (masked by profile)"
else
(( !FORCED_USE[${flag}] )) || echo " defunct USE=\"-${flag}\" (forced by profile)"
(( !MASKED_USE[${flag}] )) || echo " redundant USE=\"-${flag}\" (masked by profile)"
fi
done >&2
)
done
) 2>&1
# gather profile-default per-package USE flags
declare -A PKGS_PROFILE_USE
while read -r atom use ; do
split_atom "${atom}"
PKGS_PROFILE_USE[${CATEGORY}/${PN}]+="${atom} ${use}"$'\n'
done <<< "$(
mapfile -t files <<< "$(collect_profile_files package.use)"
grep_non_comments "${files[@]}"
)"
# gather profile-forced per-package USE flags
declare -A PKGS_FORCED_USE
while read -r atom use ; do
split_atom "${atom}"
PKGS_FORCED_USE[${CATEGORY}/${PN}]+="${atom} ${use}"$'\n'
done <<< "$(
mapfile -t files <<< "$(collect_profile_files package.use{,.stable}.force)"
grep_non_comments "${files[@]}"
)"
# gather profile-masked per-package USE flags
declare -A PKGS_MASKED_USE
while read -r atom use ; do
split_atom "${atom}"
PKGS_MASKED_USE[${CATEGORY}/${PN}]+="${atom} ${use}"$'\n'
done <<< "$(
mapfile -t files <<< "$(collect_profile_files package.use{,.stable}.mask)"
grep_non_comments "${files[@]}"
)"
# check configured per-package USE flags
for file in "${PORTAGE_CONFIGROOT%/}/etc/portage/package.use"{,/*} ; do
if [[ -f ${file} ]] ; then
echo "${file}:"
unset PKGS_ALREADY_USE ; declare -A PKGS_ALREADY_USE
grep_non_comments "${file}" | while read -r atom use ; do
split_atom "${atom}"
unset PKG_PROFILE_USE PKG_FORCED_USE PKG_MASKED_USE PKG_ALREADY_USE PKG_USE
declare -A PKG_PROFILE_USE PKG_FORCED_USE PKG_MASKED_USE PKG_ALREADY_USE PKG_USE
while read -r atom1 use1 ; do
[[ ${atom1} ]] && atom_matches "${atom1}" "${atom}" && accumulate PKG_PROFILE_USE ${use1}
done <<< "${PKGS_PROFILE_USE[${CATEGORY}/${PN}]}"
while read -r atom1 use1 ; do
[[ ${atom1} ]] && atom_matches "${atom1}" "${atom}" && accumulate PKG_FORCED_USE ${use1}
done <<< "${PKGS_FORCED_USE[${CATEGORY}/${PN}]}"
while read -r atom1 use1 ; do
[[ ${atom1} ]] && atom_matches "${atom1}" "${atom}" && accumulate PKG_MASKED_USE ${use1}
done <<< "${PKGS_MASKED_USE[${CATEGORY}/${PN}]}"
while read -r atom1 use1 ; do
[[ ${atom1} ]] && atom_matches "${atom1}" "${atom}" && accumulate PKG_ALREADY_USE ${use1}
done <<< "${PKGS_ALREADY_USE[${CATEGORY}/${PN}]}"
accumulate PKG_USE ${use}
for flag in "${!PKG_USE[@]}" ; do
if (( PKG_USE[${flag}] )) ; then
if [[ ${PKG_ALREADY_USE[${flag}]} ]] ; then
(( !PKG_ALREADY_USE[${flag}] )) || echo " [${atom}] redundant USE=\"${flag}\" (already enabled by this file)"
else
(( !GLOBAL_USE[${flag}] )) || echo " [${atom}] redundant USE=\"${flag}\" (enabled globally)"
(( !PROFILE_USE[${flag}] )) || [[ ${GLOBAL_USE[${flag}]} == 0 ]] || echo " [${atom}] redundant USE=\"${flag}\" (profile default)"
(( !FORCED_USE[${flag}] && !PKG_FORCED_USE[${flag}] )) || echo " [${atom}] redundant USE=\"${flag}\" (forced by profile)"
(( !MASKED_USE[${flag}] && !PKG_MASKED_USE[${flag}] )) || echo " [${atom}] defunct USE=\"${flag}\" (masked by profile)"
fi
else
if [[ ${PKG_ALREADY_USE[${flag}]} ]] ; then
(( PKG_ALREADY_USE[${flag}] )) || echo " [${atom}] redundant USE=\"-${flag}\" (already disabled by this file)"
else
[[ ${GLOBAL_USE[${flag}]} != 0 ]] || echo " [${atom}] redundant USE=\"-${flag}\" (disabled globally)"
(( !FORCED_USE[${flag}] && !PKG_FORCED_USE[${flag}] )) || echo " [${atom}] defunct USE=\"-${flag}\" (forced by profile)"
(( !MASKED_USE[${flag}] && !PKG_MASKED_USE[${flag}] )) || echo " [${atom}] redundant USE=\"-${flag}\" (masked by profile)"
fi
fi
done
PKGS_ALREADY_USE[${CATEGORY}/${PN}]+="${atom} ${use}"$'\n'
done
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment