| #!/bin/sh -efu |
| |
| # Copyright 2011-2012 Intel Corporation |
| # Author: Artem Bityutskiy |
| # License: GPLv2 |
| |
| srcdir="$(readlink -ev -- ${0%/*})" |
| PATH="$srcdir:$srcdir/helpers/libshell:$srcdir/helpers:$PATH" |
| |
| . shell-error |
| . shell-args |
| . shell-signal |
| . aiaiai-sh-functions |
| |
| PROG="${0##*/}" |
| message_time="yes" |
| |
| # This is a small trick to make sure the script is portable - check if 'dash' |
| # is present, and if yes - use it. |
| if can_switch_to_dash; then |
| exec dash -euf -- "$srcdir/$PROG" "$@" |
| exit $? |
| fi |
| |
| show_usage() |
| { |
| cat <<-EOF |
| Usage: $PROG [options] <kernel-tree> <defconfig,[arch[,cross]] ...> |
| |
| <kernel-tree> - directory with kernel sources |
| <defconfig,[arch[,cross]] ...> - list of configurations to test. |
| |
| The mbox file containing the patches to test is expected to come from stdin or |
| -i option can be used instead. |
| |
| The configurations are specified as follows: |
| |
| defconfig[,arch[,cross]] |
| |
| where |
| |
| * "defconfig" is the name kernel defconfig file to test (or "randconfig") |
| * "arch" is the architecture (translates to Kbuild's ARCH variable value) |
| * "cross" is the cross-compiler prefix (translates to Kbuild's CROSS_COMPILE |
| variable value). |
| |
| For example, the following list of configurations: |
| |
| omap2_defconfig,arm,arm-eabi- i386_defconfig,i386 defconfig,s390,s390x-linux- randconfig,arm |
| |
| will make $PROG to test the patch in 3 configurations: |
| |
| 1. defconfig "omap2_defconfig" with ARCH=arm and CROSS_COMPILE=arm-eabi- |
| 2. defconfig "i386_defconfig" with ARCH=arm and the default CROSS_COMPILE |
| 3. defconfig "defconfig" with ARCH=s390 and CROSS_COMPILE=s390x-linux- |
| 4. a randomly generated configuration with ARCH=arm |
| |
| Note that randconfig pre-generates a single random configuration rather than |
| using randconfig for each kernel compile. In this way, we guarantee that the |
| same kernel configuration is used each build, rather than completely random |
| each time. |
| |
| By default, $PROG assumes that defconfigs are part of the |
| kernel tree, unless --confdir option is specified, in which case the defconfig |
| files will be searched for only in the specified directory. |
| |
| Several static checking utilities can be enabled to provide enhanced |
| information, if desired. These include sparse, smatch, cppcheck, and |
| coccinelle. In addition scripts/checkpatch.pl is run if found in the project |
| tree. You can disable a checker with --no[checker] or enable it with |
| --[checker]. The last provided option on the command line wins, and supersedes |
| other settings. |
| |
| Options: |
| -j, --jobs=N allow to run N jobs simultaneously (default is 1); |
| -c, --commit-id=ID the commit id to test against (default is the head of |
| the git tree (HEAD)); |
| -i, --input=MBOX use the MBOX file instead of stdin; |
| -w, --workdir=WDIR path to the work directory where the kernel will |
| be built (default: a temporary directory is created |
| using mktemp); |
| --logdir=LOGDIR build logs will be put to this directory (by default |
| the build logs are stored in the script's temporary |
| directory and removed upon exit, unless -p is |
| specified); |
| -C, --confdir=CDIR path to the directory containing the defconfig files |
| (those you specify at the end); by default the |
| defconfig files are assumed to be part of the |
| <kernel-tree>; this option makes it possible to use |
| stand-alone defconfig files instead; |
| -p, --preserve preserve all the temporary files - do not clean up; |
| --bisectability test bisectability; |
| --[no]sparse check with sparse while building; |
| --[no]smatch check with smatch while building; |
| --[no]cppcheck check with cppcheck while building; |
| --[no]coccinelle check with coccinelle (spatch) while building; |
| --[no]checkpatch run scripts/checkpatch.pl, if found in the project; |
| -Q --quick-fixes=F sometimes it is necessary to carry out-of-tree patches |
| like quick build fixes and this option allows to pass |
| an mbox file with quick fixes which will be applied |
| first and the user patch-set will be tested on top of |
| the fixes; |
| --targets list of make targets to build (defaults to all) |
| -K --keywords=FILE match keywords from FILE against the patch; |
| -M, --kmake-opts additional options to append to the final kernel |
| compilation 'make' command |
| (e.g., W=2 KALLSYMS_EXTRA_PASS=1); |
| -v, --verbose be verbose; |
| -h, --help show this text and exit. |
| EOF |
| } |
| |
| fail_usage() |
| { |
| [ -z "$1" ] || printf "%s\n" "$1" |
| show_usage |
| exit 1 |
| } |
| |
| test_checkpatch() |
| { |
| local n=0 patch_cnt |
| local tmp_out="$tmpdir/checkpatch.tmp_out" |
| local tmp_mbox="$tmpdir/checkpatch.tmp_mbox" |
| local opts="--no-tree -q --show-types --ignore PREFER_PACKED,PREFER_ALIGNED" |
| |
| patch_cnt="$(formail -s echo 1 < "$mbox" | wc -l)" |
| |
| # Iterate over all patches and check each one individually |
| while [ "$n" -lt "$patch_cnt" ]; do |
| local subj |
| |
| formail +$n -1 -s < "$mbox" > "$tmp_mbox" |
| truncate -s0 "$tmp_out" |
| "$checkpatch_pl" $opts - < "$tmp_mbox" > "$tmp_out" ||: |
| n="$(($n+1))" |
| |
| [ -s "$tmp_out" ] || continue |
| |
| fetch_header_or_die subj "Subject" < "$tmp_mbox" |
| |
| printf "\n" >> "$tmpdir/checkpatch.results" |
| print_separator >> "$tmpdir/checkpatch.results" |
| printf "\n%s\n\n" "checkpatch.pl results for patch \"$subj\"" \ |
| >> "$tmpdir/checkpatch.results" |
| cat < "$tmp_out" >> "$tmpdir/checkpatch.results" |
| done |
| |
| if [ "$patch_cnt" -gt 1 ]; then |
| # Check the squashed patch |
| truncate -s0 "$tmp_out" |
| "$checkpatch_pl" --no-signoff $opts - < "$tmpdir/diff-for-checkpatch" > "$tmp_out" ||: |
| [ -s "$tmp_out" ] || return 0 |
| |
| printf "\n" >> "$tmpdir/checkpatch.results" |
| print_separator >> "$tmpdir/checkpatch.results" |
| |
| printf "\n%s\n\n" "checkpatch.pl results for the entire squashed patch-set" \ |
| >> "$tmpdir/checkpatch.results" |
| |
| cat < "$tmp_out" >> "$tmpdir/checkpatch.results" |
| fi |
| |
| } |
| |
| test_configuration() |
| { |
| local config="$1" |
| local defconfig="$(leave_first "$1")"; |
| local arch="$(leave_second "$1")"; |
| local cross="$(leave_third "$1")"; |
| local arch_opt= |
| local pid_bisect= |
| |
| [ -z "$arch" ] || arch_opt="-a $arch${cross:+",$cross"}" |
| |
| git --git-dir="$(git_dir "$cloned_kernel1")" reset $quiet --hard "$commit_id1" >&2 |
| |
| # Enable use of a random condiguration that is stable between the |
| # before and after tests. If we just used 'randconfig' by default, then |
| # we wouldn't be testing the before and after kernels with the same |
| # configuration. We do this even if we have specified a confdir. |
| if [ "$defconfig" = "randconfig" ]; then |
| # Generate a random configuration |
| make -C "$cloned_kernel1" ${arch:+ARCH="$arch"} \ |
| ${cross:+CROSS_COMPILE="$cross"} -- "$defconfig" >&2 |
| |
| # Store this configuration in our temporary work dir |
| defconfig="$tmpdir/$config.random" |
| verbose "Copying random configuration to $defconfig" |
| cp .config "$defconfig" >&2 |
| |
| make -C "$cloned_kernel1" mrproper >&2 |
| elif [ -n "$confdir" ]; then |
| defconfig="$confdir/$defconfig" |
| [ -f "$defconfig" ] || die "$defconfig is not available" |
| fi |
| |
| verbose "Build non-patched kernel (\"$cloned_kernel1\", configuration \"$config\")" |
| |
| local log1="$logdir1/$config" |
| local obj1="$tmpdir1/obj.$config" |
| |
| # Note, we use the --keep-going option assuming that the base commit |
| # build may fail, which is OK for continuing as long as the patches |
| # under test fix it. |
| aiaiai-make-kernel $verbose $sparse $smatch $cppcheck $coccinelle -o "$obj1" \ |
| --check-only "$mod_files" -j "$jobs" $arch_opt -D "$defconfig" \ |
| -O "$log1.stdout.log" -E "$log1.stderr.log" --keep-going \ |
| ${kmake_opts:+-M "$kmake_opts"} -- "$cloned_kernel1" "$targets" |
| |
| verbose "Done with non-patched kernel" |
| |
| # Run the bisectability test, re-use our kernel sources and built |
| # objects. |
| if [ -n "$bisectability" ]; then |
| aiaiai-test-bisectability $verbose $preserve -j "$jobs" -c "$commit_id1" \ |
| --dont-clone -i "$mbox" -w "$tmpdir" -o "$obj1" \ |
| ${confdir:+-C $confdir} ${kmake_opts:+-M "$kmake_opts"} -- \ |
| "$cloned_kernel1" "$config" > "$tmpdir/$config.bisect.results" & |
| pid_bisect="$!" |
| fi |
| |
| verbose "Build patched kernel (\"$cloned_kernel2\", configuration \"$config\")" |
| |
| local log2="$logdir2/$config" |
| local obj2="$tmpdir2/obj.$config" |
| |
| aiaiai-make-kernel $verbose $sparse $smatch $cppcheck $coccinelle -o "$obj2" \ |
| --check-only "$mod_files" -j "$jobs" $arch_opt -D "$defconfig" \ |
| -O "$log2.stdout.log" -E "$log2.stderr.log" \ |
| ${kmake_opts:+-M "$kmake_opts"} -- "$cloned_kernel2" "$targets" |
| |
| verbose "Done with patched kernel" |
| |
| aiaiai-diff-log $verbose $preserve "$tmpdir/diff-for-diff-log" "$log1.stderr.log" \ |
| -w "$tmpdir" "$log2.stderr.log" > "$tmpdir/$config.stderr.diff" |
| |
| if [ -n "$bisectability" ]; then |
| # MUST sync with bisect test before removing build artifacts |
| wait "$pid_bisect" || die "aiaiai-test-bisectability failed" |
| |
| # Clean-up the git tree after the test |
| rm $verbose -rf "$cloned_kernel1/.git/rebase-apply" >&2 |
| verbose "Done with bisectability test" |
| fi |
| |
| if [ -n "$preserve" ]; then |
| message "Preserving objdirs: $obj1 $obj2" |
| else |
| [ -d "$obj1" ] && (verbose "Removing $obj1"; rm -rf -- "$obj1" >&2) |
| [ -d "$obj2" ] && (verbose "Removing $obj2"; rm -rf -- "$obj2" >&2) |
| fi |
| } |
| |
| tmpdir= |
| preserve= |
| cleanup_handler() |
| { |
| if [ -n "$preserve" ]; then |
| message "Preserved tmpdir: $tmpdir" |
| else |
| [ -z "$tmpdir" ] || verbose "Removing $tmpdir"; |
| rm -rf -- "$tmpdir" >&2 |
| fi |
| |
| # Just in case we were interrupted, kill all our children |
| local child |
| for child in $(ps -o pid --no-headers --ppid "$$"); do |
| kill "$child" > /dev/null 2>&1 ||: |
| done |
| } |
| set_cleanup_handler cleanup_handler |
| |
| TEMP=`getopt -n $PROG -o j:,c:,i:,w:,C:,p,Q:,K:,M:,v,h --long jobs:,commit-id:,input:,workdir:,logdir:,confdir:,preserve,bisectability,sparse,smatch,cppcheck,coccinelle,checkpatch,quick-fixes:,targets:,keywords:,kmake-opts:,verbose,help -- "$@"` || |
| fail_usage "" |
| eval set -- "$TEMP" |
| |
| jobs=1 |
| logdir= |
| confdir= |
| commit_id1=HEAD |
| mbox= |
| bisectability= |
| sparse= |
| smatch= |
| cppcheck= |
| coccinelle= |
| checkpatch="yes" # Keep enabled by default for backwards compatibility |
| quick_fixes= |
| targets="all" |
| keywords= |
| kmake_opts= |
| verbose= |
| quiet="-q" |
| |
| while true; do |
| case "$1" in |
| -j|--jobs) |
| jobs="$(opt_check_number "$1" "$2")" |
| shift |
| ;; |
| -c|--commit-id) |
| commit_id1="$2" |
| shift |
| ;; |
| -i|--input) |
| mbox="$(opt_check_read "$1" "$2")" |
| shift |
| ;; |
| -w|--workdir) |
| mkdir $verbose -p -- "$2" >&2 |
| tmpdir="$(mktemp --tmpdir="$(readlink -fv -- "$2")" -dt "$PROG.XXXX")" |
| shift |
| ;; |
| --logdir) |
| logdir="$(opt_check_dir "$1" "$2")" |
| shift |
| ;; |
| -C|--confdir) |
| confdir="$(opt_check_dir "$1" "$2")" |
| shift |
| ;; |
| -p|--preserve) |
| preserve="--preserve" |
| ;; |
| --bisectability) |
| bisectability="--bisectability" |
| ;; |
| --sparse) |
| sparse="--sparse" |
| program_required "sparse" "See section 'sparse' in doc/README" |
| ;; |
| --smatch) |
| smatch="--smatch" |
| program_required "smatch" "See section 'smatch' in doc/README" |
| ;; |
| --cppcheck) |
| cppcheck="--cppcheck" |
| program_required "cppcheck" "Usually Linux distribution provide a cppcheck package" |
| ;; |
| --coccinelle) |
| coccinelle="yes" |
| program_required "spatch" "Usually Linux distribution provide a 'spatch' or 'coccinelle' package" |
| ;; |
| --checkpatch) |
| checkpatch="yes" |
| ;; |
| --nosparse) |
| sparse= |
| ;; |
| --nosmatch) |
| smatch= |
| ;; |
| --nocppcheck) |
| cppcheck= |
| ;; |
| --nococcinelle) |
| coccinelle= |
| ;; |
| --nocheckpatch) |
| checkpatch= |
| ;; |
| -Q|--quick-fixes) |
| quick_fixes="$(opt_check_read "$1" "$2")" |
| shift |
| ;; |
| --targets) |
| targets="$2" |
| shift |
| ;; |
| -K|--keywords) |
| keywords="$(opt_check_read "$1" "$2")" |
| shift |
| ;; |
| -M|--kmake-opts) |
| kmake_opts="$2" |
| shift |
| ;; |
| -v|--verbose) |
| verbose=-v |
| quiet= |
| ;; |
| -h|--help) |
| show_usage |
| exit 0 |
| ;; |
| --) shift; break |
| ;; |
| *) fail_usage "Unrecognized option: $1" |
| ;; |
| esac |
| shift |
| done |
| |
| [ "$#" -ge 2 ] || fail_usage "Insufficient arguments" |
| |
| kernel_tree="$(readlink -ev -- "$1")"; shift |
| defconfigs="$@" |
| |
| # Make sure external programs we depend on are installed |
| program_required "git" "" |
| program_required "make" "" |
| program_required "patch" "" |
| program_required "formail" "" |
| program_required "python" "" |
| program_required "perl" "" |
| program_required "diff" "" |
| program_required "tar" "" |
| |
| for defconfig in $defconfigs; do |
| cross="$(leave_third "$defconfig")"; |
| program_required "${cross}gcc" "" |
| done |
| |
| [ -n "$tmpdir" ] || tmpdir="$(mktemp -dt "$PROG.XXXX")" |
| tmpdir1="$tmpdir/before" |
| tmpdir2="$tmpdir/after" |
| mkdir -p $verbose "$tmpdir1" "$tmpdir2" >&2 |
| |
| if [ -n "$logdir" ]; then |
| logdir1="$logdir/before" |
| logdir2="$logdir/after" |
| mkdir -p $verbose "$logdir1" "$logdir2" >&2 |
| else |
| logdir="$tmpdir" |
| logdir1="$tmpdir1" |
| logdir2="$tmpdir2" |
| fi |
| |
| # Save the mbox to a temporary file if it comes from stdin |
| if [ -z "$mbox" ]; then |
| mbox="$tmpdir/mbox" |
| cat > "$mbox" |
| else |
| cp $verbose "$mbox" "$tmpdir/mbox" >&2 |
| mbox="$tmpdir/mbox" |
| fi |
| |
| cloned_kernel2="$tmpdir2/src" |
| git clone --shared "${verbose:--q}" "$kernel_tree" "$cloned_kernel2" >&2 |
| cd "$cloned_kernel2" |
| |
| # Reset to the base commit id |
| commit_id1="$(git --git-dir="$(git_dir "$kernel_tree")" rev-parse "$commit_id1^{commit}")" |
| verbose "Base commit: $(git log -1 --oneline "$commit_id1")" |
| git reset $quiet --hard "$commit_id1" >&2 |
| |
| # Apply quick fixes |
| if [ -n "$quick_fixes" ]; then |
| verbose "Applying quick fixes from $quick_fixes" |
| git apply "$quick_fixes" >&2 |
| git add -A >&2 |
| git commit $quiet -m "Quick fixes - applied by aiaiai" >&2 |
| commit_id1="$(git rev-parse "HEAD^{commit}")" |
| fi |
| |
| # Apply the patch |
| apply_patch < "$mbox" || exit 0 |
| |
| commit_id2="$(git rev-parse "HEAD^{commit}")" |
| verbose "Commit after applying the patch(es): $(git log -1 --oneline "$commit_id2")" |
| |
| # Re-generate the patch. Note, we prefer to not use the input mbox directly |
| # because it may be encoded (e.g., with quoted-printable). |
| git format-patch -M --patience --stdout "$commit_id1".."$commit_id2" > "$mbox" |
| |
| # Generate a diff for aiaiai-diff-log |
| git diff -U0 -M "$commit_id1".."$commit_id2" > "$tmpdir/diff-for-diff-log" |
| |
| # Generate a diff for checkpatch.pl |
| git diff -M "$commit_id1".."$commit_id2" > "$tmpdir/diff-for-checkpatch" |
| |
| # Make a copy of 'checkpatch.pl' and coccinelle scripts. This is safer than |
| # using them directly from the git tree because we'll apply patches under test |
| # there, and the patches may also change 'checkpatch.pl' or coccinelle scripts. |
| if [ -n "$checkpatch" ]; then |
| if git cat-file -e "$commit_id1:scripts/checkpatch.pl" 2>/dev/null; then |
| mkdir -p $verbose "$tmpdir/checkpatch" >&2 |
| checkpatch_pl="$tmpdir/checkpatch/checkpatch.pl" |
| git show "$commit_id1:scripts/checkpatch.pl" > "$checkpatch_pl" |
| chmod $verbose u+x "$checkpatch_pl" >&2 |
| |
| # Also grab the typo corrections if the kernel under test has |
| # one (no need to report errors... if something is weird |
| # checkpatch will let us know). |
| if git cat-file -e "$commit_id1:scripts/spelling.txt" 2>/dev/null; then |
| git show "$commit_id1:scripts/spelling.txt" > "$tmpdir/checkpatch/spelling.txt" |
| fi |
| else |
| verbose "Can't find checkpatch.pl.. disabling checkpatch tests." |
| checkpatch= |
| fi |
| fi |
| |
| if [ -n "$coccinelle" ]; then |
| if git cat-file -e "$commit_id1:scripts/coccinelle"; then |
| mkdir -p $verbose "$tmpdir/coccinelle" >&2 |
| git archive "$commit_id1" scripts/coccinelle | \ |
| tar $verbose --strip-components=2 -C "$tmpdir/coccinelle" -x >&2 |
| coccinelle="--coccinelle=$tmpdir/coccinelle" |
| else |
| verbose "Can't find coccinelle scripts.. disabling coccinelle tests." |
| coccinelle= |
| fi |
| fi |
| |
| # Run checkpatch.pl in backgound. |
| if [ -n "$checkpatch" ]; then |
| test_checkpatch & |
| pid_checkpatch="$!" |
| fi |
| |
| # Search for keywords |
| if [ -n "$keywords" ]; then |
| aiaiai-match-keywords $verbose "$mbox" < "$keywords" > "$tmpdir/keywords.results" & |
| pid_keywords="$!" |
| fi |
| |
| # Generate the list of modified files |
| mod_files="$tmpdir/mod_files" |
| git diff --numstat $commit_id1..$commit_id2 | |
| sed -e 's/[[:digit:][:space:]]\+//' > "$mod_files" |
| |
| # Clone another copy of the kernel tree |
| cloned_kernel1="$tmpdir1/src" |
| git clone --shared "${verbose:--q}" "$cloned_kernel2" "$cloned_kernel1" >&2 |
| cd "$cloned_kernel1" |
| |
| # Main loop for testing all configurations |
| for defconfig in $defconfigs; do |
| test_configuration "$defconfig" |
| done |
| |
| [ -z "$checkpatch" ] || wait "$pid_checkpatch" || die "checkpatch failed" |
| [ -z "$keywords" ] || wait "$pid_keywords" || die "aiaiai-match-keywords failed" |
| |
| # Print the results |
| |
| echo "Tested the patch(es) on top of the following commits:" |
| git --no-pager log -4 --oneline "$commit_id1" |
| |
| for defconfig in $defconfigs; do |
| log1="$logdir1/$defconfig.stderr.log" |
| log2="$logdir2/$defconfig.stderr.log" |
| |
| printf "\n" |
| print_separator |
| |
| # Make kernel appends a "FAILURE" string at the end when builds fail |
| base_commit_failed="" |
| if build_failed "$log1"; then |
| build_failure "$defconfig" "$commit_id1" "the base commit" < "$log1" |
| base_commit_failed="yes" |
| print_separator |
| fi |
| |
| if build_failed "$log2"; then |
| build_failure "$defconfig" "$commit_id2" < "$log2" |
| continue |
| fi |
| |
| if [ -n "$bisectability" ] && [ -s "$tmpdir/$defconfig.bisect.results" ]; then |
| printf "\n%s\n\n" "Bisectability test results for configuration \"$defconfig\"" |
| cat "$tmpdir/$defconfig.bisect.results" |
| printf "\n" |
| print_separator |
| fi |
| |
| if [ -s "$tmpdir/$defconfig.stderr.diff" ]; then |
| printf "\n%s\n" "Successfully built configuration \"$defconfig\", the resulting" |
| printf "%s" "build diff " |
| |
| if [ -n "$base_commit_failed" ]; then |
| printf "%s\n\n" "is rather messy because the base commit failed to build:" |
| else |
| printf "%s\n\n" "is:" |
| fi |
| |
| cat "$tmpdir/$defconfig.stderr.diff" |
| |
| else |
| printf "\n%s\n" "Successfully built configuration \"$defconfig\", no issues." |
| fi |
| done; |
| |
| if [ -s "$tmpdir/keywords.results" ]; then |
| printf "\n" |
| print_separator |
| printf "\n%s\n\n" "Your patch contains unwanted keywords:" |
| cat "$tmpdir/keywords.results" |
| fi |
| |
| if [ -s "$tmpdir/checkpatch.results" ]; then |
| printf "\n" |
| print_separator |
| printf "\n%s\n" "checkpatch.pl has some complaints:" |
| cat "$tmpdir/checkpatch.results" |
| fi |
| |
| printf "\n" |
| print_separator |