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

EAPI=8

PYTHON_COMPAT=( python3_{10..13} )
LLVM_COMPAT=( 20 )
DISTUTILS_USE_PEP517=setuptools
DISTUTILS_EXT=1

inherit distutils-r1 llvm-r1 qmake-utils virtualx

MY_PN=pyside-pyside-setup
MY_P=${MY_PN}-${PV}

DESCRIPTION="Python bindings for the Qt framework"
HOMEPAGE="https://wiki.qt.io/PySide6"
SRC_URI="https://github.com/qtproject/${MY_PN}/archive/refs/tags/v${PV}.tar.gz -> ${MY_P}.gh.tar.gz"
S="${WORKDIR}/${MY_P}"

LICENSE="|| ( GPL-2 GPL-3 LGPL-3 )"
SLOT="6/${PV}"
KEYWORDS="amd64 ~arm arm64 ~loong ~ppc64 ~riscv ~x86"

# If a flag enables multiple Qt modules, they should be ordered
# according to their dependencies, e.g. for 3d, 3DCore must be first.
# Widgets for various modules are handled as a special case later
declare -A QT_MODULES=(
	["3d"]="3DCore 3DRender 3DLogic 3DInput 3DAnimation 3DExtras"
	["bluetooth"]="Bluetooth"
	["charts"]="Charts"
	["+concurrent"]="Concurrent"
	["+core"]="Core"
	["+dbus"]="DBus"
	["designer"]="Designer"
	["+gui"]="Gui"
	["help"]="Help"
	["httpserver"]="HttpServer"
	["location"]="Location"
	["multimedia"]="Multimedia" # plus widgets
	["network-auth"]="NetworkAuth"
	["+network"]="Network"
	["nfc"]="Nfc"
	["+opengl"]="OpenGL" # plus widgets
	["pdfium"]="Pdf" # plus widgets
	["positioning"]="Positioning"
	["+printsupport"]="PrintSupport"
	["qml"]="Qml"
	["quick3d"]="Quick3D"
	["quick"]="Quick" # plus widgets
	["remoteobjects"]="RemoteObjects"
	["scxml"]="Scxml"
	["sensors"]="Sensors"
	["serialbus"]="SerialBus"
	["serialport"]="SerialPort"
	["spatialaudio"]="SpatialAudio"
	["+sql"]="Sql"
	["svg"]="Svg" # plus widgets
	["speech"]="TextToSpeech"
	["+testlib"]="Test"
	["uitools"]="UiTools"
	["webchannel"]="WebChannel"
	["webengine"]="WebEngineCore" # plus widgets and quick
	["websockets"]="WebSockets"
	["webview"]="WebView"
	["+widgets"]="Widgets"
	["+xml"]="Xml"
)

# Manually reextract these requirements on version bumps by running the
# following one-liner from within "${S}":
#     $ grep 'set.*_deps' PySide6/Qt*/CMakeLists.txt
declare -A QT_REQUIREMENTS=(
	["3d"]="gui network opengl"
	["bluetooth"]="core"
	["charts"]="core gui widgets"
	["concurrent"]="core"
	["dbus"]="core"
	["designer"]="widgets"
	["gles2-only"]="gui"
	["gui"]="core"
	["help"]="widgets"
	["httpserver"]="core concurrent network websockets"
	["location"]="core positioning"
	["multimedia"]="core gui network"
	["network-auth"]="network"
	["network"]="core"
	["nfc"]="core"
	["opengl"]="gui"
	["pdfium"]="core gui network"
	["positioning"]="core"
	["printsupport"]="widgets"
	["qml"]="network"
	["quick"]="gui network qml opengl"
	["quick3d"]="gui network qml quick"
	["remoteobjects"]="core network"
	["scxml"]="core"
	["sensors"]="core"
	["serialbus"]="core network serialport"
	["serialport"]="core"
	["spatialaudio"]="core gui network multimedia"
	["speech"]="core multimedia"
	["sql"]="widgets"
	["svg"]="gui"
	["testlib"]="widgets"
	["uitools"]="widgets"
	["webchannel"]="core"
	["webengine"]="core gui network printsupport quick webchannel"
	["websockets"]="network"
	["webview"]="gui"
	["widgets"]="gui"
	["xml"]="core"
)

IUSE="${!QT_MODULES[@]} debug doc gles2-only numpy test tools"
RESTRICT="!test? ( test )"

# majority of QtQml tests require QtQuick support
REQUIRED_USE="
	test? (
		qml? ( quick )
	)
"
for requirement in ${!QT_REQUIREMENTS[@]}; do
	REQUIRED_USE+=" ${requirement}? ( ${QT_REQUIREMENTS[${requirement}]} ) "
done

# Minimal supported version of Qt.
QT_PV="$(ver_cut 1-3)*:6"

# WebEngine needs sound support, so enable either pulseaudio or alsa
RDEPEND="
	=dev-qt/qtbase-${QT_PV}[concurrent?,dbus?,gles2-only=,network?,opengl?,sql?,widgets?,xml?]
	3d? ( =dev-qt/qt3d-${QT_PV}[qml?,gles2-only=] )
	bluetooth? ( =dev-qt/qtconnectivity-${QT_PV}[bluetooth] )
	charts? ( =dev-qt/qtcharts-${QT_PV} )
	designer? ( =dev-qt/qttools-${QT_PV}[designer,widgets,gles2-only=] )
	gui? (
		=dev-qt/qtbase-${QT_PV}[gui,jpeg(+)]
		x11-libs/libxkbcommon
	)
	help? ( =dev-qt/qttools-${QT_PV}[assistant,gles2-only=] )
	httpserver? ( =dev-qt/qthttpserver-${QT_PV} )
	location? ( =dev-qt/qtlocation-${QT_PV} )
	multimedia? ( =dev-qt/qtmultimedia-${QT_PV}[widgets(+)?] )
	network? ( =dev-qt/qtbase-${QT_PV}[ssl] )
	network-auth? ( =dev-qt/qtnetworkauth-${QT_PV} )
	nfc? ( =dev-qt/qtconnectivity-${QT_PV}[nfc] )
	numpy? ( >=dev-python/numpy-2.1.3[${PYTHON_USEDEP}] )
	pdfium? ( =dev-qt/qtwebengine-${QT_PV}[pdfium(-),widgets?] )
	positioning? ( =dev-qt/qtpositioning-${QT_PV} )
	printsupport? ( =dev-qt/qtbase-${QT_PV}[gui,widgets] )
	qml? ( =dev-qt/qtdeclarative-${QT_PV}[opengl?,widgets?] )
	quick3d? ( =dev-qt/qtquick3d-${QT_PV}[opengl?] )
	remoteobjects? ( =dev-qt/qtremoteobjects-${QT_PV} )
	scxml? ( =dev-qt/qtscxml-${QT_PV} )
	sensors? ( =dev-qt/qtsensors-${QT_PV}[qml?] )
	speech? ( =dev-qt/qtspeech-${QT_PV} )
	serialbus? ( =dev-qt/qtserialbus-${QT_PV} )
	serialport? ( =dev-qt/qtserialport-${QT_PV} )
	svg? ( =dev-qt/qtsvg-${QT_PV} )
	testlib? ( =dev-qt/qtbase-${QT_PV}[gui] )
	tools? (
		=dev-qt/qtbase-${QT_PV}
		=dev-qt/qtdeclarative-${QT_PV}[qmlls]
		=dev-qt/qttools-${QT_PV}[assistant,designer,linguist]
		dev-python/pkginfo[${PYTHON_USEDEP}]
	)
	uitools? ( =dev-qt/qttools-${QT_PV}[gles2-only=,widgets] )
	webchannel? ( =dev-qt/qtwebchannel-${QT_PV} )
	webengine? ( || (
		=dev-qt/qtwebengine-${QT_PV}[alsa,widgets?]
		=dev-qt/qtwebengine-${QT_PV}[pulseaudio,widgets?]
		)
	)
	websockets? ( =dev-qt/qtwebsockets-${QT_PV} )
	webview? ( =dev-qt/qtwebview-${QT_PV} )
	!dev-python/pyside:0
	!dev-python/shiboken6
	!dev-python/pyside6-tools
"

DEPEND="${RDEPEND}
	$(llvm_gen_dep '
		llvm-core/clang:${LLVM_SLOT}
		llvm-core/llvm:${LLVM_SLOT}
	')
	dev-util/vulkan-headers
	test? ( =dev-qt/qtbase-${QT_PV}[gui] )
" # testlib is toggled by the gui flag on qtbase

BDEPEND="
	dev-build/cmake
	dev-python/distro[${PYTHON_USEDEP}]
	dev-util/patchelf
	doc? (
		>=dev-libs/libxml2-2.6.32
		>=dev-libs/libxslt-1.1.19
		media-gfx/graphviz
		dev-python/sphinx[${PYTHON_USEDEP}]
		dev-python/myst-parser[${PYTHON_USEDEP}]
	)
	numpy? ( dev-python/numpy[${PYTHON_USEDEP}] )
"

PATCHES=(
	# Needs porting to newer wheel and setuptools
	"${FILESDIR}/${PN}-6.8.2-quick-fix-build-wheel.patch"
	# References files not present in our dev-qt/qtbase
	"${FILESDIR}/${P}-no-qtexampleicons.patch" # TODO: remove in 6.8.3
)

# Build system duplicates system libraries. TODO: fix
QA_PREBUILT=(
	"/usr/lib/python*/site-packages/PySide6/*"
)

python_prepare_all() {
	distutils-r1_python_prepare_all

	# Shiboken6 assumes Vulkan headers live under either "$VULKAN_SDK/include"
	# or "$VK_SDK_PATH/include" rather than "${EPREFIX}/usr/include/vulkan".
	sed -i -e "s~\bdetectVulkan(&headerPaths);~headerPaths.append(HeaderPath{QByteArrayLiteral(\"${EPREFIX}/usr/include/vulkan\"), HeaderType::System});~" \
			sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp || die

	# Shiboken6 assumes the "/usr/lib/clang/${CLANG_NEWEST_VERSION}/include/"
	# subdirectory provides Clang builtin includes (e.g., "stddef.h") for the
	# currently installed version of Clang, where ${CLANG_NEWEST_VERSION} is
	# the largest version specifier that exists under the "/usr/lib/clang/"
	# subdirectory. This assumption is false in edge cases, including when
	# users downgrade from newer Clang versions but fail to remove those
	# versions with "emerge --depclean". See also:
	#     https://github.com/leycec/raiagent/issues/85
	#
	# Sadly, the clang-* family of functions exported by the "toolchain-funcs"
	# eclass are defective, returning nonsensical placeholder strings if the
	# end user has *NOT* explicitly configured their C++ compiler to be Clang.
	# PySide6 does *NOT* care whether the end user has done so or not, as
	# PySide6 unconditionally requires Clang in either case. See also:
	#     https://bugs.gentoo.org/619490
	sed -e \
		's~(findClangBuiltInIncludesDir())~(QStringLiteral("'"${EPREFIX}"'/usr/lib/clang/'"${LLVM_SLOT}"'/include"))~' \
		-i sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp || die

	# blacklist.txt works like XFAIL
	cat <<- EOF >> build_history/blacklist.txt || die
	# segfaults with QOpenGLContext::create
	[pysidetest::qapp_like_a_macro_test]
		linux
	# Tries to execute pip install
	[pyside6-deploy::test_pyside6_deploy]
		linux
	[pyside6-android-deploy::test_pyside6_android_deploy]
		linux
	EOF

	if ! use numpy; then
		cat <<- EOF >> build_history/blacklist.txt || die
		# Requires numpy support to pass
		[sample::array_numpy]
			linux
		[sample::nontypetemplate]
			linux
		[QtGui::qpainter_test]
			linux
		EOF
	fi
}

python_configure_all() {
	ENABLED_QT_MODULES=()

	# The order matters, dependencies must come first so process
	# REQUIRED_USE and recursively enable modules
	enable_qt_mod() {
		local flag=${1}
		local modules=${QT_MODULES[${flag}]}
		if [[ -z ${modules} ]]; then
			die "incorrect flag=${flag}, not registered"
		fi
		local dependencies=${QT_REQUIREMENTS[${flag//+}]}
		if [[ -n ${dependencies} ]]; then
			local depflag
			for depflag in ${dependencies}; do
				if use ${depflag}; then
					if [[ -z ${QT_MODULES[${depflag}]} ]]; then
						depflag=+${depflag}
					fi
					enable_qt_mod ${depflag}
				else
					die "${depflag} is required but not enabled"
				fi
			done
		fi
		if [[ "${ENABLED_QT_MODULES[*]}" != *${modules}* ]]; then
			ENABLED_QT_MODULES+=( ${modules} )
		fi
	}
	# Enable specified qt modules
	local flag
	for flag in ${!QT_MODULES[@]}; do
		if use ${flag//+}; then
			enable_qt_mod ${flag}
		fi
	done

	# Special cases
	if use widgets; then
		use multimedia && ENABLED_QT_MODULES+=( MultimediaWidgets )
		use opengl && ENABLED_QT_MODULES+=( OpenGLWidgets )
		use pdfium && ENABLED_QT_MODULES+=( PdfWidgets )
		use quick && ENABLED_QT_MODULES+=( QuickWidgets )
		use svg && ENABLED_QT_MODULES+=( SvgWidgets )
		use webengine && ENABLED_QT_MODULES+=( WebEngineWidgets )
	fi
	if use quick; then
		use webengine && ENABLED_QT_MODULES+=( WebEngineQuick )
		use testlib && ENABLED_QT_MODULES+=( QuickTest )
	fi

	# Arguments listed in options.py
	MAIN_DISTUTILS_ARGS=(
		--cmake="${EPREFIX}/usr/bin/cmake"
		--ignore-git
		--limited-api=no
		--module-subset="$(printf '%s,' "${ENABLED_QT_MODULES[@]}")"
		--no-strip
		--no-size-optimization
		--openssl="${EPREFIX}/usr/bin/openssl"
		--qt=$(ver_cut 1-3)
		--qtpaths=$(qt6_get_bindir)/qtpaths
		--verbose-build
		$(usex debug "--debug" "--relwithdebinfo")
		$(usex doc "--build-docs" "--skip-docs")
		$(usex numpy "--enable-numpy-support" "--disable-numpy-support")
		$(usex test "--build-tests --use-xvfb" "")
		$(usex tools "" "--no-qt-tools")
	)
}

python_compile() {
	DISTUTILS_ARGS=(
		"${MAIN_DISTUTILS_ARGS[@]}"
		--build-type=shiboken6
	)
	distutils-r1_python_compile
	DISTUTILS_ARGS=(
		"${MAIN_DISTUTILS_ARGS[@]}"
		--reuse-build
		--build-type=shiboken6-generator
	)
	distutils-r1_python_compile
	# If no pyside modules enabled, build just shiboken
	if [[ ${#ENABLED_QT_MODULES[@]} -gt 0 ]]; then
		DISTUTILS_ARGS=(
			"${MAIN_DISTUTILS_ARGS[@]}"
			--reuse-build
			--shiboken-target-path="$(find "${BUILD_DIR}/build" -type d -name cmake)/../../"
			--build-type=pyside6
		)
		distutils-r1_python_compile
	fi

	# Link libraries to the usual location for backwards compatibility
	pushd "${BUILD_DIR}/install/$(python_get_sitedir)" >/dev/null ||
		die
	mkdir -p "${BUILD_DIR}/install/usr/$(get_libdir)" || die
	local lib
	for lib in */*.cpython-*.so
	do
		local base=${lib##*/}
		ln -s "${base}" "${lib%/*}/${base%%.*}-${EPYTHON}.so" ||
			die
	done
	for lib in */*.cpython-*.so.$(ver_cut 1-2)
	do
		local base=${lib##*/}
		ln -s "${base}" "${lib%/*}/${base%%.*}-${EPYTHON}.so.$(ver_cut 1-2)" ||
			die
	done
	for lib in */*.so*; do
		ln -s "../../$(python_get_sitedir)/${lib}" \
			"${BUILD_DIR}/install/usr/$(get_libdir)/${lib#*/}" || die
	done
	popd >/dev/null || die

	# Symlinks for compatibility with pypi wheels
	local dir
	if [[ -d ${BUILD_DIR}/install/$(python_get_sitedir)/PySide6 ]]
	then
		pushd "${BUILD_DIR}/install/$(python_get_sitedir)/PySide6" \
			>/dev/null || die
		mkdir -p "${BUILD_DIR}/install/usr/share/PySide6" || die
		for dir in doc glue typesystems; do
			ln -s "../../../$(python_get_sitedir)/PySide6/${dir}" \
				"${BUILD_DIR}/install/usr/share/PySide6/${dir}" ||
					die
		done
		popd >/dev/null || die
	fi
	mkdir -p "${BUILD_DIR}/install/usr/include"
	for dir in PySide6 shiboken6_generator; do
		if [[ -d ${BUILD_DIR}/install/$(python_get_sitedir)/${dir}/include ]]
		then
			ln -s "../../$(python_get_sitedir)/${dir}/include" \
				"${BUILD_DIR}/install/usr/include/${dir//_generator}" ||
					die
		fi
	done

	# Install misc files from inner install dir
	find "${BUILD_DIR}"/build/*/install -type f \
		-name libPySidePlugin.so -exec \
		mkdir -p "${BUILD_DIR}/install/$(qt6_get_plugindir)/designer/" \; \
		-exec \
		cp "{}" "${BUILD_DIR}/install/$(qt6_get_plugindir)/designer/" \; \
			|| die

	for dir in cmake pkgconfig; do
		find "${BUILD_DIR}"/build/*/install -type d -name ${dir} \
			-exec cp -r "{}" "${BUILD_DIR}/install/usr/lib/" \; \
				|| die
	done

	# Uniquify the pkgconfigs file for the current Python target,
	# preserving an unversioned "shiboken6.pc" file arbitrarily
	# associated with the last Python target.
	if [[ -f ${BUILD_DIR}/install/usr/lib/pkgconfig/shiboken6.pc ]]
	then
		sed -e 's~prefix=.*~prefix=/usr~g' \
			-e 's~exec_prefix=.*~exec_prefix=${prefix}~g' \
			-e "s~libdir=.*~libdir=$(python_get_sitedir)/shiboken6~g" \
			-e "s~includedir=.*~includedir=$(python_get_sitedir)/shiboken6_generator/include~g" \
			-i "${BUILD_DIR}/install/usr/lib/pkgconfig/shiboken6.pc" || die
		cp "${BUILD_DIR}/install/usr/lib/pkgconfig/"shiboken6{,-${EPYTHON}}.pc || die
	fi
	if [[ -f ${BUILD_DIR}/install/usr/lib/pkgconfig/pyside6.pc ]]
	then
		sed -e 's~^Requires: shiboken6$~&-'${EPYTHON}'~' \
			-e 's~prefix=.*~prefix=/usr~g' \
			-e 's~exec_prefix=.*~exec_prefix=${prefix}~g' \
			-e "s~libdir=.*~libdir=$(python_get_sitedir)/PySide6~g" \
			-e "s~includedir=.*~includedir=$(python_get_sitedir)/PySide6/include~g" \
			-e "s~typesystemdir=.*~typesystemdir=$(python_get_sitedir)/PySide6/typesystems~g" \
			-e "s~gluedir=.*~gluedir=$(python_get_sitedir)/PySide6/glue~g" \
			-e "s~pythonpath=.*~pythonpath=$(python_get_sitedir)~g" \
			-i "${BUILD_DIR}/install/usr/lib/pkgconfig/pyside6.pc" || die
		cp "${BUILD_DIR}/install/usr/lib/pkgconfig/"pyside6{,-${EPYTHON}}.pc || die
	fi

	sed \
		-e "s~/lib/libshiboken6\.cpython~/$(get_libdir)/libshiboken6\.cpython~g" \
		-e "s~/lib/libpyside6\.cpython~/$(get_libdir)/libpyside6\.cpython~g" \
		-e "s~/lib/libpyside6qml\.cpython~/$(get_libdir)/libpyside6qml\.cpython~g" \
		-e "s~libshiboken6\.cpython.*\.so\.$(ver_cut 1-3)~libshiboken6\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-e "s~libpyside6\.cpython.*\.so\.$(ver_cut 1-3)~libpyside6\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-e "s~libpyside6qml\.cpython.*\.so\.$(ver_cut 1-3)~libpyside6qml\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-e "s~libshiboken6\.cpython.*\.so\.$(ver_cut 1-2)~libshiboken6\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-e "s~libpyside6\.cpython.*\.so\.$(ver_cut 1-2)~libpyside6\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-e "s~libpyside6qml\.cpython.*\.so\.$(ver_cut 1-2)~libpyside6qml\${PYTHON_CONFIG_SUFFIX}\.so\.$(ver_cut 1-2)~g" \
		-i 	"${BUILD_DIR}/install/usr/lib/cmake/"*/*.cmake || die
	local file
	for file in "${BUILD_DIR}/install/usr/lib/cmake/"*/*.cpython-*.cmake
	do
		local base=${file##*/}
		ln -s "${base}" "${file%/*}/${base%%.*}-${EPYTHON}.cmake" ||
			die
	done
}

python_test() {
	# figure out the build dir
	local build_dir build_classifier
	build_dir=$(ls -d "${BUILD_DIR}"/build/qfp-*/build/)
	build_classifier="${build_dir##${BUILD_DIR}/build/qfp-}"
	build_classifier="${build_dir%%/build}"

	# Otherwise it picks the last built directory breaking assumption for multi target builds
	mkdir -p build_history/9999-99-99_999999/ || die
	cat <<- EOF > build_history/9999-99-99_999999/build_dir.txt || die
	${build_dir}
	${build_classifier}
	EOF

	virtx ${EPYTHON} testrunner.py test --projects=shiboken6 $(usev core '--projects=pyside6')  ||
		die "Tests failed with ${EPYTHON}"
}

pkg_preinst() {
	# Avoid symlinks being blocked by directories
	rm -rf "${EROOT}/usr/include/"{PySide6,shiboken6} || die
	rm -rf "${EROOT}/usr/share/PySide6" || die
}
