#!/bin/bash -eExl
#
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# See file LICENSE for terms.
#

realdir=$(realpath $(dirname $0))
requested_gcc_module=${GCC_MODULE:-}
source ${realdir}/common.sh
source ${realdir}/../az-helpers.sh
build_mode=${build_mode:-}

# Azure passes the literal template variable when a matrix row does not define
# build_mode. Treat it as unset so the local default below still applies.
[ "${build_mode}" = "\$(build_mode)" ] && build_mode=

build_mode=${build_mode:-long}

case "${build_mode}" in
long|short|sanity|compilers)
	;;
*)
	azure_log_error "Unsupported build mode: ${build_mode}"
	exit 1
	;;
esac

common_disable_config_args=(
	--disable-gtest
	--disable-dependency-tracking
	--without-go
	--without-java
	--without-valgrind
)

compile_only_config_args=(
	"${common_disable_config_args[@]}"
	--disable-examples
	--disable-test-apps
	--disable-static
)

static_link_config_args=(
	"${common_disable_config_args[@]}"
	--disable-examples
	--disable-test-apps
)

#
# Build documentation
#
build_docs() {
	if [ `cat /etc/system-release | grep -i "fedora release 34" | wc -l` -gt 0 ]; then
		azure_log_warning "Skip build docs on Fedora 34"
		return 0
	fi
	doxy_ready=0
	doxy_target_version="1.8.11"
	doxy_version="$(doxygen --version)" || true

	# Try load newer doxygen if native is older than 1.8.11
	if ! (echo $doxy_target_version; echo $doxy_version) | sort -CV
	then
		if az_module_load tools/doxygen-1.8.11
		then
			doxy_ready=1
		fi
	else
		doxy_ready=1
	fi

	if [ $doxy_ready -eq 1 ]
	then
		echo " ==== Build docs only ===="
		${WORKSPACE}/contrib/configure-release --prefix=$ucx_inst --with-docs-only
		$MAKE docs
	fi
}

#
# Build without verbs
#
build_no_verbs() {
	echo "==== Build without IB verbs ===="
	${WORKSPACE}/contrib/configure-release --prefix=$ucx_inst --without-verbs
	$MAKEP
}

#
# Build a package in release mode
#
build_release_pkg() {
	echo "==== Build release ===="
	${WORKSPACE}/contrib/configure-release
	$MAKEP distcheck

	if [ -f /etc/redhat-release -o -f /etc/fedora-release ]; then
		rpm_based=yes
	elif [ `cat /etc/os-release | grep -i "ubuntu\|mint"|wc -l` -gt 0 ]; then
		rpm_based=no
	else
		# try rpm tool to detect distro
		set +e
		out=$(rpm -q rpm 2>/dev/null)
		rc=$?
		set -e
		rpm_based=yes
		if [[ $rc != 0 || "$out" == *"not installed"* ]]; then
			rpm_based=no
		fi
	fi

	if [[ "$rpm_based" == "no" && -x /usr/bin/dpkg-buildpackage ]]; then
		echo "==== Build debian package ===="
		(
			tarball=$(ls -t ucx-*.tar.gz | head -n1)
			tar xzf $tarball
			subdir=${tarball%.tar.gz}
			cd $subdir
			dpkg-buildpackage -us -uc
		)
		echo "==== Test install DEB (dry-run) ===="
		apt-get install -s -y ./*.deb
	else
		echo "==== Build RPM ===="
		echo "$PWD"
		${WORKSPACE}/contrib/buildrpm.sh -s -b --nodeps --define "_topdir $PWD"
		if rpm -qp ${PWD}/rpm-dist/ucx-[0-9]*.rpm --requires | grep cuda; then
			azure_log_error "Release build depends on CUDA while it should not"
			exit 1
		fi
		echo "==== Test install RPM (dry-run) ===="
		if rpm -q rpm >/dev/null 2>&1; then
			rpm -Uvh --test ${PWD}/rpm-dist/*/*.rpm
		else
			azure_log_warning "Skip RPM install test: rpm db not readable as non-root"
			azure_complete_with_issues "Skip RPM install test: rpm db not readable as non-root"
		fi
		echo "==== Build debug RPM ===="
		${WORKSPACE}/contrib/buildrpm.sh -s -b -d --nodeps --define "_topdir $PWD/debug"
	fi

	# check that UCX version is present in spec file
	cd ${WORKSPACE}
	# extract version from configure.ac and convert to (MAJOR).(MINOR).(PATCH)(EXTRA) representation
	major_ver=$(grep -P "define\S+ucx_ver_major" configure.ac | awk '{print $2}' | sed 's/)//')
	minor_ver=$(grep -P "define\S+ucx_ver_minor" configure.ac | awk '{print $2}' | sed 's/)//')
	patch_ver=$(grep -P "define\S+ucx_ver_patch" configure.ac | awk '{print $2}' | sed 's/)//')
	extra_ver=$(grep -P "define\S+ucx_ver_extra" configure.ac | awk '{print $2}' | sed 's/)//')
	version=${major_ver}.${minor_ver}.${patch_ver}${extra_ver}
	if ! grep -q "$version" ucx.spec.in; then
		azure_log_error "Current UCX version ($version) is not present in ucx.spec.in changelog"
		exit 1
	fi
	cd -
}

build_icc_check() {
	cc=$1
	cxx=$2

	if $cc -v
	then
		echo "==== Build with Intel compiler $cc ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst \
			"${common_disable_config_args[@]}" CC=$cc CXX=$cxx
		$MAKEP
		make_clean distclean
	else
		azure_log_warning "Not building with Intel compiler $cc"
	fi
}

#
# Build with Intel compiler
#
build_icc() {
	if az_module_load $INTEL_MODULE
	then
		build_icc_check icc icpc
		build_icc_check icx icpx
		build_icc_check clang clang++
	else
		azure_log_warning "Not building with Intel compilers"
	fi
	az_module_unload $INTEL_MODULE
}

#
# Build with PGI compiler
#
build_pgi() {
	if az_module_load $PGI_MODULE
	then
		# add_network_host utility from $PGI_MODULE it create config file for machine
		# Doc: https://docs.nvidia.com/hpc-sdk/hpc-sdk-install-guide/index.html
		add_network_host
		echo "==== Build with PGI compiler ===="
		# PGI failed to build valgrind headers, disable it for now.
		# TODO: Using non-default PGI compiler - pgcc18 which is going to be default
		#       in next versions.
		#       Switch to default CC compiler after pgcc18 is default for pgi module
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst \
			"${common_disable_config_args[@]}"
		$MAKEP
		# TODO: Check why "make distclean" is needed to cleanup after PGI compiler
		make_clean distclean
	else
		azure_log_warning "Not building with PGI compiler"
	fi
	az_module_unload $PGI_MODULE
}

#
# Build debug version
#
build_debug() {
	echo "==== Build with --enable-debug option ===="
	${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst --enable-debug --enable-examples
	$MAKEP

	# Show UCX info
	./src/tools/info/ucx_info -s -f -c -v -y -d -b -p -w -e -uart
}

#
# Basic compile-only build
#
build_sanity() {
	echo "==== Build sanity ===="
	if [ -n "${requested_gcc_module}" ]; then
		build_gcc
		return
	fi

	${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst
	$MAKEP
}

#
# Build prof
#
build_prof() {
	echo "==== Build configure-prof ===="
	${WORKSPACE}/contrib/configure-prof --prefix=$ucx_inst
	$MAKEP
}

#
# Build UGNI
#
build_ugni() {
	echo "==== Build with cray-ugni ===="
	#
	# Point pkg-config to contrib/cray-ugni-mock, and replace
	# PKG_CONFIG_TOP_BUILD_DIR with source dir, since the mock .pc files contain
	# relative paths.
	#
	${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst --with-ugni \
		"${common_disable_config_args[@]}" \
		PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/${WORKSPACE}/contrib/cray-ugni-mock \
		PKG_CONFIG_TOP_BUILD_DIR=${WORKSPACE}
	$MAKEP

	# make sure UGNI transport is enabled
	grep '#define HAVE_TL_UGNI 1' config.h

	$MAKEP distcheck
}

#
# Build CUDA
#
build_cuda() {
	if [[ ${CONTAINER} == *rocm* ]]; then
		echo "==== Not building with cuda flags ===="
		return
	fi

	if ! az_module_load $CUDA_MODULE
	then
		echo "==== Not building with cuda flags ===="
		return
	fi

	if az_module_load $GDRCOPY_MODULE
	then
		echo "==== Build with enable cuda, gdr_copy ===="
		${WORKSPACE}/contrib/configure-release --prefix=$ucx_inst --with-cuda --with-gdrcopy
		$MAKEP
		make_clean distclean

		echo "==== Build with enable cuda, gdr_copy by ./configure parameter ===="
		# Use path to CUDA instead of loading CUDA as module, to check
		# GDRCopy subcomponent does not include CUDA headers
		cuda_path=$(dirname $(dirname $(which nvcc)))
		az_module_unload $CUDA_MODULE
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst --with-cuda=$cuda_path --with-gdrcopy
		$MAKEP
		make_clean distclean
		az_module_load $CUDA_MODULE

		az_module_unload $GDRCOPY_MODULE
	fi

	echo "==== Build with enable cuda, w/o gdr_copy and w/o GDA-KI ===="
	${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst \
		--with-cuda --without-gdrcopy --without-gda --disable-gtest
	$MAKEP
	check_no_gda

	az_module_unload $CUDA_MODULE

	echo "==== Running test_link_map with cuda build but no cuda module ===="
	env UCX_HANDLE_ERRORS=bt ./test/apps/test_link_map
	make_clean distclean
}

#
# Build ROCm
#
build_rocm() {
	if [ -f /opt/rocm/bin/rocminfo ]; then
		echo "==== Build with enable rocm  ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst --with-rocm
		$MAKEP
	else
		echo "==== Not building with rocm ===="
	fi
}

#
# Build with clang compiler
#
build_clang() {
	if which clang > /dev/null 2>&1
	then
		echo "==== Build with clang compiler ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst CC=clang CXX=clang++
		$MAKEP
		$MAKEP install
	else
		echo "==== Not building with clang compiler ===="
	fi
}

#
# Build with gcc-latest module
#
build_gcc() {
	#If the glibc version on the host is older than 2.14, don't run
	#check the glibc version with the ldd version since it comes with glibc
	#see https://www.linuxquestions.org/questions/linux-software-2/how-to-check-glibc-version-263103/
	#see https://benohead.com/linux-check-glibc-version/
	#see https://stackoverflow.com/questions/9705660/check-glibc-version-for-a-particular-gcc-compiler
	if [ `cat /etc/os-release | grep -i "ubuntu\|mint"|wc -l` -gt 0 ]; then
		azure_log_warning "Not building with latest gcc compiler on Ubuntu"
		return 0
	fi

	ldd_ver="$(ldd --version | awk '/ldd/{print $NF}')"
	if (echo "2.14"; echo $ldd_ver) | sort -CV
	then
		if az_module_load $GCC_MODULE
		then
			echo "==== Build with GCC compiler ($(gcc --version|head -1)) ===="
			${WORKSPACE}/contrib/configure-devel "$@" --prefix=$ucx_inst
			$MAKEP
			$MAKEP install
			az_module_unload $GCC_MODULE
		fi
	else
		azure_log_warning "Not building with gcc compiler, glibc version is too old ($ldd_ver)"
	fi
}

build_no_devx() {
	build_gcc --with-devx=no
}

build_no_gga() {
	echo "==== Build without GGA transport ===="
	${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst --without-gga --disable-gtest
	$MAKEP
	check_no_gga
}

build_no_openmp() {
	build_gcc --disable-openmp
}

build_gcc_debug_opt_with_dndebug() {
	build_gcc CFLAGS="-Og -DNDEBUG" CXXFLAGS="-Og -DNDEBUG"
}

#
# Build with armclang compiler
#
build_armclang() {
	arch=$(uname -m)
	if [ "${arch}" != "aarch64" ]
	then
		echo "==== Not building with armclang compiler on ${arch} ===="
		return 0
	fi

	armclang_test_file=$(mktemp ./XXXXXX).c
	echo "int main() {return 0;}" > ${armclang_test_file}
	if az_module_load $ARM_MODULE && armclang --version && armclang ${armclang_test_file} -o ${armclang_test_file}.out
	then
		echo "==== Build with armclang compiler ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst CC=armclang CXX=armclang++
		$MAKEP
		$MAKEP install
	fi

	rm -rf ${armclang_test_file} ${armclang_test_file}.out
	az_module_unload $ARM_MODULE
}

check_config_h() {
	srcdir=${WORKSPACE}/src

	# Check if all .c files include config.h
	echo "==== Checking for config.h files in directory $srcdir ===="

	missing=`find $srcdir \( -name "*.c" -o -name "*.cc" \) -type f -exec grep -LP '\#\s*include\s+"config.h"' {} \;`

	if [ `echo $missing | wc -w` -eq 0 ]
	then
		echo "Check successful "
	else
		azure_log_error "Missing include config.h in files: $missing"
		exit 1
	fi
}

#
# Test installed headers and CMake package discovery.
#
check_inst_headers() {
	echo "==== Testing installed headers ===="

	${WORKSPACE}/contrib/configure-release --prefix=$ucx_inst \
		"${compile_only_config_args[@]}"
	$MAKEP
	$MAKEP install
	${WORKSPACE}/contrib/check_inst_headers.sh ${ucx_inst}/include

	if which cmake
	then
		echo "==== Build CMake sample ===="
		mkdir -p /tmp/cmake-ucx
		pushd /tmp/cmake-ucx
		cmake ${WORKSPACE}/examples/cmake -DCMAKE_PREFIX_PATH=$ucx_inst \
		      -DCMAKE_POLICY_VERSION_MINIMUM=3.5
		cmake --build .

		if ./test_ucp && ./test_uct
		then
			echo "Check successful "
		else
			azure_log_error "CMake test failed."
			exit 1
		fi
		popd
	else
		azure_log_warning "cmake executable not found, skipping CMake test"
	fi
}

#
# Build with FUSE
#
build_fuse() {
	if az_module_load $FUSE3_MODULE
	then
		echo "==== Build with FUSE (dynamic link) ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst \
			"${compile_only_config_args[@]}" --with-fuse3
		$MAKEP
		make_clean distclean

		echo "==== Build with FUSE (static link) ===="
		${WORKSPACE}/contrib/configure-devel --prefix=$ucx_inst \
			"${static_link_config_args[@]}" --with-fuse3-static
		$MAKEP

		az_module_unload $FUSE3_MODULE
	else
		azure_log_warning "cannot load FUSE module, skipping build with FUSE"
	fi
}

check_no_gda() {
	if [ -f ${ucx_build_dir}/src/uct/ib/mlx5/gdaki/.libs/libuct_ib_mlx5_gda.so ] ; then
		azure_log_error "build --without-gda created GDA-KI SO"
		exit 1
	fi
}

check_no_gga() {
	if [ -f ${ucx_build_dir}/src/uct/ib/mlx5/gga/.libs/libuct_ib_mlx5_la-gga_mlx5.o ] ; then
		azure_log_error "build --without-gga created GGA object"
		exit 1
	fi
}

az_init_modules
prepare_build

base_tests=('build_docs' \
			'build_debug' \
			'build_prof' \
			'build_cuda' \
			'build_rocm' \
			'build_no_verbs' \
			'build_release_pkg' \
			'build_fuse')

case "${build_mode}" in
sanity)
	tests=('build_sanity')
	;;
short)
	tests=("${base_tests[@]}")
	;;
long)
	tests=("${base_tests[@]}" \
			'build_ugni' \
			'check_config_h' \
			'check_inst_headers' \
			'build_gcc' \
			'build_no_devx' \
            'build_no_gga' \
			'build_no_openmp' \
			'build_gcc_debug_opt_with_dndebug' \
			'build_clang' \
			'build_armclang')
	;;
compilers)
	tests=('build_icc' 'build_pgi')
	;;
esac

num_tests=${#tests[@]}
for ((i=0;i<${num_tests};++i))
do
	test_name=${tests[$i]}

	# update progress indicator
	progress=$(( (i * 100) / num_tests ))
	echo "##vso[task.setprogress value=${progress};]${test_name}"

	# cleanup build dir before the task
	[ -d "${ucx_build_dir}" ] && rm -rf ${ucx_build_dir}/*

	# run the test
	$test_name
done
