From a69c74ad8bf66f5a302cfd84f23876706f7065d8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=EC=98=A4=ED=98=95=EC=84=9D/On-Device=20Lab=28SR=29/Staff?= =?utf8?q?=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 5 Aug 2019 10:32:35 +0900 Subject: [PATCH] Use external volume mount in standalone scripts (#6136) It enables using external volume mount in CI test infra - Reduce external code download overhead - Remove pre-built acl binary dependency (especially on version up) Signed-off-by: Hyeongseok Oh --- Makefile.template | 4 +++ infra/scripts/docker_build_cross_arm.sh | 32 ++++++++--------- .../docker_build_cross_arm_benchmark_model.sh | 29 ++++++++------- infra/scripts/docker_build_cross_arm_neurun.sh | 31 ++++++++-------- .../docker_build_cross_arm_neurun_release.sh | 31 ++++++++-------- infra/scripts/docker_build_cross_arm_release.sh | 31 ++++++++-------- infra/scripts/docker_build_cross_coverage.sh | 41 +++++++++------------- infra/scripts/docker_build_test_x64.sh | 20 +++++++++-- infra/scripts/docker_build_tizen_cross.sh | 25 ++++++++----- infra/scripts/docker_build_tizen_gbs.sh | 7 ++-- infra/scripts/docker_coverage_report.sh | 3 +- 11 files changed, 135 insertions(+), 119 deletions(-) diff --git a/Makefile.template b/Makefile.template index ccfb6c2..832d6d0 100644 --- a/Makefile.template +++ b/Makefile.template @@ -58,6 +58,10 @@ ifneq ($(EXT_ACL_FOLDER),) OPTIONS+= -DARMCompute_EXTDIR=$(EXT_ACL_FOLDER) endif +ifneq ($(EXTERNAL_VOLUME),) + OPTIONS+= -DNNFW_EXTERNALS_DIR=$(EXTERNAL_VOLUME) +endif + ifneq ($(OBS_BUILD),OFF) # Use pre-installed google test library OPTIONS+= -DBUILD_GTEST=FALSE diff --git a/infra/scripts/docker_build_cross_arm.sh b/infra/scripts/docker_build_cross_arm.sh index a21bcd7..aab07a6 100755 --- a/infra/scripts/docker_build_cross_arm.sh +++ b/infra/scripts/docker_build_cross_arm.sh @@ -6,42 +6,42 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [[ ! -d $EXT_ACL_FOLDER ]]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" - # docker image name if [[ -z $DOCKER_IMAGE_NAME ]]; then echo "It will use default docker image name" fi -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" -DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" # Mirror server setting if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -set -e +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="export OPTIONS='-DBUILD_NEURUN=OFF -DBUILD_PURE_ARM_COMPUTE=ON -DBUILD_TFLITE_LOADER=OFF' && \ cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "${CMD}" +./nnfw docker-run bash -c "${CMD}" popd > /dev/null diff --git a/infra/scripts/docker_build_cross_arm_benchmark_model.sh b/infra/scripts/docker_build_cross_arm_benchmark_model.sh index 771d46e..e2996e8 100755 --- a/infra/scripts/docker_build_cross_arm_benchmark_model.sh +++ b/infra/scripts/docker_build_cross_arm_benchmark_model.sh @@ -7,14 +7,18 @@ ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs if [ ! -d $ROOTFS_DIR ]; then - echo "cannot find rootfs" - exit 1 + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [ ! -d $EXT_ACL_FOLDER ]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi # docker image name @@ -27,24 +31,19 @@ if [ -z $EXTERNAL_DOWNLOAD_SERVER ]; then echo "It will not use mirror server" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" - -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" DOCKER_ENV_VARS+=" -e BENCHMARK_ACL_BUILD=1" DOCKER_ENV_VARS+=" -e BUILD_TYPE=Release" -set -e - pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="export OPTIONS='-DBUILD_PURE_ARM_COMPUTE=ON -DBUILD_NEURUN=OFF -DBUILD_TFLITE_BENCHMARK_MODEL=ON -DBUILD_TFLITE_LOADER=OFF' && \ cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "$CMD" +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_build_cross_arm_neurun.sh b/infra/scripts/docker_build_cross_arm_neurun.sh index c925e16..b91ddd9 100755 --- a/infra/scripts/docker_build_cross_arm_neurun.sh +++ b/infra/scripts/docker_build_cross_arm_neurun.sh @@ -6,15 +6,19 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [[ ! -d $EXT_ACL_FOLDER ]]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi # docker image name @@ -27,21 +31,16 @@ if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" - -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" - -set -e pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "$CMD" +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_build_cross_arm_neurun_release.sh b/infra/scripts/docker_build_cross_arm_neurun_release.sh index 1e5f512..bf4ad6b 100755 --- a/infra/scripts/docker_build_cross_arm_neurun_release.sh +++ b/infra/scripts/docker_build_cross_arm_neurun_release.sh @@ -6,15 +6,19 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [[ ! -d $EXT_ACL_FOLDER ]]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi # docker image name @@ -27,22 +31,17 @@ if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" - -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" DOCKER_ENV_VARS+=" -e BUILD_TYPE=release" -set -e - pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "$CMD" +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_build_cross_arm_release.sh b/infra/scripts/docker_build_cross_arm_release.sh index b07c732..b021000 100755 --- a/infra/scripts/docker_build_cross_arm_release.sh +++ b/infra/scripts/docker_build_cross_arm_release.sh @@ -6,29 +6,28 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [[ ! -d $EXT_ACL_FOLDER ]]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" - # docker image name if [[ -z $DOCKER_IMAGE_NAME ]]; then echo "It will use default docker image name" fi -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" DOCKER_ENV_VARS+=" -e BUILD_TYPE=release" # Mirror server setting @@ -36,14 +35,14 @@ if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -set -e - pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="export OPTIONS='-DBUILD_NEURUN=OFF -DBUILD_PURE_ARM_COMPUTE=ON -DBUILD_TFLITE_LOADER=OFF' && \ cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "${CMD}" +./nnfw docker-run bash -c "${CMD}" popd > /dev/null diff --git a/infra/scripts/docker_build_cross_coverage.sh b/infra/scripts/docker_build_cross_coverage.sh index 5082656..38240c8 100755 --- a/infra/scripts/docker_build_cross_coverage.sh +++ b/infra/scripts/docker_build_cross_coverage.sh @@ -6,49 +6,42 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -# prepare external acl binary -if [[ ! -d $EXT_ACL_FOLDER ]]; then - echo "cannot find external acl binary" - exit 1 +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR:/opt/rootfs" -DOCKER_VOLUMES+=" -v $EXT_ACL_FOLDER:/opt/acl" -export DOCKER_VOLUMES - # docker image name if [[ -z $DOCKER_IMAGE_NAME ]]; then echo "It will use default docker image name" -else - export DOCKER_IMAGE_NAME fi -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" -DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" -DOCKER_ENV_VARS+=" -e EXT_ACL_FOLDER=/opt/acl" -DOCKER_ENV_VARS+=" -e COVERAGE_BUILD=1" - # Mirror server setting if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" -else - DOCKER_ENV_VARS+=" -e EXTERNAL_DOWNLOAD_SERVER=$EXTERNAL_DOWNLOAD_SERVER" fi -export DOCKER_ENV_VARS -set -e +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" +DOCKER_ENV_VARS+=" -e COVERAGE_BUILD=1" pushd $ROOT_PATH > /dev/null # TODO use command instead of makefile +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="cp -nv Makefile.template Makefile && \ make all install build_coverage_suite" -source nnfw docker-run-user bash -c "$CMD" +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_build_test_x64.sh b/infra/scripts/docker_build_test_x64.sh index 3c69b9f..257a7a4 100755 --- a/infra/scripts/docker_build_test_x64.sh +++ b/infra/scripts/docker_build_test_x64.sh @@ -5,6 +5,14 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" +fi + # docker image name if [[ -z $DOCKER_IMAGE_NAME ]]; then echo "It will use default docker image name" @@ -15,14 +23,18 @@ if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -set -e - pushd $ROOT_PATH > /dev/null +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="cp -nv Makefile.template Makefile && \ make all install build_test_suite" +./nnfw docker-run bash -c "$CMD" +EXIT_CODE=$? -source nnfw docker-run-user bash -c "$CMD" +if [ ${EXITCODE} -ne 0 ]; then + exit ${EXITCODE} +fi # Model download server setting if [[ -z $MODELFILE_SERVER ]]; then @@ -30,6 +42,8 @@ if [[ -z $MODELFILE_SERVER ]]; then exit 1 fi +set -e + export DOCKER_ENV_VARS=" -e MODELFILE_SERVER=$MODELFILE_SERVER" ./nnfw docker-run-user bash -c "./scripts/standalone/test_x64_neurun_cpu.sh" ./nnfw docker-run-user bash -c "./scripts/standalone/test_neurun_interp.sh" diff --git a/infra/scripts/docker_build_tizen_cross.sh b/infra/scripts/docker_build_tizen_cross.sh index 3a2006f..1ce966a 100755 --- a/infra/scripts/docker_build_tizen_cross.sh +++ b/infra/scripts/docker_build_tizen_cross.sh @@ -6,21 +6,28 @@ CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_PATH="$CURRENT_PATH/../../" # prepare rootfs -if [[ ! -d $ROOTFS_DIR ]]; then - echo "cannot find rootfs" - exit 1 +if [ ! -d $ROOTFS_DIR ]; then + echo "It will use default rootfs path" +else + DOCKER_VOLUMES+=" -v $ROOTFS_DIR:/opt/rootfs" + DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" fi -DOCKER_VOLUMES=" -v $ROOTFS_DIR/:/opt/rootfs" +# mount volume (or directory) for externals +if [ -n $EXTERNAL_VOLUME ]; then + DOCKER_VOLUMES+=" -v $EXTERNAL_VOLUME:/externals" + DOCKER_ENV_VARS+=" -e EXTERNAL_VOLUME=/externals" +else + echo "It will use default external path" +fi # docker image name if [[ -z $DOCKER_IMAGE_NAME ]]; then echo "It will use default docker image name" fi -DOCKER_ENV_VARS=" -e TARGET_ARCH=armv7l" +DOCKER_ENV_VARS+=" -e TARGET_ARCH=armv7l" DOCKER_ENV_VARS+=" -e CROSS_BUILD=1" -DOCKER_ENV_VARS+=" -e ROOTFS_DIR=/opt/rootfs" DOCKER_ENV_VARS+=" -e TARGET_OS=tizen" DOCKER_ENV_VARS+=" -e BUILD_TYPE=release" # Disable arm compute build (use rootfs) @@ -31,12 +38,12 @@ if [[ -z $EXTERNAL_DOWNLOAD_SERVER ]]; then echo "It will not use mirror server" fi -set -e - pushd $ROOT_PATH > /dev/null +export DOCKER_ENV_VARS +export DOCKER_VOLUMES CMD="cp -nv Makefile.template Makefile && \ make all install build_test_suite" -source nnfw docker-run-user bash -c "$CMD" +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_build_tizen_gbs.sh b/infra/scripts/docker_build_tizen_gbs.sh index e1f56c3..501cd3f 100755 --- a/infra/scripts/docker_build_tizen_gbs.sh +++ b/infra/scripts/docker_build_tizen_gbs.sh @@ -16,13 +16,14 @@ fi DOCKER_ENV_VARS=" --privileged" -set -e - pushd $ROOT_PATH > /dev/null CMD="gbs -c $ROOT_PATH/infra/nnfw/config/gbs.conf build \ -A armv7l --profile=profile.tizen --clean --include-all --define '$GBS_DEFINE' && \ cp -rf /home/GBS-ROOT/local/repos/tizen/armv7l/RPMS/*.rpm /opt/rpm/" -source nnfw docker-run bash -c "$CMD" + +export DOCKER_ENV_VARS +export DOCKER_VOLUMES +./nnfw docker-run bash -c "$CMD" popd > /dev/null diff --git a/infra/scripts/docker_coverage_report.sh b/infra/scripts/docker_coverage_report.sh index 2820433..f8794f7 100755 --- a/infra/scripts/docker_coverage_report.sh +++ b/infra/scripts/docker_coverage_report.sh @@ -17,6 +17,7 @@ pushd $ROOT_PATH > /dev/null CMD="GCOV_PATH=arm-linux-gnueabihf-gcov ./nnfw gen-coverage-report && tar -zcf coverage/coverage_report.tar.gz coverage/html && python tools/lcov-to-cobertura-xml/lcov_cobertura.py coverage/coverage.info -o coverage/nnfw_coverage.xml" -source nnfw docker-run-user bash -c "$CMD" + +./nnfw docker-run-user bash -c "$CMD" popd > /dev/null -- 2.7.4