| #!/bin/sh -efu |
| |
| # Copyright 2011-2012 Intel Corporation |
| # Author: Artem Bityutskiy |
| # License: GPLv2 |
| |
| srcdir="$(readlink -ev -- ${0%/*})" |
| PATH="$srcdir:$srcdir/..:$srcdir/../helpers:$srcdir/../helpers/libshell:$PATH" |
| |
| . shell-error |
| . shell-args |
| . shell-signal |
| . aiaiai-sh-functions |
| . aiaiai-email-sh-functions |
| |
| PROG="${0##*/}" |
| export 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] <cfgfile.ini> |
| |
| The mbox file containing the patches to test is expected to come from stdin |
| (unless --input option is specified). |
| |
| <cfgfile.ini> - the configuration file. |
| |
| Options: |
| -i, --input=MBOX use the MBOX file instead of stdin; |
| -v, --verbose be verbose; |
| -h, --help show this text and exit. |
| EOF |
| } |
| |
| fail_usage() |
| { |
| [ -z "$1" ] || printf "%s\n" "$1" |
| show_usage |
| exit 1 |
| } |
| |
| verbose= |
| mbox= |
| tmpdir= |
| cleanup_handler() |
| { |
| rm $verbose -rf -- "$mbox" >&2 |
| |
| if [ "$cfg_preserve_files" = "1" ]; then |
| verbose "Preserved tmpdir: $tmpdir" |
| else |
| [ -z "$tmpdir" ] || verbose "Removing $tmpdir"; |
| rm -rf -- "$tmpdir" >&2 |
| fi |
| } |
| set_cleanup_handler cleanup_handler |
| |
| # List currently supported projects. |
| list_projects() |
| { |
| local prj |
| |
| get_cfgfile_projects_list "$cfgfile" | \ |
| while read -r prj; do |
| # Get project description |
| local descr |
| ini_config_get_or_die descr "$cfgfile" "prj_$prj" "description" |
| local email="$cfg_ownmail_local+$prj@$cfg_ownmail_domain" |
| |
| # If given a url, display a sample clone line in the project |
| url="$(ini_config_get "$cfgfile" "prj_$prj" "canonical_url")" |
| branch="$(ini_config_get "$cfgfile" "prj_$prj" "branch")" |
| |
| if [ -n "$url" ]; then |
| printf "* %s\n" "$prj ($email) [git clone -b $branch $url]: $descr" |
| else |
| printf "* %s\n" "$prj ($email): $descr" |
| fi |
| done |
| } |
| |
| # Send an e-mail reply to the patch submitter. |
| send_email() |
| { |
| compose_email "$reply_to" "$reply_cc" "$reply_subj" "$reply_id" \ |
| > "$tmpdir/mail" |
| |
| [ -z "$verbose" ] || cat -- "$tmpdir/mail" >&2 |
| |
| if [ "$cfg_disable_notifications" != "1" ]; then |
| mutt -x -H "$tmpdir/mail" </dev/null |
| else |
| verbose "Email nofications have been disabled in the configuration file" |
| fi |
| } |
| |
| # This function is called when the target project for the patch under test was |
| # not specified. It sends a sensible reply back to the submitter, without |
| # carbon-copying anyone else. |
| error_no_project_specified() |
| { |
| send_email <<EOF |
| Sorry, but you have not specified the project name. Please, specify it |
| using the "+" symbol in the e-mail address of $cfg_ownname. For example, |
| "$cfg_ownmail_local+XYZ@$cfg_ownmail_domain" would mean project "XYZ". |
| |
| List of projects $cfg_ownname supports: |
| |
| $(list_projects) |
| |
| Please, contact "$cfg_adminname" <$cfg_adminmail> |
| if you have any questions. |
| EOF |
| exit 0 |
| } |
| |
| # This function is called when a hook has requested discarding a patch, and |
| # should be passed the main body content from the hook output as stdin. |
| error_hook_rejected_patch() |
| { |
| reply_cc="$(merge_addresses "$reply_cc" "$cfg_adminmail")" |
| |
| send_email <<EOF |
| $(cat) |
| |
| List of projects $cfg_ownname supports: |
| |
| $(list_projects) |
| |
| Please, contact "$cfg_adminname" <$cfg_adminmail> |
| if you have any questions. |
| EOF |
| exit 0 |
| } |
| |
| # This function is called when the patch submitter specifies a non-existing |
| # project. It sends a sensible reply back, without carbon-copying anyone else. |
| error_project_not_found() |
| { |
| send_email <<EOF |
| Sorry, but project "$prj" is not supported. List of projects $cfg_ownname |
| currently supports: |
| |
| $(list_projects) |
| |
| Please, contact "$cfg_adminname" <$cfg_adminmail> |
| if you have any questions. |
| EOF |
| exit 0 |
| } |
| |
| # This function is called when an internal error occurs, such as when |
| # aiaiai-test-patchset fails. This most probably means a bug or configuration |
| # issue occurred. This function sends a corresponding email notification. |
| error_internal_error_occurred() |
| { |
| send_email <<EOF |
| Sorry, but an internal $cfg_ownname error happened. Please, contact |
| "$cfg_adminname" <$cfg_adminmail>. |
| EOF |
| exit 0 |
| } |
| |
| # This is a helper function which sends a notification about the patch under |
| # test being accepted for testing. |
| send_accepted_email() |
| { |
| send_email <<EOF |
| Your patch or patch-set: |
| |
| $(fetch_header_per_patch "Subject" < "$mbox" | sort) |
| |
| has been accepted by $cfg_ownname and scheduled for testing. |
| EOF |
| } |
| |
| # This is a helper function which sends the reply e-mail with the results of |
| # testing. |
| send_results_email() |
| { |
| send_email <<EOF |
| $cfg_built_preamble |
| |
| $(fetch_header_per_patch "Subject" < "$mbox" | sort) |
| |
| Project: $pcfg_name ($pcfg_description) |
| |
| Configurations: $pcfg_configs |
| |
| $(cat -- $tmpdir/test-patchset.log) |
| EOF |
| } |
| |
| TEMP=`getopt -n $PROG -o i:,C:,p,v,h --long input:,verbose,help -- "$@"` || |
| fail_usage "" |
| eval set -- "$TEMP" |
| |
| mbox= |
| |
| while true; do |
| case "$1" in |
| -i|--input) |
| mbox="$(opt_check_read "$1" "$2")" |
| shift |
| ;; |
| -v|--verbose) verbose=-v |
| ;; |
| -h|--help) |
| show_usage |
| exit 0 |
| ;; |
| --) shift; break |
| ;; |
| *) fail_usage "Unrecognized option: $1" |
| ;; |
| esac |
| shift |
| done |
| |
| [ "$#" -eq 1 ] || fail_usage "Insufficient or too many arguments" |
| |
| program_required "mutt" "" |
| program_required "grep" "" |
| program_required "sed" "" |
| program_required "formail" "" |
| |
| cfgfile="$(readlink -fv -- "$1")"; shift |
| parse_config "$cfgfile" |
| |
| # Save the mbox to a temporary file if it comes from stdin |
| if [ -z "$mbox" ]; then |
| mbox="$(mktemp -t "$PROG.mbox.XXXX")" |
| cat > "$mbox" |
| fi |
| |
| # Use the cover letter subject and message ID if possible. If the cover letter |
| # was present, 'aiaiai-email-lda' would save them in special private headers. |
| subj="$(fetch_header "X-Aiaiai-Cover-Letter-Subject" < "$mbox")" |
| [ -n "$subj" ] || fetch_header_or_die subj "Subject" < "$mbox" |
| |
| id="$(fetch_header "X-Aiaiai-Cover-Letter-Message-Id" < "$mbox")" |
| [ -n "$id" ] || fetch_header_or_die id "Message-Id" < "$mbox" |
| |
| fetch_header_or_die from "From" < "$mbox" |
| |
| to="$(fetch_all_headers "To" < "$mbox")" |
| cc="$(fetch_all_headers "Cc" < "$mbox")" |
| # If there are multiple "To:" or "Cc:" headers in the first patch, we need to |
| # merge them into a single comma-separated list of addresses. |
| to="$(merge_addresses "$to")" |
| cc="$(merge_addresses "$cc")" |
| |
| # Either "To:" or "Cc:" must exist |
| if [ -z "$to" ] && [ -z "$cc" ]; then |
| die "Neither \"To:\" nor \"Cc:\" header found" |
| fi |
| |
| printf "\n" |
| verbose "Testing mbox: \"$from: $subj (Message-Id: $id)\"" |
| verbose "parsing config file \"$cfgfile\"" |
| |
| mkdir $verbose -p -- "$cfg_workdir" >&2 |
| |
| tmpdir="$(mktemp --tmpdir="$cfg_workdir" -dt "$PROG.XXXX")" |
| mv $verbose -- "$mbox" "$tmpdir/mbox" >&2 |
| mbox="$tmpdir/mbox" |
| hookoutput="$tmpdir/hook" |
| touch -- "$hookoutput" |
| |
| # Replies will refer the first patch of the patch-set under test |
| reply_subj="$subj" |
| reply_id="$id" |
| # Replies will be sent to the patch submitter |
| reply_to="$from" |
| # And do not Cc anyone thus far |
| reply_cc= |
| |
| # Run the aiaiai email hook. Output is stored in $hookoutput and we can parse |
| # this via fetch_header similar to how we parse the mbox. |
| hookscript="$(readlink -ev -- $cfg_email_hook)" |
| if [ -f "$hookscript" ] && [ -x "$hookscript" ]; then |
| # Hook points to an executable file, so we run it |
| verbose "Executing \"$hookscript\"" |
| |
| # Grab the error code here, using an || section to prevent exit on |
| # command failure. Otherwise, the non-zero exit code from the hook |
| # script would crash aiaiai-email-test-patchset |
| hookret="0" |
| "$hookscript" "$cfgfile" "$mbox" > "$hookoutput" || hookret="$?" |
| |
| # Error code 127 is an expected output of the hook, and |
| # indicates that we should reject this patch. The reply email |
| # will be sent to the user, and the hook is expected to have |
| # outputted the rejection indication. As a precaution, the |
| # rejection email will include a list of projects supported. |
| if [ "$hookret" -eq "127" ]; then |
| error_hook_rejected_patch < "$hookoutput" |
| elif [ "$hookret" -ne "0" ]; then |
| verbose "Hook exited with error code \"$hookret\"..." |
| error_internal_error_occurred |
| fi |
| fi |
| |
| # Find out the project name |
| prj="$(fetch_header "X-Aiaiai-Project" < "$hookoutput")" |
| [ -n "$prj" ] || prj="$(fetch_project_name "$to" "$cfg_ownmail")" |
| verbose "Project \"$prj\"" |
| |
| # Reject the e-mail if the project has not been specified |
| if [ -z "$prj" ]; then |
| error_no_project_specified |
| fi |
| |
| # Get the project configuration |
| parse_prj_config "$cfgfile" "$prj" |
| |
| # Check if the project specified by the submitter exists |
| if [ -z "$pcfg_name" ]; then |
| error_project_not_found |
| fi |
| |
| bisectability= |
| sparse= |
| smatch= |
| cppcheck= |
| coccinelle= |
| checkpatch= # Note, checkpatch is enabled by default |
| [ "$pcfg_bisectability" != "1" ] || bisectability="--bisectability" |
| [ "$pcfg_sparse" != "1" ] || sparse="--sparse" |
| [ "$pcfg_smatch" != "1" ] || smatch="--smatch" |
| [ "$pcfg_cppcheck" != "1" ] || cppcheck="--cppcheck" |
| [ "$pcfg_coccinelle" != "1" ] || coccinelle="--coccinelle" |
| [ "$pcfg_checkpatch" = "1" ] || checkpatch="--nocheckpatch" |
| |
| # Create the Cc list for replies that we'll be sending |
| if [ "$pcfg_reply_to_all" = "1" ]; then |
| # All the patch recipients will be CCed |
| reply_cc="$(merge_addresses "$to" "$cc" "$pcfg_always_cc")" |
| reply_cc=$(strip_address "$reply_cc" "$cfg_ownmail") |
| else |
| reply_cc="$pcfg_always_cc" |
| fi |
| |
| # Notify the sender that the patches have been accepted |
| if [ "$pcfg_accept_notify" = "1" ]; then |
| verbose "Sending \"accepted\" e-mail" |
| send_accepted_email |
| fi |
| |
| # Use the supplied commit from the hook or default to branch head |
| commit="$(fetch_header "X-Aiaiai-Commit" < "$hookoutput")" |
| [ -n "$commit" ] || commit="$pcfg_branch" |
| |
| # Test the path (or patch-set) |
| verbose "Test configs \"$pcfg_configs\" branch \"$pcfg_branch\" of \"$pcfg_path\"" |
| aiaiai-test-patchset $verbose ${cfg_preserve_files:+--preserve} \ |
| ${pcfg_targets:+--targets "$pcfg_targets"} $bisectability \ |
| $sparse $smatch $cppcheck $coccinelle $checkpatch \ |
| -i "$mbox" -j "$cfg_jobs" -c "$commit" -w "$tmpdir" \ |
| ${pcfg_defconfigdir:+-C "$pcfg_defconfigdir"} \ |
| ${pcfg_unwanted_keywords:+-K "$pcfg_unwanted_keywords"} \ |
| ${pcfg_kmake_opts:+-M "$pcfg_kmake_opts"} -- \ |
| "$pcfg_path" "$pcfg_configs" > "$tmpdir/test-patchset.log" || |
| { |
| verbose "aiaiai-test-patchset failed" |
| error_internal_error_occurred |
| } |
| |
| # Mail the results of testing |
| verbose "Test is finished, sending back the results" |
| send_results_email |