From b873140a69dca3e5ebb2dd66eb5aeb015c724555 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Thu, 5 Aug 2021 09:51:59 -0500 Subject: [PATCH 1/9] [Docker] Refactor/clean-up of docker/bash.sh - Added detailed help message, displayed using `-h` or `--help`. - Optional flags handled using `getopt`, can now occur in any order. - `--mount` flag may occur more than once. - Switched from short arguments to docker-run to long arguments (e.g. `--volume` instead of `-v`). Short arguments are good shortcuts for interactive work, but can be more difficult to read in longer scripts. - Mount the `.tvm_test_data` folder, to avoid re-downloading test data already available in the host environment. --- docker/bash.sh | 337 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 240 insertions(+), 97 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index 80f4a9577be1..ed1160573005 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -30,112 +30,274 @@ # With -i, execute interactively. # -set -e +set -euo pipefail + +function show_usage() { + cat < [COMMAND] + +-h, --help + + Display this help message. + +-i, --interactive + + Start the docker session in interactive mode. + +--net=host + + Expose servers run into the container to the host, passing the + "--net=host" argument through to docker. On MacOS, this is + instead passed as "-p 8888:8888" since the host networking driver + isn't supported. + +--mount MOUNT_DIR + + Expose MOUNT_DIR as an additional mount point inside the docker + container. The mount point inside the container is the same as + the folder location outside the container. This option can be + specified multiple times. + +--dry-run + + Print the docker command to be run, but do not execute it. + +DOCKER_IMAGE_NAME + + The name of the docker container to be run. This can be an + explicit name of a docker image (e.g. "tlcpack/ci-gpu:v0.76") or + can be a shortcut as defined in the TVM Jenkinsfile + (e.g. "ci_gpu"). + +COMMAND + + The command to be run inside the docker container. If this is set + to "bash", both the --interactive and --net=host flags are set. + If no command is specified, defaults to "bash". + +EOF +} -source "$(dirname $0)/dev_common.sh" || exit 2 -interactive=0 -if [ "$1" == "-i" ]; then - interactive=1 +################################# +### Start of argument parsing ### +################################# + +DRY_RUN=false +INTERACTIVE=false +USE_NET_HOST=false +DOCKER_IMAGE_NAME= +COMMAND=bash +MOUNT_DIRS=( ) + +trap "show_usage >&2" ERR +args=$(getopt \ + --name bash.sh \ + --options "ih" \ + --longoptions "interactive,net=host,mount,dry-run" \ + --longoptions "help" \ + --unquoted \ + -- "$@") +trap - ERR +set -- $args + +while (( $# )); do + case "$1" in + -h|--help) + show_usage + exit 0 + ;; + + -i|--interactive) + INTERACTIVE=true + shift + ;; + + --net=host) + USE_NET_HOST=true + shift + ;; + + --mount) + MOUNT_DIRS+=($2) + shift + shift + ;; + + --dry-run) + DRY_RUN=true + shift + ;; + + --) + shift + break + ;; + + -*|--*) + echo "Error: Unknown flag: $1" >&2 + show_usage >&2 + exit 1 + ;; + + *) + echo "Internal Error: getopt should output -- before positional" >&2 + exit 2 + ;; + esac +done + +if (( $# )); then + DOCKER_IMAGE_NAME=$1 shift +else + echo "Error: Missing DOCKER_IMAGE_NAME" >&2 + show_usage >&2 fi -CI_DOCKER_EXTRA_PARAMS=( ) -if [[ "$1" == "--net=host" ]]; then - CI_DOCKER_EXTRA_PARAMS+=('--net=host') - shift 1 +if (( $# )); then + COMMAND="$@" fi -# Mount external directory to the docker -CI_DOCKER_MOUNT_CMD=( ) -if [ "$1" == "--mount" ]; then - shift 1 - CI_DOCKER_MOUNT_CMD=( -v "$1:$1" ) - shift 1 -fi -if [ "$#" -lt 1 ]; then - echo "Usage: docker/bash.sh [-i] [--net=host] [COMMAND]" - exit -1 +if [[ "${COMMAND}" = bash ]]; then + INTERACTIVE=true + USE_NET_HOST=true fi -DOCKER_IMAGE_NAME=$(lookup_image_spec "$1") -if [ -z "${DOCKER_IMAGE_NAME}" ]; then - DOCKER_IMAGE_NAME=("$1") +############################### +### End of argument parsing ### +############################### + +source "$(dirname $0)/dev_common.sh" || exit 2 + +DOCKER_FLAGS=( ) +DOCKER_ENV=( ) +DOCKER_MOUNT=( ) +DOCKER_DEVICES=( ) + + +# If the user gave a shortcut defined in the Jenkinsfile, use it. +EXPANDED_SHORTCUT=$(lookup_image_spec "${DOCKER_IMAGE_NAME}") +if [ -n "${EXPANDED_SHORTCUT}" ]; then + DOCKER_IMAGE_NAME="${EXPANDED_SHORTCUT}" fi -if [ "$#" -eq 1 ]; then - COMMAND="bash" - interactive=1 +# Set up working directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +WORKSPACE="$(dirname "${SCRIPT_DIR}")" +DOCKER_FLAGS+=( --workdir /workspace ) +DOCKER_MOUNT+=( --volume "${WORKSPACE}":/workspace + --volume "${SCRIPT_DIR}":/docker + ) + +# Set up CI-specific environment variables +DOCKER_ENV+=( --env CI_BUILD_HOME=/workspace + --env CI_BUILD_USER="$(id -u -n)" + --env CI_BUILD_UID="$(id -u)" + --env CI_BUILD_GROUP="$(id -g -n)" + --env CI_BUILD_GID="$(id -g)" + --env CI_PYTEST_ADD_OPTIONS="${CI_PYTEST_ADD_OPTIONS:-}" + --env CI_IMAGE_NAME="${DOCKER_IMAGE_NAME}" + ) + + +# Pass tvm test data folder through to the docker container, to avoid +# repeated downloads. +TEST_DATA_PATH="${TVM_DATA_ROOT_PATH:-${HOME}/.tvm_test_data}" +DOCKER_MOUNT+=( --volume "${TEST_DATA_PATH}":/workspace/.tvm_test_data ) + + +# Remove the container once it finishes running (--rm) and share the +# PID namespace (--pid=host). The process inside does not have pid 1 +# and SIGKILL is propagated to the process inside, allowing jenkins to +# kill it if needed. +DOCKER_FLAGS+=( --rm --pid=host) + +# Expose services running in container to the host. +if $USE_NET_HOST; then if [[ $(uname) == "Darwin" ]]; then # Docker's host networking driver isn't supported on macOS. # Use default bridge network and expose port for jupyter notebook. - CI_DOCKER_EXTRA_PARAMS+=( "${CI_DOCKER_EXTRA_PARAMS[@]}" "-p 8888:8888" ) + DOCKER_FLAGS+=( "-p 8888:8888" ) else - CI_DOCKER_EXTRA_PARAMS+=( "${CI_DOCKER_EXTRA_PARAMS[@]}" "--net=host" ) + DOCKER_FLAGS+=('--net=host') fi -else - shift 1 - COMMAND=("$@") fi -if [ $interactive -eq 1 ]; then - CI_DOCKER_EXTRA_PARAMS=( "${CI_DOCKER_EXTRA_PARAMS[@]}" -it ) +# Set up interactive sessions +if ${INTERACTIVE}; then + DOCKER_FLAGS+=( --interactive --tty ) fi -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -WORKSPACE="$(pwd)" - -# Use nvidia-docker if the container is GPU. -if [[ ! -z $CUDA_VISIBLE_DEVICES ]]; then - CUDA_ENV="-e CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}" -else - CUDA_ENV="" -fi +# Expose external directories to the docker container +for MOUNT_DIR in "${MOUNT_DIRS[@]}"; do + DOCKER_MOUNT+=( --volume "${MOUNT_DIR}:${MOUNT_DIR}" ) +done +# Use nvidia-docker for GPU container. If nvidia-docker is not +# available, fall back to using "--gpus all" flag, requires docker +# version 19.03 or higher. if [[ "${DOCKER_IMAGE_NAME}" == *"gpu"* || "${DOCKER_IMAGE_NAME}" == *"cuda"* ]]; then - if ! type "nvidia-docker" 1> /dev/null 2> /dev/null - then - DOCKER_BINARY="docker" - CUDA_ENV=" --gpus all "${CUDA_ENV} + if type nvidia-docker 1> /dev/null 2> /dev/null; then + DOCKER_BINARY=nvidia-docker else - DOCKER_BINARY="nvidia-docker" + DOCKER_BINARY=docker + DOCKER_FLAGS+=( --gpus all ) fi else - DOCKER_BINARY="docker" + DOCKER_BINARY=docker +fi + +# Pass any restrictions of allowed CUDA devices from the host to the +# docker container. +if [[ -n ${CUDA_VISIBLE_DEVICES:-} ]]; then + DOCKER_ENV+=( --env CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" ) fi + + +# Set TVM import path inside the docker image if [[ "${DOCKER_IMAGE_NAME}" == *"ci"* ]]; then - CI_ADDON_ENV="-e PYTHONPATH=/workspace/python" -else - CI_ADDON_ENV="" + DOCKER_ENV+=( "--env" "PYTHONPATH=/workspace/python" ) fi -DOCKER_ENVS="" -DOCKER_DEVICES="" -WORKSPACE_VOLUMES="" -# If the Vitis-AI docker image is selected, expose the Xilinx FPGA devices and required volumes containing e.g. DSA's and overlays + + +# If the Vitis-AI docker image is selected, expose the Xilinx FPGA +# devices and required volumes containing e.g. DSA's and overlays if [[ "${DOCKER_IMAGE_NAME}" == *"demo_vitis_ai"* && -d "/dev/shm" && -d "/opt/xilinx/dsa" && -d "/opt/xilinx/overlaybins" ]]; then - WORKSPACE_VOLUMES="-v /dev/shm:/dev/shm -v /opt/xilinx/dsa:/opt/xilinx/dsa -v /opt/xilinx/overlaybins:/opt/xilinx/overlaybins" + DOCKER_MOUNT+=( --volume /dev/shm:/dev/shm + --volume /opt/xilinx/dsa:/opt/xilinx/dsa + --volume /opt/xilinx/overlaybins:/opt/xilinx/overlaybins + ) + XCLMGMT_DRIVER="$(find /dev -name xclmgmt\*)" - DOCKER_DEVICES="" - for i in ${XCLMGMT_DRIVER} ; - do - DOCKER_DEVICES+="--device=$i " + for DRIVER in "${XCLMGMT_DRIVER}"; do + DOCKER_DEVICES+=( --device="${DRIVER}" ) done RENDER_DRIVER="$(find /dev/dri -name renderD\*)" - for i in ${RENDER_DRIVER} ; - do - DOCKER_DEVICES+="--device=$i " + for DRIVER in "${RENDER_DRIVER}"; do + DOCKER_DEVICES+=( --device="${DRIVER}" ) done fi # Add ROCm devices and set ROCM_ENABLED=1 which is used in the with_the_same_user script # to add the user to the video group if [[ "${DOCKER_IMAGE_NAME}" == *"rocm"* && -d "/dev/dri" ]]; then - DOCKER_DEVICES+="--device=/dev/kfd --device=/dev/dri " - DOCKER_ENVS+="-e ROCM_ENABLED=1 " + DOCKER_DEVICES+=( --device=/dev/kfd --device=/dev/dri ) + DOCKER_ENV+=( --env ROCM_ENABLED=1 ) +fi + +# When running from a git worktree, also mount the original git dir. +if [ -f "${WORKSPACE}/.git" ]; then + git_dir=$(cd ${WORKSPACE} && git rev-parse --git-common-dir) + if [ "${git_dir}" != "${WORKSPACE}/.git" ]; then + DOCKER_MOUNT+=( --volume "${git_dir}:${git_dir}" ) + fi fi # Print arguments. @@ -145,37 +307,18 @@ echo "" echo "Running '${COMMAND[@]}' inside ${DOCKER_IMAGE_NAME}..." -# When running from a git worktree, also mount the original git dir. -EXTRA_MOUNTS=( ) -if [ -f "${WORKSPACE}/.git" ]; then - git_dir=$(cd ${WORKSPACE} && git rev-parse --git-common-dir) - if [ "${git_dir}" != "${WORKSPACE}/.git" ]; then - EXTRA_MOUNTS=( "${EXTRA_MOUNTS[@]}" -v "${git_dir}:${git_dir}" ) - fi -fi +DOCKER_CMD=(${DOCKER_BINARY} run + "${DOCKER_FLAGS[@]}" + "${DOCKER_ENV[@]}" + "${DOCKER_MOUNT[@]}" + "${DOCKER_DEVICES[@]}" + "${DOCKER_IMAGE_NAME}" + bash --login /docker/with_the_same_user + "${COMMAND[@]}" + ) -# By default we cleanup - remove the container once it finish running (--rm) -# and share the PID namespace (--pid=host) so the process inside does not have -# pid 1 and SIGKILL is propagated to the process inside (jenkins can kill it). -${DOCKER_BINARY} run --rm --pid=host\ - ${DOCKER_DEVICES}\ - ${WORKSPACE_VOLUMES}\ - -v ${WORKSPACE}:/workspace \ - -v ${SCRIPT_DIR}:/docker \ - "${CI_DOCKER_MOUNT_CMD[@]}" \ - "${EXTRA_MOUNTS[@]}" \ - -w /workspace \ - -e "CI_BUILD_HOME=/workspace" \ - -e "CI_BUILD_USER=$(id -u -n)" \ - -e "CI_BUILD_UID=$(id -u)" \ - -e "CI_BUILD_GROUP=$(id -g -n)" \ - -e "CI_BUILD_GID=$(id -g)" \ - -e "CI_PYTEST_ADD_OPTIONS=$CI_PYTEST_ADD_OPTIONS" \ - -e "CI_IMAGE_NAME=${DOCKER_IMAGE_NAME}" \ - ${DOCKER_ENVS} \ - ${CI_ADDON_ENV} \ - ${CUDA_ENV} \ - "${CI_DOCKER_EXTRA_PARAMS[@]}" \ - ${DOCKER_IMAGE_NAME} \ - bash --login /docker/with_the_same_user \ - "${COMMAND[@]}" +if ${DRY_RUN}; then + echo "${DOCKER_CMD[@]}" +else + "${DOCKER_CMD[@]}" +fi From a70e2f3c93ca0f4f4966555d4714c8d6856502b2 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Thu, 5 Aug 2021 12:45:58 -0500 Subject: [PATCH 2/9] [Docker] docker/bash.sh CI fix Dash-prefixed arguments as part of the command now require prefixing with -- to separate them from arguments intended for docker/bash.sh --- Jenkinsfile | 94 +++++++++++++++++++++++++------------------------- docker/bash.sh | 11 ++++-- 2 files changed, 55 insertions(+), 50 deletions(-) mode change 100644 => 100755 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile old mode 100644 new mode 100755 index 13ab9e03a5b6..81323446aaa9 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -112,7 +112,7 @@ stage("Sanity Check") { node('CPU') { ws(per_exec_ws("tvm/sanity")) { init_git() - sh "${docker_run} ${ci_lint} ./tests/scripts/task_lint.sh" + sh "${docker_run} ${ci_lint} -- ./tests/scripts/task_lint.sh" } } } @@ -124,18 +124,18 @@ stage("Sanity Check") { def make(docker_type, path, make_flag) { timeout(time: max_time, unit: 'MINUTES') { try { - sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}" + sh "${docker_run} ${docker_type} -- ./tests/scripts/task_build.sh ${path} ${make_flag}" // always run cpp test when build - sh "${docker_run} ${docker_type} ./tests/scripts/task_cpp_unittest.sh" + sh "${docker_run} ${docker_type} -- ./tests/scripts/task_cpp_unittest.sh" } catch (hudson.AbortException ae) { // script exited due to user abort, directly throw instead of retry if (ae.getMessage().contains('script returned exit code 143')) { throw ae } echo 'Incremental compilation failed. Fall back to build from scratch' - sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh ${path}" - sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}" - sh "${docker_run} ${docker_type} ./tests/scripts/task_cpp_unittest.sh" + sh "${docker_run} ${docker_type} -- ./tests/scripts/task_clean.sh ${path}" + sh "${docker_run} ${docker_type} -- ./tests/scripts/task_build.sh ${path} ${make_flag}" + sh "${docker_run} ${docker_type} -- ./tests/scripts/task_cpp_unittest.sh" } } } @@ -164,11 +164,11 @@ stage('Build') { node('GPUBUILD') { ws(per_exec_ws("tvm/build-gpu")) { init_git() - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_config_build_gpu.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_config_build_gpu.sh" make(ci_gpu, 'build', '-j2') pack_lib('gpu', tvm_multilib) // compiler test - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_config_build_gpu_vulkan.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_config_build_gpu_vulkan.sh" make(ci_gpu, 'build2', '-j2') } } @@ -177,18 +177,18 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-cpu")) { init_git() - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_config_build_cpu.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_config_build_cpu.sh" make(ci_cpu, 'build', '-j2') pack_lib('cpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_unittest.sh" - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_integration.sh" - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_vta_fsim.sh" - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_vta_tsim.sh" - // sh "${docker_run} ${ci_cpu} ./tests/scripts/task_golang.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_integration.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_vta_fsim.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_vta_tsim.sh" + // sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_golang.sh" // TODO(@jroesch): need to resolve CI issue will turn back on in follow up patch - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_rust.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_rust.sh" junit "build/pytest-results/*.xml" } } @@ -198,11 +198,11 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-wasm")) { init_git() - sh "${docker_run} ${ci_wasm} ./tests/scripts/task_config_build_wasm.sh" + sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_config_build_wasm.sh" make(ci_wasm, 'build', '-j2') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_wasm} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh" + sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_web_wasm.sh" } } } @@ -211,7 +211,7 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-i386")) { init_git() - sh "${docker_run} ${ci_i386} ./tests/scripts/task_config_build_i386.sh" + sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_config_build_i386.sh" make(ci_i386, 'build', '-j2') pack_lib('i386', tvm_multilib) } @@ -221,7 +221,7 @@ stage('Build') { node('ARM') { ws(per_exec_ws("tvm/build-arm")) { init_git() - sh "${docker_run} ${ci_arm} ./tests/scripts/task_config_build_arm.sh" + sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_config_build_arm.sh" make(ci_arm, 'build', '-j4') pack_lib('arm', tvm_multilib) } @@ -231,11 +231,11 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-qemu")) { init_git() - sh "${docker_run} ${ci_qemu} ./tests/scripts/task_config_build_qemu.sh" + sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_config_build_qemu.sh" make(ci_qemu, 'build', '-j2') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_qemu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_qemu} ./tests/scripts/task_python_microtvm.sh" + sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_python_microtvm.sh" junit "build/pytest-results/*.xml" } } @@ -250,10 +250,10 @@ stage('Unit Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_sphinx_precheck.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_unittest_gpuonly.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_integration_gpuonly.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_sphinx_precheck.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_unittest_gpuonly.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_integration_gpuonly.sh" junit "build/pytest-results/*.xml" } } @@ -265,10 +265,10 @@ stage('Unit Test') { init_git() unpack_lib('i386', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_i386} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_unittest.sh" - sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_integration.sh" - sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_vta_fsim.sh" + sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_integration.sh" + sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_vta_fsim.sh" junit "build/pytest-results/*.xml" } } @@ -280,8 +280,8 @@ stage('Unit Test') { init_git() unpack_lib('arm', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_arm} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_arm} ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_python_unittest.sh" junit "build/pytest-results/*.xml" // sh "${docker_run} ${ci_arm} ./tests/scripts/task_python_integration.sh" } @@ -294,8 +294,8 @@ stage('Unit Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_java_unittest.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_java_unittest.sh" } } } @@ -309,8 +309,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_topi.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_topi.sh" junit "build/pytest-results/*.xml" } } @@ -322,8 +322,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_frontend.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_frontend.sh" junit "build/pytest-results/*.xml" } } @@ -335,8 +335,8 @@ stage('Integration Test') { init_git() unpack_lib('cpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_frontend_cpu.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_frontend_cpu.sh" junit "build/pytest-results/*.xml" } } @@ -348,8 +348,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_docs.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_docs.sh" } pack_lib('mydocs', 'docs.tgz') } @@ -361,13 +361,13 @@ stage('Integration Test') { stage('Build packages') { parallel 'conda CPU': { node('CPU') { - sh "${docker_run} tlcpack/conda-cpu ./conda/build_cpu.sh + sh "${docker_run} tlcpack/conda-cpu -- ./conda/build_cpu.sh } }, 'conda cuda': { node('CPU') { - sh "${docker_run} tlcpack/conda-cuda90 ./conda/build_cuda.sh - sh "${docker_run} tlcpack/conda-cuda100 ./conda/build_cuda.sh + sh "${docker_run} tlcpack/conda-cuda90 -- ./conda/build_cuda.sh + sh "${docker_run} tlcpack/conda-cuda100 -- ./conda/build_cuda.sh } } // Here we could upload the packages to anaconda for releases diff --git a/docker/bash.sh b/docker/bash.sh index ed1160573005..abc109293703 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -36,7 +36,7 @@ function show_usage() { cat < [COMMAND] + [--] [COMMAND] -h, --help @@ -75,7 +75,9 @@ COMMAND The command to be run inside the docker container. If this is set to "bash", both the --interactive and --net=host flags are set. - If no command is specified, defaults to "bash". + If no command is specified, defaults to "bash". If the command + contains dash-prefixed arguments, the command should be preceded + by -- to indicate arguments that are not intended for bash.sh. EOF } @@ -96,7 +98,7 @@ trap "show_usage >&2" ERR args=$(getopt \ --name bash.sh \ --options "ih" \ - --longoptions "interactive,net=host,mount,dry-run" \ + --longoptions "interactive,net=host,mount:,dry-run" \ --longoptions "help" \ --unquoted \ -- "$@") @@ -138,6 +140,9 @@ while (( $# )); do -*|--*) echo "Error: Unknown flag: $1" >&2 + echo " If this flag is intended to be passed to the" >&2 + echo " docker command, please add -- before the docker" >&2 + echo " command (e.g. docker/bash.sh ci_gpu -- build -j2)" >&2 show_usage >&2 exit 1 ;; From e3a02f58a5a5bfdb8c1af077318caff2ced8a19d Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Thu, 5 Aug 2021 12:47:14 -0500 Subject: [PATCH 3/9] [Docker] docker/bash.sh, consistent quoting --- docker/bash.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index abc109293703..d03a28889403 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -226,9 +226,9 @@ if $USE_NET_HOST; then if [[ $(uname) == "Darwin" ]]; then # Docker's host networking driver isn't supported on macOS. # Use default bridge network and expose port for jupyter notebook. - DOCKER_FLAGS+=( "-p 8888:8888" ) + DOCKER_FLAGS+=( -p 8888:8888 ) else - DOCKER_FLAGS+=('--net=host') + DOCKER_FLAGS+=(--net=host) fi fi @@ -266,7 +266,7 @@ fi # Set TVM import path inside the docker image if [[ "${DOCKER_IMAGE_NAME}" == *"ci"* ]]; then - DOCKER_ENV+=( "--env" "PYTHONPATH=/workspace/python" ) + DOCKER_ENV+=( --env PYTHONPATH=/workspace/python ) fi From ffac386d9b4850a9276b1a98fdb7ca13e6787f1c Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Thu, 5 Aug 2021 13:09:29 -0500 Subject: [PATCH 4/9] [Docker] Added --repo-mount-point for docker/bash.sh --- docker/README.md | 9 +++++---- docker/bash.sh | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docker/README.md b/docker/README.md index 5e470350749d..a05079d30881 100644 --- a/docker/README.md +++ b/docker/README.md @@ -33,7 +33,7 @@ interactive bash session with a given image_name. The script does the following things: -- Mount current directory to /workspace and set it as home +- Mount current directory to the same location in the docker container, and set it as home - Switch user to be the same user that calls the bash.sh - Use the host-side network @@ -102,9 +102,10 @@ The command ``./docker/build.sh image_name COMMANDS`` is almost equivelant to ``./docker/bash.sh image_name COMMANDS`` but in the case of ``bash.sh`` a build attempt is not done. -The build command will map the tvm root to /workspace/ inside the container -with the same user as the user invoking the docker command. -Here are some common use examples to perform CI tasks. +The build command will map the tvm root to the corresponding location +inside the container with the same user as the user invoking the +docker command. Here are some common use examples to perform CI +tasks. - lint the python codes diff --git a/docker/bash.sh b/docker/bash.sh index d03a28889403..b01c14a4d6c2 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -18,7 +18,7 @@ # under the License. # -# Start a bash, mount /workspace to be current directory. +# Start a bash, mount REPO_MOUNT_POINT to be current directory. # # Usage: bash.sh [-i] [--net=host] [--mount path] # @@ -35,7 +35,8 @@ set -euo pipefail function show_usage() { cat < [--] [COMMAND] -h, --help @@ -60,6 +61,14 @@ Usage: docker/bash.sh [-i|--interactive] [--net=host] the folder location outside the container. This option can be specified multiple times. +--repo-mount-point REPO_MOUNT_POINT + + The directory inside the docker container at which the TVM + repository should be mounted, and is used as the workspace inside + the docker container. If unspecified, the TVM repository will be + mounted at the same location inside the docker container as + outside. + --dry-run Print the docker command to be run, but do not execute it. @@ -87,11 +96,15 @@ EOF ### Start of argument parsing ### ################################# +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +WORKSPACE="$(dirname "${SCRIPT_DIR}")" + DRY_RUN=false INTERACTIVE=false USE_NET_HOST=false DOCKER_IMAGE_NAME= COMMAND=bash +REPO_MOUNT_POINT="${WORKSPACE}" MOUNT_DIRS=( ) trap "show_usage >&2" ERR @@ -99,6 +112,7 @@ args=$(getopt \ --name bash.sh \ --options "ih" \ --longoptions "interactive,net=host,mount:,dry-run" \ + --longoptions "repo-mount-point:" \ --longoptions "help" \ --unquoted \ -- "$@") @@ -123,7 +137,7 @@ while (( $# )); do ;; --mount) - MOUNT_DIRS+=($2) + MOUNT_DIRS+=("$2") shift shift ;; @@ -133,6 +147,12 @@ while (( $# )); do shift ;; + --repo-mount-point) + REPO_MOUNT_POINT="$2" + shift + shift + ;; + --) shift break @@ -191,15 +211,14 @@ if [ -n "${EXPANDED_SHORTCUT}" ]; then fi # Set up working directories -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -WORKSPACE="$(dirname "${SCRIPT_DIR}")" -DOCKER_FLAGS+=( --workdir /workspace ) -DOCKER_MOUNT+=( --volume "${WORKSPACE}":/workspace + +DOCKER_FLAGS+=( --workdir "${REPO_MOUNT_POINT}" ) +DOCKER_MOUNT+=( --volume "${WORKSPACE}":"${REPO_MOUNT_POINT}" --volume "${SCRIPT_DIR}":/docker ) # Set up CI-specific environment variables -DOCKER_ENV+=( --env CI_BUILD_HOME=/workspace +DOCKER_ENV+=( --env CI_BUILD_HOME="${REPO_MOUNT_POINT}" --env CI_BUILD_USER="$(id -u -n)" --env CI_BUILD_UID="$(id -u)" --env CI_BUILD_GROUP="$(id -g -n)" @@ -212,7 +231,7 @@ DOCKER_ENV+=( --env CI_BUILD_HOME=/workspace # Pass tvm test data folder through to the docker container, to avoid # repeated downloads. TEST_DATA_PATH="${TVM_DATA_ROOT_PATH:-${HOME}/.tvm_test_data}" -DOCKER_MOUNT+=( --volume "${TEST_DATA_PATH}":/workspace/.tvm_test_data ) +DOCKER_MOUNT+=( --volume "${TEST_DATA_PATH}":"${REPO_MOUNT_POINT}"/.tvm_test_data ) # Remove the container once it finishes running (--rm) and share the @@ -266,7 +285,7 @@ fi # Set TVM import path inside the docker image if [[ "${DOCKER_IMAGE_NAME}" == *"ci"* ]]; then - DOCKER_ENV+=( --env PYTHONPATH=/workspace/python ) + DOCKER_ENV+=( --env PYTHONPATH="${REPO_MOUNT_POINT}"/python ) fi From dbca0d2923bdcb27c10607ba139a9c0bb4413868 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Fri, 6 Aug 2021 11:53:44 -0500 Subject: [PATCH 5/9] [Docker] Updated command-line parsing of docker/bash.sh - Maintained previous behavior, any unrecognized flags after the docker/bash.sh are part of the command, no -- is needed. (e.g. docker/bash.sh ci_gpu make -j2) - Reverted changes to Jenskinsfile to add a --, no longer needed. --- Jenkinsfile | 94 +++++++++++++++++++++++++------------------------- docker/bash.sh | 80 ++++++++++++++++++++++++++---------------- 2 files changed, 98 insertions(+), 76 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 81323446aaa9..13ab9e03a5b6 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -112,7 +112,7 @@ stage("Sanity Check") { node('CPU') { ws(per_exec_ws("tvm/sanity")) { init_git() - sh "${docker_run} ${ci_lint} -- ./tests/scripts/task_lint.sh" + sh "${docker_run} ${ci_lint} ./tests/scripts/task_lint.sh" } } } @@ -124,18 +124,18 @@ stage("Sanity Check") { def make(docker_type, path, make_flag) { timeout(time: max_time, unit: 'MINUTES') { try { - sh "${docker_run} ${docker_type} -- ./tests/scripts/task_build.sh ${path} ${make_flag}" + sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}" // always run cpp test when build - sh "${docker_run} ${docker_type} -- ./tests/scripts/task_cpp_unittest.sh" + sh "${docker_run} ${docker_type} ./tests/scripts/task_cpp_unittest.sh" } catch (hudson.AbortException ae) { // script exited due to user abort, directly throw instead of retry if (ae.getMessage().contains('script returned exit code 143')) { throw ae } echo 'Incremental compilation failed. Fall back to build from scratch' - sh "${docker_run} ${docker_type} -- ./tests/scripts/task_clean.sh ${path}" - sh "${docker_run} ${docker_type} -- ./tests/scripts/task_build.sh ${path} ${make_flag}" - sh "${docker_run} ${docker_type} -- ./tests/scripts/task_cpp_unittest.sh" + sh "${docker_run} ${docker_type} ./tests/scripts/task_clean.sh ${path}" + sh "${docker_run} ${docker_type} ./tests/scripts/task_build.sh ${path} ${make_flag}" + sh "${docker_run} ${docker_type} ./tests/scripts/task_cpp_unittest.sh" } } } @@ -164,11 +164,11 @@ stage('Build') { node('GPUBUILD') { ws(per_exec_ws("tvm/build-gpu")) { init_git() - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_config_build_gpu.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_config_build_gpu.sh" make(ci_gpu, 'build', '-j2') pack_lib('gpu', tvm_multilib) // compiler test - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_config_build_gpu_vulkan.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_config_build_gpu_vulkan.sh" make(ci_gpu, 'build2', '-j2') } } @@ -177,18 +177,18 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-cpu")) { init_git() - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_config_build_cpu.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_config_build_cpu.sh" make(ci_cpu, 'build', '-j2') pack_lib('cpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_unittest.sh" - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_integration.sh" - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_vta_fsim.sh" - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_vta_tsim.sh" - // sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_golang.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_integration.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_vta_fsim.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_vta_tsim.sh" + // sh "${docker_run} ${ci_cpu} ./tests/scripts/task_golang.sh" // TODO(@jroesch): need to resolve CI issue will turn back on in follow up patch - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_rust.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_rust.sh" junit "build/pytest-results/*.xml" } } @@ -198,11 +198,11 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-wasm")) { init_git() - sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_config_build_wasm.sh" + sh "${docker_run} ${ci_wasm} ./tests/scripts/task_config_build_wasm.sh" make(ci_wasm, 'build', '-j2') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_wasm} -- ./tests/scripts/task_web_wasm.sh" + sh "${docker_run} ${ci_wasm} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_wasm} ./tests/scripts/task_web_wasm.sh" } } } @@ -211,7 +211,7 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-i386")) { init_git() - sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_config_build_i386.sh" + sh "${docker_run} ${ci_i386} ./tests/scripts/task_config_build_i386.sh" make(ci_i386, 'build', '-j2') pack_lib('i386', tvm_multilib) } @@ -221,7 +221,7 @@ stage('Build') { node('ARM') { ws(per_exec_ws("tvm/build-arm")) { init_git() - sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_config_build_arm.sh" + sh "${docker_run} ${ci_arm} ./tests/scripts/task_config_build_arm.sh" make(ci_arm, 'build', '-j4') pack_lib('arm', tvm_multilib) } @@ -231,11 +231,11 @@ stage('Build') { node('CPU') { ws(per_exec_ws("tvm/build-qemu")) { init_git() - sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_config_build_qemu.sh" + sh "${docker_run} ${ci_qemu} ./tests/scripts/task_config_build_qemu.sh" make(ci_qemu, 'build', '-j2') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_qemu} -- ./tests/scripts/task_python_microtvm.sh" + sh "${docker_run} ${ci_qemu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_qemu} ./tests/scripts/task_python_microtvm.sh" junit "build/pytest-results/*.xml" } } @@ -250,10 +250,10 @@ stage('Unit Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_sphinx_precheck.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_unittest_gpuonly.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_integration_gpuonly.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_sphinx_precheck.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_unittest_gpuonly.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_integration_gpuonly.sh" junit "build/pytest-results/*.xml" } } @@ -265,10 +265,10 @@ stage('Unit Test') { init_git() unpack_lib('i386', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_unittest.sh" - sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_integration.sh" - sh "${docker_run} ${ci_i386} -- ./tests/scripts/task_python_vta_fsim.sh" + sh "${docker_run} ${ci_i386} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_integration.sh" + sh "${docker_run} ${ci_i386} ./tests/scripts/task_python_vta_fsim.sh" junit "build/pytest-results/*.xml" } } @@ -280,8 +280,8 @@ stage('Unit Test') { init_git() unpack_lib('arm', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_arm} -- ./tests/scripts/task_python_unittest.sh" + sh "${docker_run} ${ci_arm} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_arm} ./tests/scripts/task_python_unittest.sh" junit "build/pytest-results/*.xml" // sh "${docker_run} ${ci_arm} ./tests/scripts/task_python_integration.sh" } @@ -294,8 +294,8 @@ stage('Unit Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_java_unittest.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_java_unittest.sh" } } } @@ -309,8 +309,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_topi.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_topi.sh" junit "build/pytest-results/*.xml" } } @@ -322,8 +322,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_frontend.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_frontend.sh" junit "build/pytest-results/*.xml" } } @@ -335,8 +335,8 @@ stage('Integration Test') { init_git() unpack_lib('cpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_cpu} -- ./tests/scripts/task_python_frontend_cpu.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_cpu} ./tests/scripts/task_python_frontend_cpu.sh" junit "build/pytest-results/*.xml" } } @@ -348,8 +348,8 @@ stage('Integration Test') { init_git() unpack_lib('gpu', tvm_multilib) timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_ci_setup.sh" - sh "${docker_run} ${ci_gpu} -- ./tests/scripts/task_python_docs.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_ci_setup.sh" + sh "${docker_run} ${ci_gpu} ./tests/scripts/task_python_docs.sh" } pack_lib('mydocs', 'docs.tgz') } @@ -361,13 +361,13 @@ stage('Integration Test') { stage('Build packages') { parallel 'conda CPU': { node('CPU') { - sh "${docker_run} tlcpack/conda-cpu -- ./conda/build_cpu.sh + sh "${docker_run} tlcpack/conda-cpu ./conda/build_cpu.sh } }, 'conda cuda': { node('CPU') { - sh "${docker_run} tlcpack/conda-cuda90 -- ./conda/build_cuda.sh - sh "${docker_run} tlcpack/conda-cuda100 -- ./conda/build_cuda.sh + sh "${docker_run} tlcpack/conda-cuda90 ./conda/build_cuda.sh + sh "${docker_run} tlcpack/conda-cuda100 ./conda/build_cuda.sh } } // Here we could upload the packages to anaconda for releases diff --git a/docker/bash.sh b/docker/bash.sh index b01c14a4d6c2..cff5215e850b 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -20,7 +20,10 @@ # # Start a bash, mount REPO_MOUNT_POINT to be current directory. # -# Usage: bash.sh [-i] [--net=host] [--mount path] +# Usage: docker/bash.sh [-i|--interactive] [--net=host] +# [--mount MOUNT_DIR] [--repo-mount-point REPO_MOUNT_POINT] +# [--dry-run] +# [--] [COMMAND] # # Usage: docker/bash.sh # Starts an interactive session @@ -107,17 +110,20 @@ COMMAND=bash REPO_MOUNT_POINT="${WORKSPACE}" MOUNT_DIRS=( ) -trap "show_usage >&2" ERR -args=$(getopt \ - --name bash.sh \ - --options "ih" \ - --longoptions "interactive,net=host,mount:,dry-run" \ - --longoptions "repo-mount-point:" \ - --longoptions "help" \ - --unquoted \ - -- "$@") -trap - ERR -set -- $args +function parse_error() { + echo "$@" >&2 + show_usage >&2 + exit 1 +} + + +# Handle joined flags, such as interpreting -ih as -i -h. Either rewrites +# the current argument if it is a joined argument, or shifts all arguments +# otherwise. Should be called as "eval $break_joined_flag" where joined +# flags are possible. Can't use a function definition, because it needs +# to overwrite the parent scope's behavior. +break_joined_flag='if (( ${#1} == 2 )); then shift; else set -- -"${1#-i}" "${@:2}"; fi' + while (( $# )); do case "$1" in @@ -126,9 +132,9 @@ while (( $# )); do exit 0 ;; - -i|--interactive) + -i*|--interactive) INTERACTIVE=true - shift + eval $break_joined_flag ;; --net=host) @@ -137,8 +143,16 @@ while (( $# )); do ;; --mount) - MOUNT_DIRS+=("$2") - shift + if [[ -n "$2" ]]; then + MOUNT_DIRS+=("$2") + shift 2 + else + parse_error 'ERROR: --mount requires a non-empty argument' + fi + ;; + + --mount=?*) + MOUNT_DIRS+=("${1#*=}") shift ;; @@ -148,13 +162,22 @@ while (( $# )); do ;; --repo-mount-point) - REPO_MOUNT_POINT="$2" - shift + if [[ -n "$2" ]]; then + REPO_MOUNT_POINT="$2" + shift 2 + else + parse_error 'ERROR: --repo-mount-point requires a non-empty argument' + fi + ;; + + --repo-mount-point=?*) + REPO_MOUNT_POINT="${1#*=}" shift ;; --) shift + COMMAND="$@" break ;; @@ -168,25 +191,24 @@ while (( $# )); do ;; *) - echo "Internal Error: getopt should output -- before positional" >&2 - exit 2 + # First positional argument is the image name, all + # remaining below to the COMMAND. + if [[ -z "${DOCKER_IMAGE_NAME}" ]]; then + DOCKER_IMAGE_NAME=$1 + shift + else + COMMAND="$@" + break + fi ;; esac done -if (( $# )); then - DOCKER_IMAGE_NAME=$1 - shift -else +if [[ -z "${DOCKER_IMAGE_NAME}" ]]; then echo "Error: Missing DOCKER_IMAGE_NAME" >&2 show_usage >&2 fi -if (( $# )); then - COMMAND="$@" -fi - - if [[ "${COMMAND}" = bash ]]; then INTERACTIVE=true USE_NET_HOST=true From 9581e638eb03eb55d84f74019a4f4e93734c0a3d Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Fri, 6 Aug 2021 12:18:07 -0500 Subject: [PATCH 6/9] [Docker] Fixed multi-argument commands --- docker/bash.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index cff5215e850b..fb793ec52389 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -177,7 +177,7 @@ while (( $# )); do --) shift - COMMAND="$@" + COMMAND=( "$@" ) break ;; @@ -197,7 +197,7 @@ while (( $# )); do DOCKER_IMAGE_NAME=$1 shift else - COMMAND="$@" + COMMAND=( "$@" ) break fi ;; @@ -209,7 +209,7 @@ if [[ -z "${DOCKER_IMAGE_NAME}" ]]; then show_usage >&2 fi -if [[ "${COMMAND}" = bash ]]; then +if [[ "${COMMAND[@]}" = bash ]]; then INTERACTIVE=true USE_NET_HOST=true fi From 00f43b75b4d3ef03e7ffde2e3621a1bc53587f8e Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Fri, 6 Aug 2021 14:22:26 -0500 Subject: [PATCH 7/9] [Docker] docker/bash.sh check permissions before mounting ~/.tvm_test_data --- docker/bash.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index fb793ec52389..04349a1c52d2 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -251,9 +251,12 @@ DOCKER_ENV+=( --env CI_BUILD_HOME="${REPO_MOUNT_POINT}" # Pass tvm test data folder through to the docker container, to avoid -# repeated downloads. +# repeated downloads. Check if we have permissions to write to the +# directory first, since the CI may not. TEST_DATA_PATH="${TVM_DATA_ROOT_PATH:-${HOME}/.tvm_test_data}" -DOCKER_MOUNT+=( --volume "${TEST_DATA_PATH}":"${REPO_MOUNT_POINT}"/.tvm_test_data ) +if [[ -d "${TEST_DATA_PATH}" && -w "${TEST_DATA_PATH}" ]]; then + DOCKER_MOUNT+=( --volume "${TEST_DATA_PATH}":"${REPO_MOUNT_POINT}"/.tvm_test_data ) +fi # Remove the container once it finishes running (--rm) and share the From cb9da3282c919c8b1fa05ba945096b32b909e435 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Fri, 6 Aug 2021 19:17:28 -0500 Subject: [PATCH 8/9] [Docker] Consistent workplace directory in docker/bash.sh for Jenkins Some locations in the CI perform build commands outside of the build steps (e.g. tests/scripts/task_ci_setup.sh#L38), and cmake doesn't like it if the build directory changes. These should probably be moved into the build steps of the CI, and be packed in tvm_multilib in the Jenkinsfile, but for the meantime maintaining a consistent /workspace directory on all CI nodes allows cmake to run. --- docker/bash.sh | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index 04349a1c52d2..beff864522dd 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -68,9 +68,13 @@ Usage: docker/bash.sh [-i|--interactive] [--net=host] The directory inside the docker container at which the TVM repository should be mounted, and is used as the workspace inside - the docker container. If unspecified, the TVM repository will be - mounted at the same location inside the docker container as - outside. + the docker container. + + If unspecified, the mount location depends on the environment. If + running inside Jenkins, the mount location will be /workspace. + Otherwise, the mount location of the repository will be the same + as the external location of the repository, to maintain + compatibility with git-worktree. --dry-run @@ -100,23 +104,32 @@ EOF ################################# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -WORKSPACE="$(dirname "${SCRIPT_DIR}")" +REPO_DIR="$(dirname "${SCRIPT_DIR}")" DRY_RUN=false INTERACTIVE=false USE_NET_HOST=false DOCKER_IMAGE_NAME= COMMAND=bash -REPO_MOUNT_POINT="${WORKSPACE}" MOUNT_DIRS=( ) +# TODO(Lunderberg): Remove this if statement and always set to +# "${REPO_DIR}". The consistent directory for Jenkins is currently +# necessary to allow cmake build commands to run in CI after the build +# steps. +if [[ -n "${JENKINS_HOME:-}" ]]; then + REPO_MOUNT_POINT=/workspace +else + REPO_MOUNT_POINT="${REPO_DIR}" +fi + + function parse_error() { echo "$@" >&2 show_usage >&2 exit 1 } - # Handle joined flags, such as interpreting -ih as -i -h. Either rewrites # the current argument if it is a joined argument, or shifts all arguments # otherwise. Should be called as "eval $break_joined_flag" where joined @@ -214,6 +227,8 @@ if [[ "${COMMAND[@]}" = bash ]]; then USE_NET_HOST=true fi + + ############################### ### End of argument parsing ### ############################### @@ -235,7 +250,7 @@ fi # Set up working directories DOCKER_FLAGS+=( --workdir "${REPO_MOUNT_POINT}" ) -DOCKER_MOUNT+=( --volume "${WORKSPACE}":"${REPO_MOUNT_POINT}" +DOCKER_MOUNT+=( --volume "${REPO_DIR}":"${REPO_MOUNT_POINT}" --volume "${SCRIPT_DIR}":/docker ) @@ -342,15 +357,15 @@ if [[ "${DOCKER_IMAGE_NAME}" == *"rocm"* && -d "/dev/dri" ]]; then fi # When running from a git worktree, also mount the original git dir. -if [ -f "${WORKSPACE}/.git" ]; then - git_dir=$(cd ${WORKSPACE} && git rev-parse --git-common-dir) - if [ "${git_dir}" != "${WORKSPACE}/.git" ]; then +if [ -f "${REPO_DIR}/.git" ]; then + git_dir=$(cd ${REPO_DIR} && git rev-parse --git-common-dir) + if [ "${git_dir}" != "${REPO_DIR}/.git" ]; then DOCKER_MOUNT+=( --volume "${git_dir}:${git_dir}" ) fi fi # Print arguments. -echo "WORKSPACE: ${WORKSPACE}" +echo "REPO_DIR: ${REPO_DIR}" echo "DOCKER CONTAINER NAME: ${DOCKER_IMAGE_NAME}" echo "" From 0078025308783df3ae2bc30f420f4fe2356b7496 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Tue, 10 Aug 2021 09:05:43 -0500 Subject: [PATCH 9/9] [Docker] Updated bash.sh for MacOS compatibility MacOS has an older version of bash that handles arrays slightly differently. All instances of array expansion `"${ARRAY[@]}"` should instead be written as `${ARRAY[@]+"${ARRAY[@]}"}`. Otherwise, `set -u` will erroneously complain about an undefined variable. See https://stackoverflow.com/a/61551944 for details. Even though this is an older version of bash (observed in version 3.2.57), this is the last major version available under GPLv2 and is therefore the default version on MacOSX. At some point, the `docker/bash.sh` could be migrated to python for ease of maintenance/testing. --- docker/bash.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docker/bash.sh b/docker/bash.sh index beff864522dd..702cfa0f671b 100755 --- a/docker/bash.sh +++ b/docker/bash.sh @@ -222,7 +222,7 @@ if [[ -z "${DOCKER_IMAGE_NAME}" ]]; then show_usage >&2 fi -if [[ "${COMMAND[@]}" = bash ]]; then +if [[ ${COMMAND[@]+"${COMMAND[@]}"} = bash ]]; then INTERACTIVE=true USE_NET_HOST=true fi @@ -297,7 +297,7 @@ if ${INTERACTIVE}; then fi # Expose external directories to the docker container -for MOUNT_DIR in "${MOUNT_DIRS[@]}"; do +for MOUNT_DIR in ${MOUNT_DIRS[@]+"${MOUNT_DIRS[@]}"}; do DOCKER_MOUNT+=( --volume "${MOUNT_DIR}:${MOUNT_DIR}" ) done @@ -369,20 +369,20 @@ echo "REPO_DIR: ${REPO_DIR}" echo "DOCKER CONTAINER NAME: ${DOCKER_IMAGE_NAME}" echo "" -echo "Running '${COMMAND[@]}' inside ${DOCKER_IMAGE_NAME}..." +echo Running \'${COMMAND[@]+"${COMMAND[@]}"}\' inside ${DOCKER_IMAGE_NAME}... DOCKER_CMD=(${DOCKER_BINARY} run - "${DOCKER_FLAGS[@]}" - "${DOCKER_ENV[@]}" - "${DOCKER_MOUNT[@]}" - "${DOCKER_DEVICES[@]}" + ${DOCKER_FLAGS[@]+"${DOCKER_FLAGS[@]}"} + ${DOCKER_ENV[@]+"${DOCKER_ENV[@]}"} + ${DOCKER_MOUNT[@]+"${DOCKER_MOUNT[@]}"} + ${DOCKER_DEVICES[@]+"${DOCKER_DEVICES[@]}"} "${DOCKER_IMAGE_NAME}" bash --login /docker/with_the_same_user - "${COMMAND[@]}" + ${COMMAND[@]+"${COMMAND[@]}"} ) if ${DRY_RUN}; then - echo "${DOCKER_CMD[@]}" + echo ${DOCKER_CMD[@]+"${DOCKER_CMD[@]}"} else - "${DOCKER_CMD[@]}" + ${DOCKER_CMD[@]+"${DOCKER_CMD[@]}"} fi