# Copyright 2024-2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

EAPI=8

# supports ROCM/HIP >=5.5, but we define 6.1 due to the eclass
ROCM_VERSION="6.1"
inherit cuda rocm
inherit cmake
inherit flag-o-matic go-module linux-info systemd toolchain-funcs

DESCRIPTION="Get up and running with Llama 3, Mistral, Gemma, and other language models"
HOMEPAGE="https://ollama.com"

if [[ ${PV} == *9999* ]]; then
	inherit git-r3
	EGIT_REPO_URI="https://github.com/ollama/ollama.git"
else
	SRC_URI="
		https://github.com/ollama/${PN}/archive/refs/tags/v${PV}.tar.gz -> ${P}.gh.tar.gz
		https://github.com/gentoo-golang-dist/${PN}/releases/download/v${PV}/${P}-deps.tar.xz
	"
	KEYWORDS="~amd64"
fi

LICENSE="MIT"
SLOT="0"

X86_CPU_FLAGS=(
	sse4_2
	avx
	f16c
	avx2
	bmi2
	fma3
	avx512f
	avx512vbmi
	avx512_vnni
	avx_vnni
)
CPU_FLAGS=( "${X86_CPU_FLAGS[@]/#/cpu_flags_x86_}" )
CUDA_FLAGS=(
	cuda_sm_75
	cuda_sm_80
	cuda_sm_86
	cuda_sm_87
	cuda_sm_89
	cuda_sm_90
	cuda_sm_100
	cuda_sm_103
	cuda_sm_110
	cuda_sm_120
	cuda_sm_121
)
IUSE="blas ${CPU_FLAGS[*]} cuda ${CUDA_FLAGS[*]} mkl rocm vulkan"
REQUIRED_USE="cuda? ( || ( ${CUDA_FLAGS[*]} ) )"

RESTRICT="mirror test"

COMMON_DEPEND="
	blas? (
		!mkl? (
			virtual/blas
		)
		mkl? (
			sci-libs/mkl[llvm-openmp]
		)
	)
	cuda? (
		dev-util/nvidia-cuda-toolkit:=
	)
	rocm? (
		>=dev-util/hip-${ROCM_VERSION}:=
		>=sci-libs/hipBLAS-${ROCM_VERSION}:=
		>=sci-libs/rocBLAS-${ROCM_VERSION}:=
	)
"

DEPEND="
	${COMMON_DEPEND}
	>=dev-lang/go-1.24.1
"
BDEPEND="
	vulkan? (
		dev-util/vulkan-headers
		media-libs/shaderc
	)
"

RDEPEND="
	${COMMON_DEPEND}
	acct-group/${PN}
	>=acct-user/${PN}-3[cuda?]
"

pkg_pretend() {
	if use amd64; then
		if use cpu_flags_x86_f16c && use cpu_flags_x86_avx2 && use cpu_flags_x86_fma3 && ! use cpu_flags_x86_bmi2; then
			ewarn
			ewarn "CPU_FLAGS_X86: bmi2 not enabled."
			ewarn "  Not building haswell runner."
			ewarn "  Not building skylakex runner."
			ewarn "  Not building icelake runner."
			ewarn "  Not building alderlake runner."
			ewarn
			if grep bmi2 /proc/cpuinfo > /dev/null; then
				ewarn "bmi2 found in /proc/cpuinfo"
				ewarn
			fi
		fi
	fi
}

pkg_setup() {
	if use rocm; then
		linux-info_pkg_setup
		if linux-info_get_any_version && linux_config_exists; then
			if ! linux_chkconfig_present HSA_AMD_SVM; then
				ewarn "To use ROCm/HIP, you need to have HSA_AMD_SVM option enabled in your kernel."
			fi
		fi
	fi
}

src_unpack() {
	# Filter LTO flags for ROCM (bug 963401)
	if use rocm; then
		strip-unsupported-flags
		export CXXFLAGS="$(test-flags-HIPCXX "${CXXFLAGS}")"
	fi

	if [[ "${PV}" == *9999* ]]; then
		git-r3_src_unpack
		go-module_live_vendor
	else
		go-module_src_unpack
	fi
}

src_prepare() {
	cmake_src_prepare

	# Disable ccache and bundled header regexes
	sed \
		-e "/set(GGML_CCACHE/s/ON/OFF/g" \
		-e "/PRE_INCLUDE_REGEXES.*cu/d" \
		-e "/PRE_INCLUDE_REGEXES.*hip/d" \
		-i CMakeLists.txt || die "bundle headers sed failed"

	# Remove hardcoded -O3 from Go CGO flags
	sed \
		-e "s/ -O3//g" \
		-i ml/backend/ggml/ggml/src/ggml-cpu/cpu.go \
		|| die "-O3 sed failed"

	# Fix library location for multilib
	sed \
		-e "s/\"..\", \"lib\"/\"..\", \"$(get_libdir)\"/" \
		-e "s#\"lib/ollama\"#\"$(get_libdir)/ollama\"#" \
		-i \
			ml/backend/ggml/ggml/src/ggml.go \
			ml/path.go \
		|| die "libdir sed failed"

	# Fix CMakeLists.txt lib path for multilib
	sed \
		-e "s#lib/ollama#$(get_libdir)/ollama#g" \
		-i CMakeLists.txt || die "cmake libdir sed failed"

	# Disable CPU backend variants based on CPU_FLAGS_X86
	if use amd64; then
		if ! use cpu_flags_x86_sse4_2; then
			sed -e "/ggml_add_cpu_backend_variant(sse42/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
		if ! use cpu_flags_x86_sse4_2 || ! use cpu_flags_x86_avx; then
			sed -e "/ggml_add_cpu_backend_variant(sandybridge/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
		if ! use cpu_flags_x86_sse4_2 || ! use cpu_flags_x86_avx ||
			! use cpu_flags_x86_f16c || ! use cpu_flags_x86_avx2 ||
			! use cpu_flags_x86_bmi2 || ! use cpu_flags_x86_fma3; then
			sed -e "/ggml_add_cpu_backend_variant(haswell/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
		if ! use cpu_flags_x86_sse4_2 || ! use cpu_flags_x86_avx ||
			! use cpu_flags_x86_f16c || ! use cpu_flags_x86_avx2 ||
			! use cpu_flags_x86_bmi2 || ! use cpu_flags_x86_fma3 ||
			! use cpu_flags_x86_avx512f; then
			sed -e "/ggml_add_cpu_backend_variant(skylakex/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
		if ! use cpu_flags_x86_sse4_2 || ! use cpu_flags_x86_avx ||
			! use cpu_flags_x86_f16c || ! use cpu_flags_x86_avx2 ||
			! use cpu_flags_x86_bmi2 || ! use cpu_flags_x86_fma3 ||
			! use cpu_flags_x86_avx512f || ! use cpu_flags_x86_avx512vbmi ||
			! use cpu_flags_x86_avx512_vnni; then
			sed -e "/ggml_add_cpu_backend_variant(icelake/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
		if ! use cpu_flags_x86_sse4_2 || ! use cpu_flags_x86_avx ||
			! use cpu_flags_x86_f16c || ! use cpu_flags_x86_avx2 ||
			! use cpu_flags_x86_bmi2 || ! use cpu_flags_x86_fma3 ||
			! use cpu_flags_x86_avx_vnni; then
			sed -e "/ggml_add_cpu_backend_variant(alderlake/s/^/# /g" \
				-i ml/backend/ggml/ggml/src/CMakeLists.txt || die
		fi
	fi

	if use cuda; then
		cuda_src_prepare
	fi

	if use rocm; then
		# --hip-version gets appended to compile flags which isn't a known flag.
		# Disable -Werror's from go modules to fix rocm builds.
		find "${S}" -name "*.go" -exec sed -i "s/ -Werror / /g" {} + || die
	fi
}

src_configure() {
	local mycmakeargs=(
		-DGGML_CCACHE="no"

		# Dynamic backend loading
		-DGGML_BACKEND_DL="yes"
		-DGGML_BACKEND_DIR="${EPREFIX}/usr/$(get_libdir)/${PN}"

		-DGGML_BLAS="$(usex blas)"

		"$(cmake_use_find_package vulkan Vulkan)"
	)

	if use blas; then
		if use mkl; then
			mycmakeargs+=(
				-DGGML_BLAS_VENDOR="Intel10_64lp"
			)
		else
			mycmakeargs+=(
				-DGGML_BLAS_VENDOR="Generic"
			)
		fi
	fi

	if use cuda; then
		local -x CUDAHOSTCXX CUDAHOSTLD
		CUDAHOSTCXX="$(cuda_gccdir)"
		CUDAHOSTLD="$(tc-getCXX)"

		# Compile only for selected GPU architectures
		local cuda_arches=()
		local cuda_arch
		for cuda_arch in "${CUDA_FLAGS[@]}"; do
			if use "${cuda_arch}" && [[ "${cuda_arch}" =~ cuda_sm_([0-9]+) ]]; then
				cuda_arches+=("${BASH_REMATCH[1]}")
			fi
		done
		local enabled_cuda_arches
		enabled_cuda_arches=$(IFS=';'; echo "${cuda_arches[*]}")

		mycmakeargs+=(
			-DCMAKE_CUDA_ARCHITECTURES="${enabled_cuda_arches}"
		)

		cuda_add_sandbox -w
		addpredict "/dev/char/"
	else
		mycmakeargs+=(
			-DCMAKE_CUDA_COMPILER="NOTFOUND"
		)
	fi

	if use rocm; then
		mycmakeargs+=(
			-DCMAKE_HIP_ARCHITECTURES="$(get_amdgpu_flags)"
			-DCMAKE_HIP_PLATFORM="amd"
			-DAMDGPU_TARGETS="$(get_amdgpu_flags)"
		)

		local -x HIP_PATH="${ESYSROOT}/usr"
	else
		mycmakeargs+=(
			-DCMAKE_HIP_COMPILER="NOTFOUND"
		)
	fi

	cmake_src_configure
}

src_compile() {
	local VERSION
	if [[ "${PV}" == *9999* ]]; then
		VERSION="$(
			git describe --tags --first-parent --abbrev=7 --long --dirty --always \
			| sed -e "s/^v//g"
		)"
	else
		VERSION="${PVR}"
	fi
	local EXTRA_GOFLAGS_LD=(
		"-X=github.com/ollama/ollama/version.Version=${VERSION}"
		"-X=github.com/ollama/ollama/server.mode=release"
	)
	GOFLAGS+=" '-ldflags=${EXTRA_GOFLAGS_LD[*]}'"

	ego build

	cmake_src_compile
}

src_install() {
	dobin ollama

	cmake_src_install

	newinitd "${FILESDIR}/ollama.init" "${PN}"
	newconfd "${FILESDIR}/ollama.confd" "${PN}"

	systemd_dounit "${FILESDIR}/ollama.service"
}

pkg_preinst() {
	keepdir /var/log/ollama
	fperms 750 /var/log/ollama
	fowners "${PN}:${PN}" /var/log/ollama
}

pkg_postinst() {
	if [[ -z ${REPLACING_VERSIONS} ]]; then
		einfo "Quick guide:"
		einfo "\tollama serve"
		einfo "\tollama run llama3:70b"
		einfo
		einfo "See available models at https://ollama.com/library"
	fi

	if use cuda; then
		einfo "When using cuda the user running ${PN} has to be in the video group."
		einfo "The ebuild ensures this for user ${PN} via acct-user/${PN}[cuda]"
	fi
}