[PATCH v05 02/72] scripts/headers_compile_test.sh: compile test script for exported headers

From: Mikko Rapeli
Date: Mon Aug 22 2016 - 14:46:45 EST


Users of kernel header files would be happier if they did not contain
kernel specific parts and would contain #include statements for all
other header files that they depend on, and in general would compile.

For each header file exported to userspace, this script tries to compile it
together with minimal header files from GCC and libc, and reports results.

Kernel headers depend on GCC headers so their path is included in the
test compiler command line.

Default libc and GCC header file locations are parsed from compiler
configuration.

Some gcc and kernel headers depend on libc headers which are made available
by copying from the compiler default location to a temporary location and
removing possibly existing kernel headers from this directory. This is a bit
of a hack but seems to work in multiple environments.

Tested natively on:

Debian unstable, i586-linux-gnu and gcc 4.9.2
Raspbian Wheezy, arm-linux-gnueabihf and gcc 4.6.3
Ubuntu 12.04 LTS, x86_64-linux-gnu and gcc 4.6.3

Tested cross compilation using standard CROSS_COMPILE=/path/to/gcc with:

arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-4.9-2014.09 - Linaro GCC 4.9-2014.09) 4.9.2 20140904 (prerelease)

Execute the script in the directory where kernel header files are installed.
For example:

$ make headers_install
$ cd usr/include
$ ../../scripts/headers_compile_test.sh

Example statistics from v4.4-rc3-10-gc846b17 kernel on 32bit x86:

122 files failed the compile test.
645 files passed the compile test.

Example error types from v4.4-rc3-10-gc846b17 kernel:

$ ../../scripts/headers_compile_test.sh 2>&1 | \
grep error: | sed -e 's/.*error://g' | sort | uniq -c | sort -rn

381 unknown type name âuint32_tâ
125 unknown type name âuint64_tâ
99 unknown type name âsize_tâ
43 unknown type name â__kernel_ulong_tâ
29 unknown type name âuint8_tâ
23 unknown type name âint32_tâ
16 unknown type name â__kernel_time_tâ
14 unknown type name â__be16â
14 âIFNAMSIZâ undeclared here (not in a function)
14 field âaddrâ has incomplete type
9 field âifru_netmaskâ has incomplete type
9 field âifru_hwaddrâ has incomplete type
9 field âifru_dstaddrâ has incomplete type
9 field âifru_broadaddrâ has incomplete type
9 field âifru_addrâ has incomplete type
8 unknown type name âuint16_tâ
8 unknown type name âpid_tâ
8 unknown type name â__kernel_pid_tâ
7 unknown type name âu_shortâ
5 unknown type name â__kernel_long_tâ
4 unknown type name â__kernel_uid32_tâ
4 unknown type name â__kernel_gid32_tâ
4 âETH_ALENâ undeclared here (not in a function)
3 unknown type name âcaddr_tâ
3 âIPSET_ERR_TYPE_SPECIFICâ undeclared here (not in a function)
3 field âsrc_addrâ has incomplete type
3 field âladdrâ has incomplete type
3 field âbssidâ has incomplete type
3 expected specifier-qualifier-list before âuint64_tâ
2 unknown type name âu_longâ
2 unknown type name âstack_tâ
2 unknown type name âsigset_tâ
2 unknown type name âsa_family_tâ
2 unknown type name â__kernel_mode_tâ
2 unknown type name â__kernel_key_tâ
2 unknown type name âelf_gregset_tâ
2 unknown type name âboolâ
2 âuint64_tâ undeclared here (not in a function)
2 âtrueâ undeclared (first use in this function)
2 âNAME_MAXâ undeclared here (not in a function)
2 â__kernel_mode_tâ undeclared here (not in a function)
2 invalid application of âsizeofâ to incomplete type âstruct sockaddrâ
2 field âuc_mcontextâ has incomplete type
2 field âsrcâ has incomplete type
2 field âshm_permâ has incomplete type
2 field âsem_permâ has incomplete type
2 field âraddrâ has incomplete type
2 field âmsg_permâ has incomplete type
2 field âgrpâ has incomplete type
2 field âdst_addrâ has incomplete type
2 field âarp_paâ has incomplete type
2 field âarp_netmaskâ has incomplete type
2 field âarp_haâ has incomplete type
2 âfalseâ undeclared (first use in this function)
1 xen/interface/xen.h: No such file or directory
1 via_drmclient.h: No such file or directory
1 unknown type name âwait_queue_head_tâ
1 unknown type name âuid_tâ
1 unknown type name âint64_tâ
1 unknown type name âint16_tâ
1 unknown type name âino_tâ
1 unknown type name âelf_greg_tâ
1 unknown type name âelf_fpxregset_tâ
1 unknown type name âelf_fpregset_tâ
1 unknown type name â__be32â
1 âSIOCDEVPRIVATEâ undeclared here (not in a function)
1 âsa_family_tâ undeclared here (not in a function)
1 âNULLâ undeclared (first use in this function)
1 âMSG_FINâ undeclared here (not in a function)
1 âMAX_IPOPTLENâ undeclared here (not in a function)
1 âMAX_ADDR_LENâ undeclared here (not in a function)
1 âIFHWADDRLENâ undeclared here (not in a function)
1 field âvmaskâ has incomplete type
1 field âvifc_rmt_addrâ has incomplete type
1 field âvifc_lcl_addrâ has incomplete type
1 field âvaddrâ has incomplete type
1 field âuc_chainâ has incomplete type
1 field âtcpâ has incomplete type
1 field âsspp_addrâ has incomplete type
1 field âssp_addrâ has incomplete type
1 field âsrc_maskâ has incomplete type
1 field âspt_addressâ has incomplete type
1 field âspp_addressâ has incomplete type
1 field âspinfo_addressâ has incomplete type
1 field âspc_aaddrâ has incomplete type
1 field âsmskâ has incomplete type
1 field âsin_addrâ has incomplete type
1 field âsas_obs_rto_ipaddrâ has incomplete type
1 field âsaddrâ has incomplete type
1 field ârtmsg_srcâ has incomplete type
1 field ârtmsg_gatewayâ has incomplete type
1 field ârtmsg_dstâ has incomplete type
1 field ârt_genmaskâ has incomplete type
1 field ârt_gatewayâ has incomplete type
1 field ârt_dstâ has incomplete type
1 field âprefixâ has incomplete type
1 field âmfcc_originâ has incomplete type
1 field âmfcc_mcastgrpâ has incomplete type
1 field âmf6cc_originâ has incomplete type
1 field âmf6cc_mcastgrpâ has incomplete type
1 field âmaskâ has incomplete type
1 field âiphâ has incomplete type
1 field âipâ has incomplete type
1 field âim_srcâ has incomplete type
1 field âim_dstâ has incomplete type
1 field âim6_srcâ has incomplete type
1 field âim6_dstâ has incomplete type
1 field âgwâ has incomplete type
1 field âdst_maskâ has incomplete type
1 field âdmskâ has incomplete type
1 field âdest_addrâ has incomplete type
1 field âdaddrâ has incomplete type
1 field âap_addrâ has incomplete type
1 field âa6â has incomplete type
1 field âa4â has incomplete type
1 #error "patchkey.h included directly"
1 âDLM_RESNAME_MAXLENâ undeclared here (not in a function)
1 array type has incomplete element type âstruct timespecâ
1 array type has incomplete element type âstruct in6_addrâ

Once all these errors have been fixed, this test should be added to
'make headers_check'.

Since I've been stubborn enough to work with this script and fixes to the
test failures for over a year, I added my self to MAINTAINERS.
I will continue to maintain this script as a hobby.

Signed-off-by: Mikko Rapeli <mikko.rapeli@xxxxxx>
Cc: Alexander Stein <alexander.stein@xxxxxxxxxxxxxxxxxxxxx>
Cc: Gabriel Laskar <gabriel@xxxxxxxxxxxx>
---
MAINTAINERS | 5 +
scripts/headers_compile_test.sh | 198 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 203 insertions(+)
create mode 100755 scripts/headers_compile_test.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index a306795..a6fb57d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3245,6 +3245,11 @@ S: Maintained
F: mm/memcontrol.c
F: mm/swap_cgroup.c

+COMPILE TEST FOR HEADER FILES EXPORTED TO USERSPACE
+M: Mikko Rapeli <mikko.rapeli@xxxxxx>
+S: Maintained
+F: scripts/headers_compile_test.sh
+
CORETEMP HARDWARE MONITORING DRIVER
M: Fenghua Yu <fenghua.yu@xxxxxxxxx>
L: linux-hwmon@xxxxxxxxxxxxxxx
diff --git a/scripts/headers_compile_test.sh b/scripts/headers_compile_test.sh
new file mode 100755
index 0000000..e13f533
--- /dev/null
+++ b/scripts/headers_compile_test.sh
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+help() {
+ cat << EOF_HELP
+Userspace compile test for exported kernel headers.
+
+ Copyright (C) 2015 Mikko Rapeli <mikko.rapeli@xxxxxx>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; version 2
+ of the License.
+
+Execute in root directory of exported kernel headers in Linux kernel source
+tree. Sets up gcc and libc headers without existing kernel headers to
+a temporary environment and tries to compile all exported header files
+from current directory against them. Return value is zero if all tests pass,
+non-zero if something goes wrong during execution, or the amount of files
+which failed the compile test.
+
+Supported arguments:
+
+ -h|--help print help
+ -k|--keep don't cleanup temporary header files and directories
+ -v|--verbose print more verbose output
+
+Example in Linux kernel source tree:
+
+ \$ make headers_install
+ \$ cd usr/include
+ \$ $( readlink -f "$0" )
+
+EOF_HELP
+}
+
+# bash due to arithmetics and pipefail
+set -euo pipefail
+
+KEEP=0
+HELP=0
+
+# command line arguments
+for p in "$@"; do
+ case "$p" in
+ -k|--keep)
+ KEEP=1
+ ;;
+ -h|--help)
+ HELP=1
+ ;;
+ -v|--verbose)
+ set -x
+ ;;
+ *)
+ help
+ echo "Unknown argument: $p"
+ exit 1
+ ;;
+ esac
+done
+
+if [ "$HELP" != "0" ]; then help; exit 0; fi
+
+# sanity test
+if [ ! -d ./linux ]; then
+ echo Sanity check error: ./linux directory not found
+ echo Should be called in usr/include after \'make headers_install\'.
+ echo Returns number of failed files, 0 if none.
+ exit 1
+fi
+
+# Support CC variable for compiler and ccache, and cross compiling.
+# CC is used without quotes to support CC="ccache gcc".
+set +u
+if [ "$CC"foobar == "foobar" ]; then
+ CC=cc
+fi
+
+if [ "$CROSS_COMPILE"foobar != "foobar" ]; then
+ # Using gcc name since some cross compiler tool chains don't provide
+ # the cc symlink
+ CC="$CROSS_COMPILE"gcc
+fi
+set -u
+
+# Kernel headers refer to some gcc and libc headers so make them available.
+set +u
+if [ "$ARCH_TRIPLET"foobar == "foobar" ]; then
+ # Taking triplet from gcc/cpp
+ ARCH_TRIPLET="$( $CC -v -x c -E - < /dev/null 2>&1 | \
+ grep Target | sed -e 's/Target: //' )"
+fi
+
+if [ "$LIBC"foobar == "foobar" ]; then
+ # trying to grep libc includes from gcc/cpp defaults
+ _TEMP="$( $CC -v -x c -E - < /dev/null 2>&1 | \
+ sed -n -e '/^#include <...> search starts here:$/,/^End of search list.$/{//!p}' | \
+ sed -e 's/^\ \//\//g' | \
+ grep '/usr/include' )"
+
+ # sanity check and prepare LIBC dirs
+ for d in $_TEMP; do
+ if [ ! -d "$d" ]; then
+ echo "$d not a directory"
+ exit 1
+ fi
+ LIBC="$LIBC $d"
+ done
+fi
+set -u
+
+# Copy libc include files to temp directory for the tests.
+COMPILE_TEST_INC="$( readlink -f \
+ "$( mktemp -d ../headers_compile_test_include.XXXXXX )" )"
+
+# cleanup if keep not set
+if [ "$KEEP" = "0" ]; then
+ trap 'rm -rf "$COMPILE_TEST_INC"' EXIT
+else
+ trap 'printf \
+"Temporary directory not cleaned up! Remove manually:\n${COMPILE_TEST_INC}\n"' \
+ EXIT
+fi
+
+for d in $LIBC; do
+ # check if last part of dir is the arch triplet, cross compile paths
+ # can have it also elsewhere so just the last one counts.
+ if ! ( echo "$d" | egrep "$ARCH_TRIPLET$" > /dev/null ); then
+ # hopefully just main libc dir, e.g. /usr/include,
+ # follow symlinks from e.g. /usr/include/bits
+ cp -aL "$d"/* "$COMPILE_TEST_INC"/
+ elif ( echo "$d" | egrep "$ARCH_TRIPLET$" > /dev/null ); then
+ # hopefully the arch specific dir, e.g. /usr/include/x86_64-linux-gnu
+ cp -ar "$d"/* "$COMPILE_TEST_INC/"
+ else
+ echo "$d unexpected, bailing out"
+ exit 1
+ fi
+done
+
+# Simulate libc headers without kernel headers by removing
+# all known kernel header dirs from the copied libc ones.
+# This seems to magically work.
+_KERNEL_DIRS="$( find . -type d | grep -v '^\.$' )"
+( cd "$COMPILE_TEST_INC" && rm -rf $_KERNEL_DIRS )
+
+# GCC headers
+set +u
+if [ "$GCC_INC"foobar == "foobar" ]; then
+ # Take from $CC default system include paths, filter out
+ # /usr/local/include and /usr/include stuff first, then try to match
+ # for gcc.
+ _TEMP="$( $CC -v -x c -E - < /dev/null 2>&1 | \
+ sed -n -e '/^#include <...> search starts here:$/,/^End of search list.$/{//!p}' | \
+ sed -e 's/^\ \//\//g' | \
+ egrep -v '/usr/local/include' | \
+ egrep -v '/usr/include' | \
+ grep gcc | \
+ xargs )"
+
+ # merge and prepare for use with $CC
+ for d in $_TEMP; do
+ # sanity test
+ if [ ! -d "$d" ]; then
+ echo "$d: is not a directory"
+ exit 1
+ fi
+ GCC_INC="$GCC_INC -I $d"
+ done
+fi
+set -u
+
+# For each header file, try to compile it using the headers we prepared.
+_FAILED=0
+_PASSED=0
+for f in $( find . -name "*\.h" | xargs ); do
+ _FAIL=0
+
+ # compile test, CC not quoted to support ccache
+ echo $CC -Wall -c -nostdinc $GCC_INC -I . -I "$COMPILE_TEST_INC" -I "$COMPILE_TEST_INC/$ARCH_TRIPLET" -o /dev/null "$f"
+ $CC -Wall -c -nostdinc $GCC_INC -I . -I "$COMPILE_TEST_INC" -I "$COMPILE_TEST_INC/$ARCH_TRIPLET" -o /dev/null "$f" \
+ || _FAIL=1
+
+ # report errors
+ if [ "$_FAIL" -gt 0 ]; then
+ echo "FAILED: $f"
+ _FAILED="$(( _FAILED + 1 ))"
+ else
+ echo "PASSED: $f"
+ _PASSED="$(( _PASSED + 1))"
+ fi
+done
+
+echo Statistics:
+echo "$_FAILED files failed the compile test."
+echo "$_PASSED files passed the compile test."
+
+exit "$_FAILED"
--
2.8.1