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

EAPI=8

inherit cmake

MY_PN="whisper.cpp"
MY_P="${MY_PN}-${PV}"

DESCRIPTION="Port of OpenAI's Whisper model in C/C++"
HOMEPAGE="https://github.com/ggml-org/whisper.cpp"
SRC_URI="https://github.com/ggml-org/whisper.cpp/archive/refs/tags/v${PV}.tar.gz -> ${MY_P}.tar.gz"

S="${WORKDIR}/${MY_P}"

LICENSE="MIT"
SLOT="0"
KEYWORDS="~amd64 ~arm64"

X86_CPU_FLAGS=(
	sse4_2
	avx
	avx_vnni
	avx2
	bmi2
	avx512f avx512cd avx512vl avx512dq avx512bw
	avx512vbmi
	avx512_vnni
	avx512_bf16
	fma3
	f16c
	amx_tile
	amx_int8
	amx_bf16
)
CPU_FLAGS=( "${X86_CPU_FLAGS[@]/#/cpu_flags_x86_}" )

# ggml exposes no per-feature -DGGML_* option on ARM.  The only lever is
# GGML_CPU_ARM_ARCH, an accumulated -march= string, so these flags are gated at
# compile time via -march tags rather than by individual cmake options.
# ggml SME (armv9.2-a) is deliberately not exposed: it has no cpu_flags_arm_*
# value in profiles/desc/cpu_flags_arm.desc, and forcing armv9.2-a would exclude
# every pre-Armv9 core.  Users who want it set GGML_CPU_ARM_ARCH themselves.
ARM_CPU_FLAGS=( asimddp asimdhp sve i8mm sve2 )
CPU_FLAGS+=( "${ARM_CPU_FLAGS[@]/#/cpu_flags_arm_}" )

IUSE="blas cuda ffmpeg opencl hip sdl2 vulkan ${CPU_FLAGS[*]}"

# The ROCm stack behind USE=rocm (sci-libs/hipBLAS and the dev-util/hip and
# sci-libs/rocBLAS packages it pulls) is ~amd64-only, so arm64? ( !hip ) makes
# the combination unselectable instead of leaving arm64 users with an
# unsatisfiable dependency.  ::gentoo expresses the same thing for sci-ml/ggml
# through profiles/arch/arm64/package.use.mask, which an overlay cannot reach:
# the profile in use comes from ::gentoo and does not inherit ours.
#
# sci-ml/llama-cpp carries the equivalent constraint and still trips
# NonsolvableDeps{InDev,InStable} on arm64 profiles, because pkgcheck's solver
# gives up on its hip? ( ${ROCM_REQUIRED_USE} ) branch.  The simpler constraint
# below is honoured, so this package scans clean; should that ever regress to a
# NonsolvableDeps report it is benign for the same reason it is benign there.
REQUIRED_USE="
	arm64? ( !hip )
	cpu_flags_arm_sve2? ( cpu_flags_arm_sve )
"

CDEPEND="
	blas? ( sci-libs/openblas )
	cuda? ( dev-util/nvidia-cuda-toolkit:= )
	ffmpeg? ( media-video/ffmpeg:= )
	hip? ( sci-libs/hipBLAS:= )
	sdl2? ( media-libs/libsdl2:= )
"
DEPEND="${CDEPEND}
	opencl? ( dev-util/opencl-headers )
	vulkan? (
		dev-util/spirv-headers
		dev-util/vulkan-headers
	)
"
RDEPEND="${CDEPEND}
	opencl? ( dev-libs/opencl-icd-loader )
	vulkan? ( media-libs/vulkan-loader )
"
BDEPEND="vulkan? ( media-libs/shaderc )"

src_configure() {
	# Every ggml artifact has to stay out of the default libdir: sci-ml/llama-cpp
	# vendors the same ggml tree and already owns libggml.so, libggml-base.so,
	# the per-backend libggml-*.so, lib/cmake/ggml/ and pkgconfig/ggml.pc there.
	# whisper.cpp 1.9.x adds libparakeet.so next to libwhisper.so, and both of
	# those key off CMAKE_INSTALL_LIBDIR too, along with their cmake/ and
	# pkgconfig/ files.  Relocating LIBDIR moves the whole set in one shot;
	# RPATH is then required so the /usr/bin/whisper-* binaries still resolve.
	local whisperdir="${EPREFIX}/usr/$(get_libdir)/${MY_PN}"

	local mycmakeargs=(
		-DCMAKE_INSTALL_LIBDIR="${whisperdir}"
		-DCMAKE_INSTALL_RPATH="${whisperdir}"

		-DWHISPER_BUILD_EXAMPLES=ON
		-DWHISPER_BUILD_SERVER=ON
		-DWHISPER_BUILD_TESTS=OFF
		-DGGML_CCACHE=OFF

		-DGGML_BLAS="$(usex blas)"
		-DGGML_CUDA="$(usex cuda)"
		-DGGML_HIP="$(usex hip)"
		-DGGML_OPENCL="$(usex opencl)"
		-DGGML_VULKAN="$(usex vulkan)"
		-DWHISPER_COMMON_FFMPEG="$(usex ffmpeg)"
		-DWHISPER_SDL2="$(usex sdl2)"

		# GGML_NATIVE=OFF alone is not enough: ggml sets INS_ENB=ON whenever
		# GGML_NATIVE is off but GGML_NATIVE_DEFAULT is on, which turns
		# GGML_SSE42/AVX/AVX2/BMI2/FMA/F16C on unconditionally and produces
		# binaries that SIGILL on any host without AVX2.  Every instruction-set
		# option is therefore pinned to the corresponding CPU_FLAGS_X86 value.
		-DGGML_NATIVE=OFF
		-DGGML_SSE42="$(usex cpu_flags_x86_sse4_2)"
		-DGGML_AVX="$(usex cpu_flags_x86_avx)"
		-DGGML_AVX_VNNI="$(usex cpu_flags_x86_avx_vnni)"
		-DGGML_AVX2="$(usex cpu_flags_x86_avx2)"
		-DGGML_BMI2="$(usex cpu_flags_x86_bmi2)"
		-DGGML_AVX512_VBMI="$(usex cpu_flags_x86_avx512vbmi)"
		-DGGML_AVX512_VNNI="$(usex cpu_flags_x86_avx512_vnni)"
		-DGGML_AVX512_BF16="$(usex cpu_flags_x86_avx512_bf16)"
		-DGGML_FMA="$(usex cpu_flags_x86_fma3)"
		-DGGML_F16C="$(usex cpu_flags_x86_f16c)"
		-DGGML_AMX_TILE="$(usex cpu_flags_x86_amx_tile)"
		-DGGML_AMX_INT8="$(usex cpu_flags_x86_amx_int8)"
		-DGGML_AMX_BF16="$(usex cpu_flags_x86_amx_bf16)"
	)

	if use cpu_flags_x86_avx512f &&
		use cpu_flags_x86_avx512cd &&
		use cpu_flags_x86_avx512vl &&
		use cpu_flags_x86_avx512dq &&
		use cpu_flags_x86_avx512bw; then
		mycmakeargs+=( -DGGML_AVX512=ON )
	else
		mycmakeargs+=( -DGGML_AVX512=OFF )
	fi

	if use arm64; then
		# Mirrors upstream's escalation table in ggml/src/ggml-cpu/CMakeLists.txt:
		# each feature bumps the baseline to the first architecture level that
		# supported it, and the tags accumulate.  Order is load-bearing —
		# last write wins, so the highest required level ends up in arm_arch.
		# Setting GGML_CPU_ARM_ARCH bypasses that table entirely (it is the
		# first branch of an if/elseif), which is why it is replicated here.
		local arm_arch="armv8-a" arm_tags=""
		if use cpu_flags_arm_asimddp; then arm_arch="armv8.2-a"; arm_tags+="+dotprod"; fi
		if use cpu_flags_arm_asimdhp; then arm_arch="armv8.2-a"; arm_tags+="+fp16";    fi
		if use cpu_flags_arm_sve;     then arm_arch="armv8.2-a"; arm_tags+="+sve";     fi
		if use cpu_flags_arm_i8mm;    then arm_arch="armv8.6-a"; arm_tags+="+i8mm";    fi
		if use cpu_flags_arm_sve2;    then arm_arch="armv8.6-a"; arm_tags+="+sve2";    fi
		mycmakeargs+=( -DGGML_CPU_ARM_ARCH="${arm_arch}${arm_tags}" )
	fi

	if use blas; then
		# ggml-blas calls cblas_sgemm; without an explicit vendor, CMake's
		# FindBLAS picks the Fortran-only libblas.so and the link dies on
		# undefined cblas_sgemm. openblas (already a dep) ships cblas.
		mycmakeargs+=( -DGGML_BLAS_VENDOR=OpenBLAS )
	fi

	if use cuda; then
		# CUDA 13.x nvcc rejects gcc > 15, so pin the host compiler when a
		# matching gcc-15 is installed.  Derived from ${CHOST} instead of a
		# hardcoded x86_64-pc-linux-gnu path, which could never match on arm64
		# and silently skipped the pin there.
		local cuda_host_cxx="${BROOT}/usr/bin/${CHOST}-g++-15"
		if [[ -x ${cuda_host_cxx} ]]; then
			mycmakeargs+=( -DCMAKE_CUDA_HOST_COMPILER="${cuda_host_cxx}" )
		fi
	fi

	cmake_src_configure
}

src_install() {
	cmake_src_install

	# The ggml, whisper and parakeet public headers are installed through cmake's
	# PUBLIC_HEADER property, which keys off CMAKE_INSTALL_INCLUDEDIR and is not
	# covered by the libdir relocation in src_configure.  sci-ml/llama-cpp
	# already owns /usr/include/ggml*.h and /usr/include/gguf.h, so drop the
	# whole tree: this package ships applications, not a development target.
	rm -rf "${ED}/usr/include" || die

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