| #!/usr/bin/env bash | |
| set -e | |
| TOOLCHAINS_CSV='support/config-fragments/autobuild/toolchain-configs.csv' | |
| TEMP_CONF="" | |
| abort=0 | |
| do_abort() { | |
| abort=1 | |
| } | |
| do_clean() { | |
| if [ -n "${TEMP_CONF}" ]; then | |
| rm -f "${TEMP_CONF}" | |
| fi | |
| } | |
| main() { | |
| local o O opts | |
| local cfg dir pkg random toolchains_csv toolchain all number mode prepare_only | |
| local ret nb nb_skip nb_fail nb_legal nb_show nb_tc build_dir keep | |
| local -a toolchains | |
| local pkg_br_name | |
| o='hakc:d:n:p:r:t:T:' | |
| O='help,all,keep,prepare-only,config-snippet:,build-dir:,number:,package:,random:,toolchains-csv:,toolchain-name:' | |
| opts="$(getopt -n "${my_name}" -o "${o}" -l "${O}" -- "${@}")" | |
| eval set -- "${opts}" | |
| random=0 | |
| all=0 | |
| keep=0 | |
| number=0 | |
| mode=0 | |
| prepare_only=0 | |
| toolchains_csv="${TOOLCHAINS_CSV}" | |
| while [ ${#} -gt 0 ]; do | |
| case "${1}" in | |
| (-h|--help) | |
| help; exit 0 | |
| ;; | |
| (-a|--all) | |
| all=1; shift 1 | |
| ;; | |
| (-k|--keep) | |
| keep=1; shift 1 | |
| ;; | |
| (--prepare-only) | |
| prepare_only=1; shift 1 | |
| ;; | |
| (-c|--config-snippet) | |
| cfg="${2}"; shift 2 | |
| ;; | |
| (-d|--build-dir) | |
| dir="${2}"; shift 2 | |
| ;; | |
| (-n|--number) | |
| number="${2}"; shift 2 | |
| ;; | |
| (-p|--package) | |
| pkg="${2}"; shift 2 | |
| ;; | |
| (-r|--random) | |
| random="${2}"; shift 2 | |
| ;; | |
| (-t|--toolchains-csv) | |
| toolchains_csv="${2}"; shift 2 | |
| ;; | |
| (-T|--toolchain-name) | |
| toolchain_name="${2}"; shift 2 | |
| ;; | |
| (--) | |
| shift; break | |
| ;; | |
| esac | |
| done | |
| trap do_abort INT | |
| trap do_clean TERM HUP EXIT | |
| if [ -z "${cfg}" ]; then | |
| pkg_br_name="${pkg//-/_}" | |
| pkg_br_name="BR2_PACKAGE_${pkg_br_name^^}" | |
| TEMP_CONF="$(mktemp /tmp/test-"${pkg}"-config.XXXXXX)" | |
| echo "${pkg_br_name}=y" > "${TEMP_CONF}" | |
| cfg="${TEMP_CONF}" | |
| fi | |
| if [ ! -e "${cfg}" ]; then | |
| printf "error: %s: no such file\n" "${cfg}" >&2; exit 1 | |
| fi | |
| if [ -z "${dir}" ]; then | |
| dir="${HOME}/br-test-pkg" | |
| fi | |
| if [ "${random}" -gt 0 ]; then | |
| mode=$((mode+1)) | |
| fi | |
| if [ "${number}" -gt 0 ]; then | |
| mode=$((mode+1)) | |
| fi | |
| if [ "${all}" -eq 1 ]; then | |
| mode=$((mode+1)) | |
| fi | |
| # Default mode is to test the N first toolchains, which have been | |
| # chosen to be a good selection of toolchains. | |
| if [ ${mode} -eq 0 ] ; then | |
| number=6 | |
| elif [ ${mode} -gt 1 ] ; then | |
| printf "error: --all, --number and --random are mutually exclusive\n" >&2; exit 1 | |
| fi | |
| # Extract the URLs of the toolchains; drop internal toolchains | |
| # E.g.: http://server/path/to/name.config,arch,libc | |
| # --> http://server/path/to/name.config | |
| mapfile -t toolchains < <(sed -r -e 's/,.*//; /internal/d; /^#/d; /^$/d;' "${toolchains_csv}" \ | |
| | if [ -n "${toolchain_name}" ]; then \ | |
| grep -E "[/^]${toolchain_name}.config" | |
| elif [ "${random}" -gt 0 ]; then \ | |
| sort -R | head -n "${random}" | |
| elif [ "${number}" -gt 0 ]; then \ | |
| head -n "${number}" | |
| else | |
| sort | |
| fi | |
| ) | |
| nb_tc="${#toolchains[@]}" | |
| if [ "${nb_tc}" -eq 0 ]; then | |
| printf "error: no toolchain found (networking issue?)\n" >&2; exit 1 | |
| fi | |
| nb=0 | |
| nb_skip=0 | |
| nb_fail=0 | |
| nb_legal=0 | |
| nb_show=0 | |
| for toolchainconfig in "${toolchains[@]}"; do | |
| : $((nb++)) | |
| toolchain="$(basename "${toolchainconfig}" .config)" | |
| build_dir="${dir}/${toolchain}" | |
| printf "%40s [%*d/%d]: " "${toolchain}" ${#nb_tc} "${nb}" "${nb_tc}" | |
| build_one "${build_dir}" "${toolchainconfig}" "${cfg}" "${pkg}" "${prepare_only}" && ret=0 || ret=${?} | |
| case ${ret} in | |
| (0) printf "OK\n";; | |
| (1) : $((nb_skip++)); printf "SKIPPED\n";; | |
| (2) : $((nb_fail++)); printf "FAILED\n";; | |
| (3) : $((nb_legal++)); printf "FAILED\n";; | |
| (4) : $((nb_show++)); printf "FAILED\n";; | |
| esac | |
| if [ "${abort}" -eq 1 ]; then | |
| return 1 | |
| fi | |
| done | |
| printf "%d builds, %d skipped, %d build failed, %d legal-info failed, %d show-info failed\n" \ | |
| "${nb}" "${nb_skip}" "${nb_fail}" "${nb_legal}" "${nb_show}" | |
| return $((nb_fail + nb_legal)) | |
| } | |
| build_one() { | |
| local dir="${1}" | |
| local toolchainconfig="${2}" | |
| local cfg="${3}" | |
| local pkg="${4}" | |
| local prepare_only="${5}" | |
| mkdir -p "${dir}" | |
| CONFIG_="" support/kconfig/merge_config.sh -O "${dir}" \ | |
| "${toolchainconfig}" "support/config-fragments/minimal.config" "${cfg}" \ | |
| >> "${dir}/logfile" 2>&1 | |
| # We want all the options from the snippet to be present as-is (set | |
| # or not set) in the actual .config; if one of them is not, it means | |
| # some dependency from the toolchain or arch is not available, in | |
| # which case this config is untestable and we skip it. | |
| # We don't care about the locale to sort in, as long as both sort are | |
| # done in the same locale. | |
| comm -23 <(sort "${cfg}") <(sort "${dir}/.config") >"${dir}/missing.config" | |
| if [ -s "${dir}/missing.config" ]; then | |
| if [ ${keep} -ne 1 ]; then | |
| # Invalid configuration, drop it | |
| rm -f "${dir}/.config" | |
| fi | |
| return 1 | |
| fi | |
| # Remove file, it's empty anyway. | |
| rm -f "${dir}/missing.config" | |
| # Defer building the job to the caller (e.g. a gitlab pipeline) | |
| if [ "${prepare_only}" -eq 1 ]; then | |
| return 0 | |
| fi | |
| if [ -n "${pkg}" ]; then | |
| if ! make O="${dir}" "${pkg}-dirclean" >> "${dir}/logfile" 2>&1; then | |
| return 2 | |
| fi | |
| fi | |
| # shellcheck disable=SC2086 | |
| if ! BR_FORCE_CHECK_DEPENDENCIES=YES make O="${dir}" ${pkg} >> "${dir}/logfile" 2>&1; then | |
| return 2 | |
| fi | |
| # legal-info done systematically, because some packages have different | |
| # sources depending on the configuration (e.g. lua-5.2 vs. lua-5.3) | |
| if ! make O="${dir}" legal-info >> "${dir}/logfile" 2>&1; then | |
| return 3 | |
| fi | |
| # Validate that we generate proper json as show-info | |
| { tput smso; printf '>>> Running show-info\n'; tput rmso; } >> "${dir}/logfile" 2> /dev/null; | |
| JQ="$(which jq 2> /dev/null)" | |
| if [ -z "${JQ}" ]; then | |
| make O="${dir}" host-jq >> "${dir}/logfile" 2>&1 | |
| JQ="${dir}/host/bin/jq" | |
| fi | |
| if ! make O="${dir}" "${pkg:+${pkg}-}show-info" > "${dir}/info.json" 2>> "${dir}/logfile"; then | |
| return 4 | |
| fi | |
| if ! "${JQ}" . < "${dir}/info.json" >> "${dir}/logfile" 2>&1; then | |
| return 4 | |
| fi | |
| # If we get here, the build was successful. Clean up the build/host | |
| # directories to save disk space, unless 'keep' was set. | |
| if [ ${keep} -ne 1 ]; then | |
| make O="${dir}" clean >> "${dir}/logfile" 2>&1 | |
| fi | |
| } | |
| help() { | |
| cat <<_EOF_ | |
| test-pkg: test-build a package against various toolchains and architectures | |
| The supplied config snippet is appended to each toolchain config, the | |
| resulting configuration is checked to ensure it still contains all options | |
| specified in the snippet; if any is missing, the build is skipped, on the | |
| assumption that the package under test requires a toolchain or architecture | |
| feature that is missing. | |
| In case failures are noticed, you can fix the package and just re-run the | |
| same command again; it will re-run the test where it failed. If you did | |
| specify a package (with -p), the package build dir will be removed first. | |
| The list of toolchains is retrieved from ${TOOLCHAINS_CSV}. | |
| Only the external toolchains are tried, because building a Buildroot toolchain | |
| would take too long. An alternative toolchains CSV file can be specified with | |
| the -t option. This file should have lines consisting of the path to the | |
| toolchain config fragment and the required host architecture, separated by a | |
| comma. The config fragments should contain only the toolchain and architecture | |
| settings. | |
| By default, a useful subset of toolchains is tested. If needed, all | |
| toolchains can be tested (-a), an arbitrary number of toolchains (-n | |
| in order, -r for random). | |
| Options: | |
| -h, --help | |
| Print this help. | |
| -c CFG, --config-snippet CFG | |
| Use the CFG file as the source for the config snippet. This file | |
| should contain all the config options required to build a package. | |
| -d DIR, --build-dir DIR | |
| Do the builds in directory DIR, one sub-dir per toolchain. | |
| If not specified, defaults to \${HOME}/br-test-pkg | |
| -p PKG, --package PKG | |
| Test-build the package PKG, by running 'make PKG'; if not specified, | |
| just runs 'make'. | |
| -a, --all | |
| Test all toolchains, instead of the default subset defined by | |
| Buildroot developers. | |
| -n N, --number N | |
| Test N toolchains, in the order defined in the toolchain CSV | |
| file. | |
| -r N, --random N | |
| Limit the tests to the N randomly selected toolchains. | |
| -t CSVFILE, --toolchains-csv CSVFILE | |
| CSV file containing the paths to config fragments of toolchains to | |
| try. If not specified, the toolchains in ${TOOLCHAINS_CSV} will be | |
| used. | |
| -T NAME, --toolchain-name NAME | |
| Name of the single toolchain to use. It must be defined in the CSV | |
| list of toolchains (see also -t / --toolchains-csv). If this option | |
| is specified, then a single build will be performed on that | |
| specific toolchain only. | |
| -k, --keep | |
| Keep the build directories even if the build succeeds. | |
| Note: the logfile and configuration is always retained, even without | |
| this option. | |
| --prepare-only | |
| Only prepare the .config files, but do not build them. Output the | |
| list of build directories to stdout, and the status on stderr. | |
| Example: | |
| Testing libcec would require a config snippet that contains: | |
| BR2_PACKAGE_LIBCEC=y | |
| Testing libcurl with openSSL support would require a snippet such as: | |
| BR2_PACKAGE_OPENSSL=y | |
| BR2_PACKAGE_LIBCURL=y | |
| _EOF_ | |
| } | |
| my_name="${0##*/}" | |
| main "${@}" |