Skip to content

Instantly share code, notes, and snippets.

@thesamesam
Last active November 30, 2023 22:24
Show Gist options
  • Save thesamesam/4ddaa95f3f42c2be312b676476cbf505 to your computer and use it in GitHub Desktop.
Save thesamesam/4ddaa95f3f42c2be312b676476cbf505 to your computer and use it in GitHub Desktop.
Portage /etc/portage/bashrc hook for testing src_configure with stricter Clang
# Distributed under the terms of the GNU General Public License v2
#
# Hook to make it easier to detect compiler output changes in src_configure with stricter Clang
# Outputs logs to /var/tmp/clang.
# Written by Sam James <[email protected]> and Arsen Arsenović <[email protected]>
#
# For background, please see https://wiki.gentoo.org/wiki/Modern_C_porting.
#
# Instructions:
# 1. Install clang:15 or clang:16 or clang:17
# 2. Install ansifilter
# 3. Two options:
# - Set CC=clang and CXX=clang++ in make.conf
# - If you don't want to build your system using Clang, set PORTAGE_USE_CLANG_HOOK_GCC=1 too.
# This will run configure tests using Clang in 'pretend' mode in parallel
# but the actual results used on your system should be your normal compiler.
# 4. File any bugs hit using this hook as a blocker for tracker bug #870412
#
# Variables:
# - PORTAGE_USE_CLANG_HOOK=0 to disable (PORTAGE_USE_CLANG_HOOK=1 is default on to enable)
# - PORTAGE_USE_CLANG_HOOK_GCC=1 to use gcc 'for real' but Clang for the testing
#
# Notes:
# - To check /var/tmp/clang for `clang.diff` files likely to indicate problems, run:
# find /var/tmp/clang | grep -i broken | sed -e 's:clang16-broken:clang.diff:g' | xargs grep -rsin "error:.*C99" -l | sort -t/ -k 4,5
: ${PORTAGE_USE_CLANG_HOOK:=1}
hook_timestamp='2023-11-30T22:24'
pre_src_prepare() {
# If CC is set to something other than Clang, we shouldn't try
# to override it (could even be done in the ebuild).
if [[ -n ${CC} && ${CC} != *clang* ]] ; then
return
fi
case ${EAPI} in
[0123456])
hv_args="--host-root"
;;
*)
hv_args="-b"
;;
esac
local ver
for ver in 15 16 17 ; do
has_version ${hv_args} sys-devel/clang:${ver} && __PORTAGE_HOOK_CLANG=clang-${ver}
done
# Change to the full path to avoid recursion
__PORTAGE_HOOK_CLANG=$(type -P "${__PORTAGE_HOOK_CLANG}")
if ! type -P ansifilter &>/dev/null ; then
return
fi
if [[ -z ${__PORTAGE_HOOK_CLANG} ]] || ! type -P ${__PORTAGE_HOOK_CLANG} &>/dev/null ; then
# Avoid issues in early setup when clang:15 or clang:16 haven't yet been emerged.
export CC=gcc
export CXX=g++
return
fi
}
pre_src_configure() {
if [[ ${PORTAGE_USE_CLANG_HOOK} == 1 ]] ; then
if [[ -n ${CC} && ${CC} != *clang* && -z ${PORTAGE_USE_CLANG_HOOK_GCC} ]] ; then
return
fi
if ! type -P ansifilter &>/dev/null ; then
return
fi
ewarn "Modern C testing: this build is using a /etc/portage/bashrc hook ($hook_timestamp)!"
ewarn "Modern C testing: see https://wiki.gentoo.org/wiki/Modern_C_porting for more info."
# Avoid noise from intentional ones...
export ac_cv_c_undeclared_builtin_options="none needed"
export gl_cv_compiler_check_decl_option="-Werror=implicit-function-declaration"
if [[ ${CHOST} == *musl* ]] ; then
if has_version dev-libs/libbsd ; then
export ac_cv_func___fpurge=yes
export ac_cv_func_fpurge=yes
export ac_cv_have_decl_fpurge=no
export gl_cv_func_fpurge_works=no
fi
# These should be fine on glibc IIRC but let's be safe until confirmed.
export ac_cv_header_sys_types_h_makedev=no
export gl_cv_minmax_in_limits_h=no
else
export ac_cv_have_decl_strerror_r="yes" # -Wint-conversion
fi
# Weird hangs (see timeout comment below w/ tee)
has waf-utils ${INHERITED} && return
# bug #885497
has qmake-utils ${INHERITED} && return
# bug #885497
has qt5-build ${INHERITED} && return
# Issues with CPP, easier to just avoid (bug #882363)
if grep -q "x11-misc/imake" <<< "${BDEPEND}" ; then
return
fi
# Change to the full path to avoid recursion
__PORTAGE_HOOK_CLANG=$(type -P "${__PORTAGE_HOOK_CLANG}")
mkdir "${T}"/clang-wrappers
cat <<- EOF > "${T}"/clang-wrappers/clang
#!/usr/bin/env bash
hook_clang=${__PORTAGE_HOOK_CLANG}
real_compiler=${__PORTAGE_HOOK_CLANG}
if [[ -n \${PORTAGE_USE_CLANG_HOOK_GCC} ]] ; then
if [[ -n \${CC} && \${CC##*/} == *clang* ]] ; then
:;
else
real_compiler="${BROOT:-/}"/usr/bin/${CHOST}-gcc
fi
fi
if [[ \$0 == *++ ]]; then
real_compiler=\${real_compiler/clang/clang++}
real_compiler=\${real_compiler/gcc/g++}
hook_clang=\${hook_clang/clang/clang++}
hook_clang=\${hook_clang/gcc/g++}
fi
if [[ \${PORTAGE_CLANG_HOOK_USE_REAL_COMPILER} -gt 1 ]]; then
# clang's self recursing... try your best!
case "\${real_compiler}" in
*++*) real_compiler="/usr/bin/\${CHOST+\${CHOST}-}g++" ;;
*) real_compiler="/usr/bin/\${CHOST+\${CHOST}-}gcc" ;;
esac
real_compiler="\${real_compiler/clang/gcc}"
fi
# Safety net against recursive calls
PORTAGE_orig_USE_REAL_COMP="\${PORTAGE_CLANG_HOOK_USE_REAL_COMPILER}"
export PORTAGE_CLANG_HOOK_USE_REAL_COMPILER=\$((PORTAGE_CLANG_HOOK_USE_REAL_COMPILER+1))
if [[ \${PORTAGE_orig_USE_REAL_COMP} ]]; then
exec \${real_compiler} "\$@"
fi
export CFLAGS="${CFLAGS/-fdiagnostics-color=always}"
export CFLAGS="${CFLAGS/-fdiagnostics-urls=never}"
# - Need > /dev/null for sys-libs/musl
# - No LTO because of the noise it generates in diffs (temps)
cl1() {
\${hook_clang} -fno-lto -Wno-unused-command-line-argument \
-Wno-error=implicit-function-declaration \
-Wno-error=implicit-int \
-Wno-error=int-conversion \
-Wno-error=incompatible-function-pointer-types \
-Wno-error=incompatible-pointer-types \
-Wno-error=return-type \
${CFLAGS} \
\$@ 2>&1 \
-fdiagnostics-color=never | ansifilter | sed \
-e "s:14.0.6:14.ignoreme:g" \
-e "s:15.0.0:14.ignoreme:g" \
-e "s:clang-\(14\|15\|16\|17\):clang-ignoreme:g" \
-e 's:clang version \([0-9]\+\)\.[0-9]\.[0-9]:clang version ignoreme:' \
-e "s:/usr/lib/llvm/\(14\|15\|16\|17\)/:/usr/lib/llvm/ignoreme/:g" \
-e "s:conftest-.*\.o:conftest.o:g" \
-e "s_conftest\.c\:[0-9]\+\:[0-9]\+_ignoreme_g" \
-e "s:garbage2:ignoreme:" \
-e "s:garbage:ignoreme:" \
-e "/ac_nonexistent.h/d" \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: no input files/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: unsupported option/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: unknown argument/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: no such file or directory/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: linker command failed/d' \
-e '/[0-9]\+ \(warning\|error\) generated/d' | tee -a "${T}"/clang16-safe.log > /dev/null
if ! [[ \${PIPESTATUS[0]} -eq 0 ]] ; then
touch "${T}"/clang16-safe-failed
fi
}
# TODO: No -Werror=strict-prototypes here for now as AC_PROG_LEX, AC_CHECK_FUNCS rely on it
# also, I think Clang 15.0.1 (which reverts the other bits) keeps that in anyway.
cl2() {
\${hook_clang} -fno-lto -Wno-unused-command-line-argument \
-Werror=implicit-function-declaration \
-Werror=implicit-int \
-Werror=int-conversion \
-Werror=incompatible-function-pointer-types \
-Werror=incompatible-pointer-types \
-Werror=return-type \
${CFLAGS} \
\$@ 2>&1 \
-fdiagnostics-color=never | ansifilter | sed \
-e "s:14.0.6:14.ignoreme:g" \
-e "s:15.0.0:14.ignoreme:g" \
-e "s:clang-\(14\|15\|16\|17\):clang-ignoreme:g" \
-e 's:clang version \([0-9]\+\)\.[0-9]\.[0-9]:clang version ignoreme:' \
-e "s:/usr/lib/llvm/\(14\|15\|16\|17\)/:/usr/lib/llvm/ignoreme/:g" \
-e "s:15:14:g" \
-e "s:conftest-.*\.o:conftest.o:g" \
-e "s_conftest\.c\:[0-9]\+\:[0-9]\+_ignoreme_g" \
-e "s:garbage2:ignoreme:" \
-e "s:garbage:ignoreme:" \
-e "/ac_nonexistent.h/d" \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: no input files/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: unsupported option/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: unknown argument/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: no such file or directory/d' \
-e '/clang-\(14\|15\|16\|17\|ignoreme\): error: linker command failed/d' \
-e '/[0-9]\+ \(warning\|error\) generated/d' | tee -a "${T}"/clang16-errors.log > /dev/null
if ! [[ \${PIPESTATUS[0]} -eq 0 ]] ; then
touch "${T}"/clang16-errors-failed
fi
}
# Safety net against recursive calls
export PORTAGE_CLANG_HOOK_USE_REAL_COMPILER=1
# Because clang can invoke GCC
export PORTAGE_USE_CLANG_HOOK_GCC=1
# - Just run it again as it's easier for anything which wants to parse stdout/stderr,
# and we're not concerned about performance for this experiment anyway.
# - We have to do the tee & pipe dance to not greedily consume stdin: bug 870985.
# - Timeout as a safety net because we don't ever want to make a build hang. Occurs
# with waf sometimes (being investigated), but we should keep the timeout after that anyway.
timeout --preserve-status -s PIPE 10 tee -p >(cl1 "\$@" -o "${T}"/garbage.\$\$) >(cl2 "\$@" -o "${T}"/garbage2.\$\$) | \${real_compiler} "\$@"
ret="\${PIPESTATUS[1]}"
# If at least one of them succeeded, then complain if *only* one of them did.
if [[ -f "${T}"/clang16-safe-failed || -f "${T}"/clang16-errors-failed ]] ; then
if ! [[ -f "${T}"/clang16-safe-failed && -f "${T}"/clang16-errors-failed ]] ; then
touch "${T}"/clang16-broken
fi
fi
rm -f "${T}"/clang16-safe-failed "${T}"/clang16-errors-failed
exit \${ret}
EOF
chmod +x "${T}"/clang-wrappers/clang
if in_iuse clang ; then
# If an ebuild is trying to force compiler choice, it gets tricky
# when we interfere with it. It's easier to just leave things be.
# Firefox for example will get confused in its homebrew configure script.
:;
else
# TODO: cc, ${CHOST}-cc?
for alias in ${CHOST}-gcc gcc ${CHOST}-clang \
${CHOST}-g++ g++ ${CHOST}-clang++; do
ln -s "${T}"/clang-wrappers/clang "${T}"/clang-wrappers/${alias} || exit 1
chmod +x "${T}"/clang-wrappers/${alias}
done
# -Werror=strict-prototypes
# TODO: add -Werror=int-conversion?
# TODO: add -Werror=incompatible-function-pointer-types here? (Clang only)
export CFLAGS="${CFLAGS} -Werror=implicit-function-declaration -Werror=implicit-int"
export PATH="${T}/clang-wrappers:${PATH}"
fi
fi
}
pre_src_compile() {
if [[ -f "${T}"/clang16-errors.log ]] ; then
rm -rf "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}
mkdir -p "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}
cp -rv "${T}"/clang16-{errors,safe}.log "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}
if diff -ruN "${T}"/clang16-{safe,errors}.log > "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang.diff ; then
# No point in keeping an empty diff around if no differences.
rm "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang.diff
fi
[[ -f "${T}"/clang16-broken ]] && touch "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang16-broken
fi
# We want to just delete the wrapper rather than mess with PATH again, as the ebuild
# may have modified PATH itself.
export PORTAGE_CLANG_HOOK_USE_REAL_COMPILER=1
# TODO: commented out because CMake hardcodes the path to the discovered binary
#rm -f "${T}"/clang-wrappers/{clang,gcc,cc,${CHOST}-gcc,${CHOST}-clang}
}
post_src_install() {
if [[ ${PORTAGE_USE_CLANG_HOOK} == 1 && -f "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang16-broken ]] ; then
if grep -q "error:.*C99" "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang.diff ; then
eqawarn "Clang 16 testing: Found possible issues in configure!"
eqawarn "Clang 16 testing: Please check "${EROOT}"/var/tmp/clang/${CATEGORY}/${PF}/clang.diff"
fi
fi
}
# Local variables:
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "hook_timestamp='"
# time-stamp-format: "%:y-%02m-%02dT%02H:%02M"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "'"
# time-stamp-line-limit: 32
# mode: sh
# End:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment