Last active
November 30, 2023 22:24
-
-
Save thesamesam/4ddaa95f3f42c2be312b676476cbf505 to your computer and use it in GitHub Desktop.
Portage /etc/portage/bashrc hook for testing src_configure with stricter Clang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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