#!/usr/bin/bash

set -e

DOCA_VERSION="25.01-0.6.0.0"
DOCA_VERSION_PACK="${DOCA_VERSION//[_-]/.}"
arch=`uname -m` # ARCH may be a special name
SUDO=""
DRY_RUN="0"
DR=""
VERBOSE="0"
KEEP_GOING="0"
CLEAN_TMPS="1"
PACKAGE_NAME="doca-kernel-repo"
MAINTAINER="Tzafrir Cohen <nvidia@cohens.org.il>"

RES_FOLDER="/opt/mellanox/doca/tools/resources"
TOP_DIR=""
BUILD_DIR=""
PACKAGES_DIR=""
REPO_DIR=""
TAR_FILE=""
KMP=1
FINAL_REPO_FILE=
RPM_PACKAGE_INSTALL_TOOL="dnf"

me=${0##*/}

usage() {
	cat <<EOF
$me: Rebuild DOCA-host kernel modules

Use this to provide kernel modules built against a custom kernel or
an incompatible newer kernel update.

Usage: $me [optional parameters]

Options:
-k VERSION --kernel VERSION - Build for kernel VERSION instead of the
                              running kernel.
			      May also be set by the environment variable
			      KERNEL_VERSION
-s PATH --kernel-source PATH - Build for kernel from directory PATH.
                               If given, overrides any value set by
			       -k above and the version is set from the
			       source tree.
			       May also be set by the environment variable
			       KERNEL_VERSION
-t TARFILE --tarball TARFILE - Get sources from a custom sources tar file.
-v --verbose     - Print more messages.
-d --dry-run     - Show what would happen. Not a full run, though. Only
                   shows what needs to be installed and what would be built.
-h --help        - Show this message.
-K --keep-going  - Don't exit on a build failure. Except when building
                   mlnx-ofa_kernel / mlnx-ofed-kernel.
   --dirty       - Avoid cleanup of temporary files.
EOF
}

set_format() {
	FORMAT="deb"
	# Checks what installer we have. Not what system we're on:
	if tar tf "$TAR_FILE" 2>/dev/null | grep -q '/SRPMS/.*\.src\.rpm$'; then
		FORMAT="rpm"
	fi
}

set_sudo() {
	if [ "`id -u`" != 0 ]; then
		if sudo -n ls /root >/dev/null 2>&1; then
			SUDO="sudo"
			verbose "Using sudo"
		else
			info "Running as non-root and sudo not available"
		fi
	else
		verbose "Running as root"
	fi
}

set_dry_run() {
	if [ "$DRY_RUN" = 0 ]; then
		return
	fi
	verbose "Dry run"
	DR="dry_run"
}

set_tar_file() {
	if [ "$TAR_FILE" = '' ]; then
		TAR_FILE=`echo ${RES_FOLDER}/MLNX_OFED_*.tgz`
	fi
	if [ ! -r "$TAR_FILE" ]; then
		error "Cannot use source archive '$TAR_FILE' as a source. Aborting."
		exit 2
	fi
	if ! which tar &>/dev/null; then
		error "Need to install 'tar' to extract sources. Aborting."
		exit 2
	fi
	verbose "Using source tar file $TAR_FILE."
}

info() {
	echo "$me: $*"
}

verbose() {
	if [ "$VERBOSE" = 0 ]; then
		return
	fi
	echo "$me: $*"
}

dry_run() {
	info "Would run:" "$@"
}

error () {
	echo "$me: Error: $*"
}

set_kernel_vars() {
	local kernel_source kernel_version kernel_version_test dev_package

	kernel_source="$1"
	kernel_version="$2"

	case "$FORMAT" in
	rpm) dev_package="kernel-devel";;
	deb) dev_package="linux-headers";;
	esac

	if [ "$kernel_source" != '' ]; then
		kernel_version=`make -s -C "$kernel_source" M="$PWD" kernelrelease 2>/dev/null || :`
		if [ "$kernel_version" = '' ]; then
			error "Could not determine kernel version from source directory $kernel_source"
			error "Incorrect path or missing package 'make' or $dev_package for the kernel. Aborting."
			exit 2
		fi
		verbose "Setting kernel version from kernel source directory $kernel_source:"
	else
		if [ "$kernel_version" = '' ]; then
			kernel_version=`uname -r`
		fi

		kernel_source="/lib/modules/$kernel_version/build"
		kernel_version_test=`make -s -C "$kernel_source" M="$PWD" kernelrelease 2>/dev/null || :`
		if [ "$kernel_version_test" = '' ]; then
			error "Could not determine kernel version from source directory $kernel_source"
			error "Incorrect version string or missing $dev_package for kernel $kernel_version. Aborting."
			exit 2
		fi
		verbose "Setting kernel source directory from kernel version $kernel_version:"
	fi
	KSRC="$kernel_source"
	KVERS="$kernel_version"
	KVERS_PACK="${KVERS//[_-]/.}"
	verbose "Using kernel version ${KVERS} from ${KSRC}"
}

# A version string, based basically on the data of
# /etc/os-release . Tweak as needed for useful results
get_distro() {
	(
		. /etc/os-release
		id="$ID:$VERSION_ID"
		case "$ID" in
		uos)
			case "$VERSION_CODENAME" in
			fuyu) id=uos20:1060;; # 1060e
			kongzi) id=uos20:1060a;;
			esac
			;;
		esac
		# for euleros: SP num: echo $VERSION | sed -e 's/.*(SP//' -e 's/[^0-9].*//'
		echo "$id"
	)
}

parse_args() {
	local kernel_source kernel_version

	DISTRO=`get_distro`
	verbose "Running on distribution $DISTRO, architecture $arch."
	options=`getopt -n doca-kernel-support -o dhKk:s:t:v \
		-l dirty,dry-run,help,keep-going,kernel:,kernel-source,tarfile:,verbose -- "$@"`

	kernel_version=${KERNEL_VERSION}
	kernel_source=${KERNEL_SOURCE}
	eval set -- $options
	while [ "$1" != -- ]; do
		case "$1" in
			--dirty) CLEAN_TMPS="0" ;;
			--dry-run|-d) DRY_RUN="1" ;;
			--help|-h) usage; exit 0 ;;
			--keep-going|-K) KEEP_GOING="1" ;;
			--kernel|-k) shift; kernel_version="$1" ;;
			--kernel-source|-s) shift; kernel_source="$1" ;;
			--tarfile|-t) shift; TAR_FILE="$1" ;;
			--verbose|-v) VERBOSE="1" ;;
		esac
		shift
	done
	shift

	set_tar_file
	set_format
	set_kernel_vars "$kernel_source" "$kernel_version"
	REPO_PATH="/usr/share/doca-host-$DOCA_VERSION/Modules/$KVERS"

	set_sudo
	set_dry_run
}

set_rpm_package_install_tool() {
	case "$DISTRO" in
	ol:7* | photon:*) RPM_PACKAGE_INSTALL_TOOL="yum";;
	sles:*) RPM_PACKAGE_INSTALL_TOOL="zypper";;
	esac
}

rpm_install_build_deps() {
	set_rpm_package_install_tool
	local createrepo deps rpm to_install gcc_toolset

	set_kmp
	createrepo="createrepo"
	kernel_elfutils_devel="elfutils-libelf-devel"
	kernel_abi="kernel-abi-stablelists"
	binutils="binutils"
	gcc_toolset=""
	case "$DISTRO" in
	euleros:*)
		kernel_abi=""
		binutils="binutils-extra"
		;;
	alinux:* | ol:7* | ol:8[0-3] | rhel:8.[0-3] | tencentos:*)
		kernel_abi="kernel-abi-whitelists";;
	sles:*)
		kernel_elfutils_devel="libelf-devel"
		createrepo="createrepo_c"
		;;
	ol:8.[7-9] | ol:8.10)
		gcc_toolset="gcc-toolset-11";;
	ol:9.4)
		kernel_abi=""
		;;
	esac
	deps="$createrepo rpm-build gcc autoconf automake libtool $kernel_elfutils_devel $binutils $gcc_toolset"
	# Extra packages for KMP:
	if [ "$KMP" = 1 ]; then
		case "$DISTRO" in
		sles:*) deps="$deps kernel-syms";; # Really?
		*) deps="$deps kernel-rpm-macros $kernel_abi";;
		esac
	fi
	to_install=""
	for rpm in $deps; do
		if ! rpm -q --whatprovides "$rpm" >/dev/null 2>&1; then
			to_install="$to_install $rpm"
		fi
	done
	if [ "$to_install" = '' ]; then
		return
	fi
	info "Installing dependencies missing (for $DISTRO): $to_install"
	$DR $SUDO $RPM_PACKAGE_INSTALL_TOOL install -y $to_install
}

deb_install_build_deps() {
	local deps deb to_install
	deps="build-essential debhelper fakeroot autoconf automake quilt pkgconf apt-utils"
	for deb in $deps; do
		if ! dpkg -l "$deb" 2>/dev/null | grep -q ^.i; then
			to_install="$to_install $deb"
		fi
	done
	if [ "$to_install" = '' ]; then
		return
	fi
	info "Installing dependencies missing (for $DISTRO): $deps"
	$DR $SUDO apt install -y --no-install-recommends $to_install
}


rpm_build() {
	local base_name log_file rc restore_errexit target_dir top_dir gcc_toolset

	base_name="$1"
	top_dir="$BUILD_DIR/$base_name"
	log_file="$LOGS_DIR/$base_name.log"

	gcc_toolset=":"
	case "$DISTRO" in
		ol:8.[7-9]* | ol:8.10*) gcc_toolset="source /opt/rh/gcc-toolset-11/enable";;
	esac

	shift

	vmlinuz_file=/boot/vmlinuz-"$KVERS"
	if [[ ! -f $vmlinuz_file ]]; then
		vmlinuz_file=/boot/vmlinux-"$KVERS"
	fi

	if [[ "$base_name" =~ ^(xpmem|iser|isert|srp)$ ]] && \
	[[ "$KMP" == "1" ]] && \
	! rpm -qf --provides $vmlinuz_file | grep -q 'kernel(PDE_DATA)'; then
		find_requires=("--define" "__find_requires %{nil}")
	else
		find_requires=()
	fi

	if ! is_rpm_built "$base_name"; then
		return
	fi
	rm -rf "$top_dir/RPMS"
	mkdir -p "$LOGS_DIR"
	mkdir -p "$top_dir/BUILD" # knem assumes build dir exists
	info "Building $base_name under $top_dir with log $log_file"
	restore_errexit=`set +o | grep errexit`
	set +e
	(
	exec >$log_file 2>&1
	set -x
	$gcc_toolset; rpmbuild --rebuild --define "_topdir $top_dir" --define "KVERSION $KVERS" --define "_kmp_build_num .1" --define "K_SRC $KSRC" --define "KMP $KMP" "${find_requires[@]}" "$@" SRPMS/$base_name-[0-9]*.src.rpm
	)
	rc=$?
	$restore_errexit # set -e
	if [ "$rc" != 0 ]; then
		error "Build of $base_name rpm failed. See log file $log_file (returned: $rc)."
		if [ "$KEEP_GOING" = "0" -o "$base_name" = "mlnx-ofa_kernel" ]; then
			exit $rc
		fi
		verbose "Failed build but moving to next one because of --keep-going"
		return 0
	fi
	target_dir="$PACKAGES_DIR/$KVERS/$base_name"
	mkdir -p "$target_dir"
	cp -a `find $top_dir/RPMS -name '*.rpm'` "$target_dir/"
}

rpm_export_ofa_dir() {
	local dev_package

	dev_package=`echo $PACKAGES_DIR/$KVERS/mlnx-ofa_kernel/mlnx-ofa_kernel-devel-[0-9]*.rpm`
	rm -rf "$OFA_HEADERS_DIR"
	mkdir -p "$OFA_HEADERS_DIR"
	rpm2cpio "$dev_package" | ( cd "$OFA_HEADERS_DIR"; cpio -idu --quiet)
	export OFA_DIR="$OFA_HEADERS_DIR/usr/src/ofa_kernel/$arch/$KVERS"
}

set_kmp() {
	case "$DISTRO" in
	azurelinux:* | bclinux:* | ctyunos:* | fedora:* | kylin:* | ol:9.4 |\
	mariner:* | openEuler:* | tencentos:* | uos20:* | xenserver:*)
		KMP=0
		verbose "KMP support is disabled for this distro ($DISTRO)."
		;;
	esac
	case "$DISTRO" in
	sles:*)
		if echo "$KVERS" | grep -E -q '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+(\.[0-9]+)*)?-[a-z]+$'
		then :
		else
			KMP=0
			verbose "KMP disabled for a non-default SLES kernel ($DISTRO, $KVERS)."
		fi
		;;
	esac
	# FIXME: a sanity check to compare the kernel version KMP thinks
	# will used to the one we want. Maybe run that test later, when
	# macro packages are installed.
	# Basically:
	#found_ksrc=`rpmbuild --eval "%kernel_module_package -r $KVERS" --eval '%global flavor %flavors_to_build' --eval '%define ksrc %(echo %{kernel_source %flavor})' --eval 'ksrc: %ksrc' | awk '/^ksrc:/ {print $2}'`
	# and compare `realpath $KSRC` to `realpath $found_skrc`
	# Note, however, that on SLES %flavors_to_build may have several
	# items.


}

# Check the generated autoconf files
# Note that it is quite safe to check for them directly, as opposed to
# checking headers or even .config: this is resulting code of the build
# system
check_autofconf() {
	var="$1"

	value=`tac $KSRC/include/generated/autoconf.h 2>/dev/null | grep -m1 $var 2>/dev/null | sed -ne 's/.*\([01]\)$/\1/gp' 2>/dev/null`
	if [ "$value" = '' ]; then
		value="0"
	fi
	echo $value
}

set_configure_options() {
	# Core:
	local options
	options="--with-core-mod --with-user_mad-mod --with-user_access-mod --with-addr_trans-mod"
	# mlx5_fpga_tools:
	options="$options --with-innova-flex"
	CONFIG_XFRM_OFFLOAD=`     check_autofconf CONFIG_XFRM_OFFLOAD`
	CONFIG_INET_ESP_OFFLOAD=` check_autofconf CONFIG_INET_ESP_OFFLOAD`
	CONFIG_INET6_ESP_OFFLOAD=`check_autofconf CONFIG_INET6_ESP_OFFLOAD`
	verbose "CONFIG_XFRM_OFFLOAD=$CONFIG_XFRM_OFFLOAD, CONFIG_INET_ESP_OFFLOAD=$CONFIG_INET_ESP_OFFLOAD, CONFIG_INET6_ESP_OFFLOAD=$CONFIG_INET6_ESP_OFFLOAD."
	if [ "$CONFIG_XFRM_OFFLOAD" = "1" -a "$CONFIG_INET_ESP_OFFLOAD$CONFIG_INET6_ESP_OFFLOAD" != "00" ]; then
		options="$options --with-innova-ipsec"
	fi
	options="$options --with-mlx5-mod"
	options="$options --with-mlxfw-mod"
	if [ "$CONFIG_XFRM_OFFLOAD$CONFIG_INET_ESP_OFFLOAD$CONFIG_INET6_ESP_OFFLOAD" = "111" ]; then
		options="$options --with-mlx5-ipsec"
	fi
	options="$options --with-ipoib-mod"
	options="$options --with-srp-mod"
	options="$options --with-iser-mod"
	options="$options --with-isert-mod"
	options="$options --with-nfsrdma-mod"
	case "$DISTRO" in rhel:* | kylin:* | ubuntu:18.04 | ubuntu:2[024].04)
		verbose "Kernel build option GDS enabled ($DISTRO)."
		options="$options --with-gds"
		;;
	esac
	case "$DISTRO" in euleros:sp8 | euleros:sp1[01])
		verbose "Kernel build option sf-cfg-drv enabled ($DISTRO)."
		options="$options --with-sf-cfg-drv"
		;;
	esac
	verbose "mlnx-ofed-kernel configure options: $options"
	configure_options="$options"
}

rpm_rebuild_modules() {
	local topdir configure_options mft_args mft_orig_rel

	set_configure_options

	mft_args=()
	if [ "$KMP" != 1 ]; then
		mft_orig_rel=`rpm -qp --queryformat '%{release}\n' SRPMS/kernel-mft-*.src.rpm`
		mft_args=('--define' "_release $mft_orig_rel.kver.$KVERS_PACK")
		# FIXME: Do the same to knem and xpmem. Should work, but
		# will also change release number of userspace packages/
	fi
	$DR rpm_build mlnx-ofa_kernel --define "configure_options $configure_options"
	$DR rpm_export_ofa_dir
	$DR rpm_build iser
	$DR rpm_build isert
	$DR rpm_build srp
	$DR rpm_build mlnx-nfsrdma
	$DR rpm_build mlnx-nvme
	$DR rpm_build fwctl
	$DR rpm_build knem --define 'source 1' --define 'debug_package %{nil}'
	$DR rpm_build xpmem --with kernel-only
	$DR rpm_build kernel-mft --define 'source 1' "${mft_args[@]}"
}

# 0: true
# 1: false
is_rpm_built() {
	case "$1" in
	iser)
		CONFIG_ISCSI_TCP=`check_autofconf CONFIG_ISCSI_TCP`
		if [ "$CONFIG_ISCSI_TCP" != 1 ]; then
			return 1
		fi
		case "$DISTRO" in
		rhel:* | almalinux:* | rocky:* | fc:* | sles:* | anolis:* | alinux:*) :;;
		*) return 1;;
		esac
		;;
	isert)
		CONFIG_ISCSI_TARGET=`check_autofconf CONFIG_ISCSI_TARGET`
		if [ "$CONFIG_ISCSI_TARGET" != 1 ]; then
			return 1
		fi
		case "$DISTRO" in
		rhel:* | almalinux:* | rocky:* | fc:* | sles:* | anolis:* | alinux:*) :;;
		*) return 1;;
		esac
		;;
	knem)
		case "$DISTRO" in
		alinux:* | anolis:* | uos20:*) return 1;;
		esac
		;;
	mlnx-nvme)
		case "$DISTRO" in
		azurelinux:* | mariner:*) return 1;;
		esac
		;;
	srp)
		case "$DISTRO" in
		rhel:* | almalinux:* | rocky:* | fc:* | sles:* | anolis:* | alinux:*) :;;
		*) return 1;;
		esac
		case "$arch" in
		ppc*) return 1;;
		esac
		;;
	xpmem)
		case "$DISTRO" in
		kylin:* | ol:8.[78] | ol:8.10 | sles:* | uos20:*) return 1;;
		esac
		case "$arch" in
		ppc*) return 1;;
		esac
		;;
	esac
	return 0
}

deb_export_ofa_dir() {
	local dev_package

	dev_package=`echo $PACKAGES_DIR/$KVERS/mlnx-ofed-kernel/mlnx-ofed-kernel-modules_*.deb`
	rm -rf "$OFA_HEADERS_DIR"
	dpkg-deb -x "$dev_package" "$OFA_HEADERS_DIR"
	export OFA_DIR="$OFA_HEADERS_DIR/usr/src/ofa_kernel/$arch/$KVERS"
}

deb_build() {
	local base_name log_file rc restore_errexit target_dir top_dir

	base_name="$1"
	top_dir="$BUILD_DIR/$base_name"
	log_file="$LOGS_DIR/$base_name.log"

	shift
	if ! is_deb_built "$base_name"; then
		return
	fi
	rm -rf "$top_dir"
	mkdir -p "$top_dir"
	cp SOURCES/${base_name}_*.orig.t* "$top_dir"
	mkdir -p "$LOGS_DIR"
	configure_options=""
	if [ "$base_name" = "mlnx-ofed-kernel" ]; then
		set_configure_options
	fi
	info "Building $base_name under $top_dir with log $log_file"
	restore_errexit=`set +o | grep errexit`
	set +e
	(
	cd "$top_dir"
	tar xf ${base_name}_*.orig.t*
	exec >$log_file 2>&1
	set -x
	cd $PWD/${base_name}-*
	mv -f debian/control.no_dkms debian/control
	echo "configure_options=$configure_options"
	env \
		configure_options="$configure_options" \
		WITH_DKMS="0" \
		kernelver="$KVERS" \
		KVER="$KVERS" \
		kernel_source_dir="$KSRC" \
		K_BUILD="$KSRC" \
		MLNX_KO_NO_STRIP="1" \
		dpkg-buildpackage -uc -us -Pmodules
	)
	rc=$?
	$restore_errexit
	if [ "$rc" != 0 ]; then
		error "Build of $base_name deb failed. See log file $log_file"
		if [ "$KEEP_GOING" = "0" -o "$base_name" = "mlnx-ofed-kernel" ]; then
			exit $rc
		fi
		verbose "Failed build but moving to next one because of --keep-going"
		return 0
	fi
	target_dir="$PACKAGES_DIR/$KVERS/$base_name"
	mkdir -p "$target_dir"
	cp -a $top_dir/*.deb "$target_dir/" # FIXME: .changes? buildinfo?
}

# 0: true
# 1: false
is_deb_built() {
	case "$1" in
	iser)
		CONFIG_ISCSI_TCP=`check_autofconf CONFIG_ISCSI_TCP`
		if [ "$CONFIG_ISCSI_TCP" != 1 ]; then
			return 1
		fi
		;;
	isert)
		CONFIG_ISCSI_TARGET=`check_autofconf CONFIG_ISCSI_TARGET`
		if [ "$CONFIG_ISCSI_TARGET" != 1 ]; then
			return 1
		fi
		;;
	xpmem)
		case "$DISTRO" in
		debian:*) return 1;;
		esac
		case "$arch" in
		ppc*) return 1;;
		esac
		;;
	srp)
		case "$arch" in
		ppc*) return 1;;
		esac
		;;
	mlnx-nfsrdma)
		case "$DISTRO" in debian:12) return 1;; esac
		case "$arch" in
		aarch64)
			case "$DISTRO" in
			ubuntu:22.04 | ubuntu:24.04) return 0;;
			*) return 1;;
			esac
			;;
		x86_64)
			case "$KVERS" in
			5.4.0-* | 5.13.0-* | \
			5.1[357-9].* | 6.[0-8].*) return 0;;
			*) return 1;;
			esac
			;;
		*) return 1;;
		esac
	esac
	return 0
}

deb_rebuild_modules() {
	local topdir configure_options

	$DR deb_build mlnx-ofed-kernel
	$DR deb_export_ofa_dir
	$DR deb_build iser
	$DR deb_build isert
	$DR deb_build srp
	$DR deb_build mlnx-nfsrdma
	$DR deb_build mlnx-nvme
	$DR deb_build fwctl
	$DR deb_build knem --define 'source 1' --define 'debug_package %{nil}'
	$DR deb_build xpmem
	$DR deb_build kernel-mft
}

rebuild_modules() {
	${FORMAT}_install_build_deps
	info "Rebuilding kernel modules"
	${FORMAT}_rebuild_modules
}

# It seems only rhel7-grade does not support suggests.
# So likely only ol:7* should reject. But for now let's start
# small
distro_supports_suggests() {
	case "$DISTRO" in
	rhel:[891]* | ol:[89]*) return 0;;
	esac
	return 1
}

rpm_create_metapackage() {
	local dep deps excluded excluded_suggests excluded_text package_name repo_dir rpm rpm_name spec_file top_dir

	info "Creating a rpm meta package:"
	repo_dir="$1"
	package_name="doca-kernel-$KVERS_PACK"
	top_dir="$BUILD_DIR/$package_name"
	excluded=()
	excluded_text=""
	deps=()
	for rpm in "$repo_dir/"*.rpm; do
		rpm_name="${rpm##*/}"
		case "$rpm_name" in
		*debug* | \
		knem-[0-9]* | xpmem-[0-9]*)
			continue;;
		*-nvme* | *-nfsrdma* | *fwctl-*)
			excluded+=(`rpm -qp "$rpm"`)
			continue
			;;
		esac
		dep=`rpm -qp --queryformat="%{name} >= %{version}-%{release}" "$rpm"`
		deps+=("$dep")
	done
	excluded_suggests=""
	if [ "$excluded" != '' ]; then
		excluded_text="Extra module packages in repository but not installed by this package:"
		if distro_supports_suggests; then
			excluded_suggests="Suggests: ${excluded[*]}"
		fi
	fi

	mkdir -p "$top_dir/SPECS"
	spec_file="$top_dir/SPECS/$package_name.spec"
	cat <<EOF >"$spec_file"
Name:       $package_name
Version:    $DOCA_VERSION_PACK
Release:    1.kver.$KVERS_PACK
Summary:    %{name} kernel modules for $KVERS
BuildArch:  noarch
$excluded_suggests

Group:      Kernel
License:    GPL2
EOF
	for dep in "${deps[@]}"; do
		echo "Requires: $dep" >>"$spec_file"
	done
	cat <<EOF >>"$spec_file"

%description
A meta-package that installs DOCA-host modules rebuilt for kernel $KVERS.

$excluded_text
${excluded[*]}

%prep

%build

%install

%files

%changelog
EOF
	log_file="$LOGS_DIR/$package_name.log"
	verbose "Building rpm meta package under $top_dir with $log_file"
	if ! rpmbuild -bb --define "_topdir $top_dir" "$spec_file" >"$log_file" 2>&1
	then
		error "Failed building rpm meta package. See log $log_file"
		exit 3
	fi
	cp $top_dir/RPMS/noarch/doca-kernel*.rpm "$repo_dir/"
}

gen_rules_tiny() {
	local rules_file
	rules_file="$1"
	cat <<EOF >"$rules_file"
#!/usr/bin/make -f
%:
	dh \$@
EOF
	chmod +x "$rules_file"
}

deb_create_metapackage() {
	local base_dir deb deb_name debian_dir dep deps log_file package_dir repo_dir top_dir package_name

	repo_dir="$1"
	package_name="doca-kernel-$KVERS_PACK"
	base_dir="$BUILD_DIR/$package_name"
	package_dir="$base_dir/$package_name"
	debian_dir="$package_dir/debian"
	info "Building a deb meta package under $base_dir with log $LOGS_DIR/$package_name.build.log"
	excluded=()
	excluded_text=""
	deps=()
	for deb in "$repo_dir/"*-modules_*.deb; do
		deb_name="${deb##*/}"
		case "$deb_name" in
		*-nvme-* | *-nfsrdma-* | *fwctl-*)
			excluded+=(${deb_name%%_*})
			continue
			;;
		esac
		dep=`echo "$deb_name" | sed -r -e 's/([^_]*)_([^_]*)_.*/\1 (>= \2),/'`
		deps+=("$dep")
	done
	excluded_text=`echo ${excluded[*]} | sed -e 's/ /, /g'`

	rm -rf "$base_dir"
	mkdir -p "$debian_dir/source"
	# FIXME: using the same function as in for the single package?
	deb_gen_copyright > "$debian_dir/copyright"
	echo "3.0 (native)" > "$debian_dir/source/format"
	gen_rules_tiny "$debian_dir/rules"
	cat <<EOF >"$debian_dir/changelog"
$package_name ($DOCA_VERSION_PACK) unstable; urgency=medium

  * Rebuilt modules with kernel $KVERS

 -- $MAINTAINER  `date -R`
EOF
	cat <<EOF >"$debian_dir/control"
Source: $package_name
Priority: optional
Section: net
Maintainer: $MAINTAINER
Build-Depends: debhelper-compat (= 10),
Standards-Version: 4.6.0
Rules-Requires-Root: no

Package: $package_name
Architecture: any
Depends: \${misc:Depends}, ${deps[*]}
Suggests: $excluded_text
Description: DOCA-host kernel modules, rebuilt for $KVERS
 This package is a meta-packages (empty package with dependencies)
 that pulls the locally-built kernel modules of DOCA-host for
 kernel $KVERS.
 .
 Other modules packages built but not installed by this package: ${excluded_text}.
EOF
	log_file="$LOGS_DIR/$package_name.build.log"
	if ! (cd "$package_dir"; dpkg-buildpackage -uc -us) >"$log_file" 2>&1;
	then
		error "Build of deb meta package failed. See log file $log_file"
		exit 3
	fi
	cp $base_dir/$PACKAGE_NAME_*.deb "$repo_dir/"
}


rpm_create_repo() {
	info "Creating a package repository in $1"
	createrepo_options=""
	case "$DISTRO" in
	azurelinux:*)
		 createrepo_options="--compatibility"
		 ;;
	esac
	createrepo $createrepo_options -q "$1"
}

release_file_header() {
	# FIXME: This is a place for handy meta-data, but we should keep
	# it consistent:
	cat <<EOF
Origin: Nvidia
Label: DOCA-HOST
Suite: DOCA-HOST-$DOCA_VERSION
Codename: Something
Version: $KVERS
Description: DOCA-HOST $DOCA_VERSION kernel modules for $KVERS
EOF
}

deb_create_repo() {
	local dir
	dir="$1"

	info "Creating repo in $dir"
	(
		cd $dir
		apt-ftparchive packages . > Packages
		apt-ftparchive contents . > Contents
		(
			release_file_header
			apt-ftparchive release .
		) > Release
	)
}

rpm_create_single_package() {
	local log_file packages_tree top_dir package package_name

	verbose "Creating a single output rpm package"
	top_dir="$BUILD_DIR/$PACKAGE_NAME"
	packages_tree="`realpath $1`"

	mkdir -p "$top_dir/SOURCES"
	cat <<EOF >"$top_dir/SOURCES/doca-kernel-$KVERS.repo"
[doca-kernel-${KVERS//+/--}]
name=DOCA kernel (kernel $KVERS)
baseurl=file://$REPO_PATH
enabled=1
gpgcheck=0
EOF

	mkdir -p "$top_dir/SPECS"
	cat <<EOF >"$top_dir/SPECS/$PACKAGE_NAME.spec"
Name:       $PACKAGE_NAME
Version:    $DOCA_VERSION_PACK
Release:    1.kver.$KVERS_PACK
Summary:    %{name} single repository package
Source0:    doca-kernel-$KVERS.repo

Group:      Kernel
License:    GPL2

%if "%{_vendor}" == "suse"
%global repo_dir %{_sysconfdir}/zypp/repos.d
%else
%global repo_dir %{_sysconfdir}/yum.repos.d
%endif

%description
A repository of rebuilt kernel modules from DOCA $DOCA_VERSION for kernel
$KVERS .

A local repository installed under $REPO_PATH and added as a yum/dnf/zypper
repository called 'doca-kernel-${KVERS}'

%prep

%build

%install
cp -a "$packages_tree"/* %{buildroot}/
mkdir -p %{buildroot}%{repo_dir}
cp %{SOURCE0} %{buildroot}%{repo_dir}

%files
%{repo_dir}/*.repo
$REPO_PATH

%changelog

EOF
	log_file="$LOGS_DIR/$PACKAGE_NAME.log"
	verbose "Building a single package under $top_dir with $log_file"
	if ! rpmbuild -bb --define "_topdir $top_dir" "$top_dir/SPECS/$PACKAGE_NAME.spec" >"$log_file" 2>&1
	then
		error "Failed building single rpm package. See log $log_file"
		exit 3
	fi
	package=`echo $top_dir/RPMS/*/*.rpm`
	package_name="${package##*/}"
	cp "$package" "$TOP_DIR/"
	FINAL_REPO_FILE="$TOP_DIR/$package_name"
	info "Built single package: $FINAL_REPO_FILE"
}

deb_gen_copyright() {
	cat <<EOF
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: $PACKAGE_NAME
Upstream-Contact: FIXME
Source: FIXME

Files: *
Copyright: 2024, Nvidia Inc
License: FIXME
EOF
}

deb_create_single_package() {
	local base_dir debian_dir packages_tree package package_dir package_name

	verbose "Creating a single output deb package"
	base_dir="$BUILD_DIR/$PACKAGE_NAME"
	package_dir="$base_dir/$PACKAGE_NAME"
	packages_tree="`realpath $1`"
	debian_dir="$package_dir/debian"
	# Create a local var for KVERS_PACK to replace + by --
	modified_kvers_pack="${KVERS_PACK//+/--}"
	rm -rf "$base_dir"
	mkdir -p "$package_dir/tree"
	cp -a $packages_tree/* "$package_dir/tree/"
	mkdir -p "$debian_dir/source"
	echo "tree/usr/* usr" > "$debian_dir/install"
	echo "deb [ trusted=yes ] file://$REPO_PATH ./" \
		>"$debian_dir/doca-kernel-$modified_kvers_pack.list"
	echo "debian/doca-kernel-$modified_kvers_pack.list etc/apt/sources.list.d" >> \
		"$debian_dir/install"
	deb_gen_copyright > "$debian_dir/copyright"
	echo "3.0 (native)" > "$debian_dir/source/format"
	gen_rules_tiny "$debian_dir/rules"
	cat <<EOF >"$debian_dir/changelog"
$PACKAGE_NAME ($DOCA_VERSION_PACK) unstable; urgency=medium

  * Rebuilt modules with kernel $KVERS

 -- $MAINTAINER  `date -R`
EOF
	cat <<EOF >"$debian_dir/control"
Source: $PACKAGE_NAME
Priority: optional
Section: net
Maintainer: $MAINTAINER
Build-Depends: debhelper-compat (= 10),
Standards-Version: 4.6.0
Rules-Requires-Root: no

Package: $PACKAGE_NAME-$DOCA_VERSION-$KVERS_PACK
Architecture: any
Depends: \${misc:Depends}
Description: rebuilt debs of DOCA-HOST $DOCA_VERSION for kernel $KVERS
 This package includes, under $REPO_PATH, packages of rebuilt kernel
 modules for DOCA-HOST.
 .
 This package also configures this repository, see
 /etc/apt/sources.list.d/doca-kernel-$modified_kvers_pack.list .
EOF
	log_file="$LOGS_DIR/$PACKAGE_NAME.build.log"
	if ! (cd "$package_dir"; dpkg-buildpackage -uc -us) >"$log_file" 2>&1;
	then
		error "Build of single deb failed. See log file $log_file"
		exit 3
	fi
	package=`echo $base_dir/$PACKAGE_NAME_*.deb`
	package_name="${package##*/}"
	cp "$package" "$TOP_DIR/"
	FINAL_REPO_FILE="$TOP_DIR/$package_name"
	info "Built single package: $FINAL_REPO_FILE"
}

create_local_repo() {
	local packages subdir

	packages=`echo $PACKAGES_DIR/$KVERS/*/*.$FORMAT`
	subdir="$REPO_DIR$REPO_PATH"
	rm -rf "$REPO_DIR"
	mkdir -p "$subdir"
	cp -a $packages "$subdir/"
	${FORMAT}_create_metapackage "$subdir"
	${FORMAT}_create_repo "$subdir"
	${FORMAT}_create_single_package "$REPO_DIR"
}

create_tmp_folder() {
	TOP_DIR=$(mktemp -d /tmp/DOCA.XXXXXXXXXX)
	chmod go+rx "$TOP_DIR"
	BUILD_DIR="$TOP_DIR/build"
	LOGS_DIR="$TOP_DIR/logs"
	OFED_DIR="$TOP_DIR/ofed"
	PACKAGES_DIR="$TOP_DIR/packages"
	REPO_DIR="$TOP_DIR/repo"
	OFA_HEADERS_DIR="$TOP_DIR/ofa_headers"

	trap cleanup 0

	info "Building under $TOP_DIR"
}

extract_tarball() {
	if [ "$DRY_RUN" != 0 ]; then
		return
	fi
	mkdir -p "$OFED_DIR"
	cd "$OFED_DIR"
	tar xf "$TAR_FILE"
	cd *
	verbose "Extracted tarball $tar_file into $PWD"
}

cleanup() {
	# Sanity check, just in case cleanup is moved earlier:
	if [ "$TOP_DIR" = '' ]; then
		return
	fi
	if [ "$CLEAN_TMPS" = 0 ]; then
		return
	fi
	# Output would be in the top level directory. also leave logs
	rm -rf "$BUILD_DIR" "$OFED_DIR" "$PACKAGES_DIR" "$REPO_DIR" "$OFA_HEADERS_DIR"
}

display_rpm_package_manager_update_command() {
    case "$RPM_PACKAGE_INSTALL_TOOL" in
        zypper)
            echo "  zypper refresh"
            ;;
        yum|dnf)
            echo "  $RPM_PACKAGE_INSTALL_TOOL makecache"
            ;;
        *)
            echo "  Unsupported package manager: $RPM_PACKAGE_INSTALL_TOOL"
            ;;
    esac

    echo ""
}

rpm_final_message() {
	cat <<EOF
Now you should install the generated single repository package to make its
files available:

  rpm -ivh $FINAL_REPO_FILE

After installing the package, doca-kernel-$KVERS_PACK metapackage should be available.

Next steps could probably be:
EOF

        display_rpm_package_manager_update_command

	cat << EOF
Then, install ofed packages (e.g. doca-all, doca-ofed).
EOF
	if [ "$KMP" != 1 ]; then
		cat <<EOF
The above is likely to install the newely-built drivers. In case it
would not, the sugested workaround would be to install a userspace-only
package and the new kernel metapackage. e.g.:

	doca-ofed-userspace doca-kernel-$KVERS_PACK
EOF
	fi
}

deb_final_message() {
	cat <<EOF
Now you should install the generated single repository package to make its
files available:

  dpkg --install $FINAL_REPO_FILE

Next steps could probably be:

  apt update
  apt install doca-ofed-userspace doca-kernel-$KVERS_PACK

You can install any of the other userspace packages (doca-all-userspace, doca-all-networking)
EOF
}

print_final_message() {
	info "Done"
	${FORMAT}_final_message
}

main() {
	parse_args "$@"

	create_tmp_folder tmp_folder
	extract_tarball

	rebuild_modules
	if [ "$DRY_RUN" != 0 ]; then
		verbose "Exit now because of a dry-run"
		exit 0
	fi
	create_local_repo
	print_final_message
}

main "$@"

# vi:syntax=sh:ts=8:sts=8:sw=8:noexpandtab
