################################################################################
#
# python-wheel.cygclass - for building Wheels for multiple Python versions
#
# Part of cygport - Cygwin packaging application
# Copyright (C) 2006-2020 Cygport authors
# Provided by the Cygwin project <https://cygwin.com/>
#
# cygport is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cygport is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cygport.  If not, see <https://www.gnu.org/licenses/>.
#
################################################################################

#****h* Cygclasses/python-wheel.cygclass
#  DESCRIPTION
#  Wheels are the standard for installing Python libraries and programs.
#  The build is defined by a PEP 518 conformant source tree, or by a top-level
#  setup.cfg or setup.py file, which controls the installation of files and the
#  building of C Python extensions.  Many such packages are hosted on the Python
#  Package Index (PyPI).
#
#  This cygclass handles the building of wheel-based Python module packages
#  for multiple Python versions simultaneously.
#  EXAMPLE
#    inherit python-wheel
#    
#    NAME="python-six"
#    VERSION=1.10.0
#    RELEASE=1
#    CATEGORY="Python"
#    SUMMARY="Python 2 and 3 compatibility library"
#    DESCRIPTION="Six is a Python 2 and 3 compatibility library. It provides
#    utility functions for smoothing over the differences between the Python
#    versions with the goal of writing Python code that is compatible on both
#    Python versions."
#    
#    ARCH=noarch
#  INHERITS
#  python.org.cygclass
#  REQUIRES
#  pythonXY, pythonXY-pip (for each X.Y version of Python being built)
#****
ORIG_PN=${ORIG_PN:-${PN#python*-}}
PYTHON_WHEEL_NAME=${PYTHON_WHEEL_NAME:-${NAME#python*-}}

inherit python.org

: ${PYTHON_WHEEL_BUILD_METHOD:=pip}

#****v* python-wheel.cygclass/PYTHON_WHEEL_VERSIONS
#  SYNOPSIS
#  PYTHON_WHEEL_VERSIONS="3.5:3.6" # e.g. added to stdlib in 3.7
#  inherit python-wheel
#  DESCRIPTION
#  A colon-seperated list of version(s) for which this module should be built.
#  Some aliases are also accepted:
#  * all: all available 3.y versions (currently: 3.8, 3.9, 3.12)
#  * default: the default set of 3.y version(s) (currently: 3.9, 3.12)
#  * future: the default and any upcoming 3.y versions (currently: 3.9, 3.12)
#  * 3: the default 3.x version (currently: 3.12)
#
#  If defined, this variable must be set before inheriting python-wheel.cygclass.
#  If undefined, "default" is assumed.
#  NOTES
#  * Python 2.7 is no longer supported upstream, and all code should be moving
#    to Python 3.y.  A 2.x version appearing in PYTHON_WHEEL_VERSIONS is now an
#    error.
#  * It is not generally expected for wheel packages to be built with "all".
#    This option is primarily intended for the most basic modules used to
#    build, install, and run other modules (e.g. setuptools, wheel, pip, and
#    virtualenv).  The "default" value is sufficient for most packages.
#  * The value "future" is intended to allow maintainers to get a head start on
#    rebuilding for the new version, but the maintainer must first assure that
#    the module works correctly on the new version and that either there are
#    no module dependencies outside of the standard library, or that they too
#    are already available.
#****

# when the default versions of Python change, this, the python3 virtual package
# target, and the ENSUREPIP_OPTIONS switches need to be updated accordingly
_tmp_wheel_v=
: ${PYTHON_WHEEL_VERSIONS:=default}
for ver in ${PYTHON_WHEEL_VERSIONS//:/ }
do
	case "${ver}" in
	all)		_tmp_wheel_v+=3.8:3.9:3.12: ;;
	future)	_tmp_wheel_v+=3.9:3.12: ;;
	default)	_tmp_wheel_v+=3.9:3.12: ;;
	2)		_tmp_wheel_v+=2.7: ;;
	3)		_tmp_wheel_v+=3.12: ;;
	*)		_tmp_wheel_v+=$ver: ;;
	esac
done
PYTHON_WHEEL_VERSIONS=${_tmp_wheel_v%:}
unset _tmp_wheel_v

for ver in ${PYTHON_WHEEL_VERSIONS//:/ }
do
	case "${ver}" in
	2.*)
		error "${ver} in PYTHON_WHEEL_VERSIONS"
		;;
	esac

	check_prog_req pip${ver} python${ver//.}-pip
	check_prog_req wheel-${ver} python${ver//.}-wheel

	if [ "${PYTHON_WHEEL_BUILD_METHOD}" = "build" ]
	then
		python${ver} -c 'import build' 2>/dev/null || error "pyproject-build${ver} is required to build this package";
	fi
done

#****o* python-wheel.cygclass/PKG_NAMES (python-wheel)
#  DESCRIPTION
#  For a NAME of python-foo, pythonXY-foo binary packages are created
#  automatically, corresponding to each X.Y version in PYTHON_WHEEL_VERSIONS.
#
#  Each package also includes its own independent copy of the automatically
#  installed documentation (COPYING, README, etc.).
#
#  NOTES
#  For those packages which install scripts into /usr/bin, they should be added
#  to the respective pythonXY_foo_CONTENTS, for example:
#
#    declare python${PYTHON3_PKGVERSION}_foo_CONTENTS+=" usr/bin/foo"
#
#  or
#
#    for v in ${PYTHON_WHEEL_VERSIONS//:/ }
#      do
#        declare python${v/./}_foo_CONTENTS+=" usr/bin/foo${v}"
#      done
#
#****
if [ ${NAME%%-*} = "python" ]
then
	_CYGPORT_INTERNAL_multi_doc_=1
	for ver in ${PYTHON_WHEEL_VERSIONS//:/ }
	do
		PKG_NAMES+=" python${ver/.}-${PYTHON_WHEEL_NAME}"
		case ${ver} in
		# this is the default 3.x version
		3.12)
		    # If we are making a 3.x package (where x is the default),
		    # also make a python3-wheelname virtual package, which just
		    # requires python3x-wheelname
		    PKG_NAMES+=" python3-${PYTHON_WHEEL_NAME}"
		    declare -g python3_${PYTHON_WHEEL_NAME//[-\.]/_}_DESCRIPTION="The python3-${PYTHON_WHEEL_NAME} virtual package. Selecting this package for installation will cause the python${ver/.}-${PYTHON_WHEEL_NAME} package to be installed."
		    declare -g python3_${PYTHON_WHEEL_NAME//[-\.]/_}_CATEGORY="Python Virtual"
		    declare -g python3_${PYTHON_WHEEL_NAME//[-\.]/_}_REQUIRES="python${ver/.}-${PYTHON_WHEEL_NAME}"
		    declare -g python3_${PYTHON_WHEEL_NAME//[-\.]/_}_CONTENTS=""
		    ;;
		esac
		declare -g python${ver/.}_${PYTHON_WHEEL_NAME//[-\.]/_}_CONTENTS="usr/lib/python${ver}/site-packages/ usr/share/doc/python${ver/.}-${PYTHON_WHEEL_NAME}"
	done
fi

#****C* python-wheel.cygclass/python_wheel_compile
#  SYNOPSIS
#  python_wheel_compile [OPTIONS]
#  DESCRIPTION
#  Runs the 'pip wheel' command to build python wheel from a setup.py or a PEP
#  518 conformant source tree.
#
#  Any arguments provided will be passed as arguments to 'setup.py bdist_wheel',
#  if that is invoked by the build.
#****

#****iv* python-wheel.cygclass/PYTHON_WHEEL_BUILD_METHOD
#  SYNOPSIS
#  PYTHON_WHEEL_BUILD_METHOD="pip"
#  DESCRIPTION
#  Selects the method used to build a binary wheel:
#  * 'build': Uses pyproject-build (the 'build' module).
#  * 'pip': Uses 'pip wheel'.
#  * 'setuptools': Uses 'setup.py bdist_wheel'.
#  NOTES
#   If you find yourself using this to override cygport's default behaviour, that
#   is in all likelihood a bug in cygport.
#****

python_wheel_compile() {
	local ver
	local bdist_args

	if [ ! -e pyproject.toml ] && [ ! -e setup.py ] && [ ! -e setup.cfg ]
	then
		error "No pyproject.toml (PEP 518), setup.cfg or setup.py detected in source tree"
	fi

	if [ $# -gt 0 ]
	then
		bdist_args="--build-option ${@}"
	fi

	for ver in ${PYTHON_WHEEL_VERSIONS//:/ }
	do
		[ ! -d build/lib ] || find build/lib -delete
		if [ ! -f dist/*-py2.py3*-none-any.whl -a ! -f dist/*py${ver:0:1}-none-any.whl ]
		then
			case "${PYTHON_WHEEL_BUILD_METHOD}" in
			"setuptools")
				# setuptools.launch imports setuptools hooks regardles of setup.py
				/usr/bin/python${ver} -msetuptools.launch setup.py bdist_wheel "${@}" || error "setup.py bdist_wheel failed"
			;;

			"pip")
				pip${ver} --disable-pip-version-check wheel --no-deps --no-build-isolation -w dist ${bdist_args} . || error "pip${ver} wheel failed"
			;;

			"build")
				/usr/bin/python${ver} -m build --wheel --outdir dist --skip-dependency-check --no-isolation . || error "pyproject-build${ver} failed"
			;;

			*)
				error "unknown PYTHON_WHEEL_BUILD_METHOD: ${PYTHON_WHEEL_BUILD_METHOD}"
			;;

			esac
		fi
	done
}

#****I* python-wheel.cygclass/python_wheel_install
#  SYNOPSIS
#  python_wheel_install
#  DESCRIPTION
#  Installs the previously built wheel into $D.
#****
python_wheel_install() {
	local ver whl

	if [ ! -e pyproject.toml ] && [ ! -e setup.py ] && [ ! -e setup.cfg ]
	then
		error "No pyproject.toml (PEP 518), setup.cfg or setup.py detected in source tree"
	fi

	for ver in ${PYTHON_WHEEL_VERSIONS//:/ }
	do
		if [ -f dist/*-py2.py3*-none-any.whl ]
		then
			# Universal wheels can be installed on all versions of Python
			whl="*-py2.py3*-none-any.whl"

			if [ "${ARCH}" != "noarch" ]
			then
				   warning "A universal wheel was produced, but ARCH=noarch is not set"
			fi

		elif [ -f dist/*-py${ver:0:1}-none-any.whl ]
		then
			# not all noarch wheels are officially universal
			whl="*-py${ver:0:1}-none-any.whl"

			if [ "${ARCH}" != "noarch" ]
			then
				   warning "A pure python wheel was produced, but ARCH=noarch is not set"
			fi

		elif [ -f dist/*-cp${ver/.}-*cygwin_*_${ARCH}.whl ]
		then
			whl="*-cp${ver/.}-*cygwin_*_${ARCH}.whl"
		else

			inform "This error occurs when ARCH=noarch is set but a platform wheel was produced; otherwise it indicates a bug in cygport"
			whl=$(cd dist ; ls *.whl)
			error "unknown wheel filename: ${whl}"

		fi

		# ENSUREPIP_OPTIONS not present: pip, pipX, pipX.Y, easy_install, easy_install-X.Y
		# ENSUREPIP_OPTIONS=altinstall: pipX.Y, easy_install-X.Y
		# ENSUREPIP_OPTIONS=install: pipX, pipX.Y, easy_install-X.Y

		# if there are any collisions (e.g over console script names),
		# the last version appearing in PYTHON_WHEEL_VERSIONS is the one
		# that gets to install them

		case ${ver} in
		3.12) export ENSUREPIP_OPTIONS="install" ;;
		*) export ENSUREPIP_OPTIONS="altinstall" ;;
		esac
		pip${ver} --disable-pip-version-check install -I dist/${whl} --root ${D} --prefix /usr --no-compile --no-deps --no-warn-script-location || error "pip${ver} install failed"
	done
}

#****I* python-wheel.cygclass/dowheel
#  SYNOPSIS
#  dowheel WHEEL [WHEEL] ...
#  DESCRIPTION
#  Installs the given Python wheel(s) into $D/usr/share/python-wheels, to
#  allow offline installation in Python virtual environments.
#****
dowheel() {
	local whldir=/usr/share/python-wheels
	local i
	local mode

	dodir ${whldir}

	for i
	do
		if [ ! -e ${i} ]
		then
			error "dowheel: ${i}: file not found"
		fi

		__doinstall 0644 ${i} ${whldir} || error "dowheel ${i} failed"
	done
}

#****o* python-wheel.cygclass/src_compile (python-wheel)
#  DEFINITION
src_compile() {
	lndirs
	cd ${B}
	python_wheel_compile
}
#****

#****o* python-wheel.cygclass/src_install (python-wheel)
#  DEFINITION
src_install() {
	cd ${B}
	python_wheel_install
}
#****

readonly -f python_wheel_compile python_wheel_install dowheel
