1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
|
# (C) Copyright 2025 ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation nor
# does it submit to any jurisdiction.
##############################################################################
#.rst:
#
# download_python_wheels
# ======================
#
# Download all dependencies for the given ``REQUIREMENT_SPEC`` and cache them in a
# wheelhouse at ``WHEELS_DIR``
#
# download_python_wheels( REQUIREMENT_SPEC <spec> [ WHEELS_DIR <path> ] [ PYTHON_VERSION <version str> ]
# [ WHEEL_ARCH <spec> ] [ WHEEL_PYTHON_VERSION <spec> ] )
#
# Implementation note
# -------------------
#
# This function does intentionally not expose all PIP options directly because the PIP command line
# interface allows to specify option values via environment variables. These can therefore be used
# to further control the PIP behaviour, see https://pip.pypa.io/en/stable/cli/pip_download/
#
# Because PIP does not provide a mechanism for downloading PEP 518 build dependencies,
# this function builds the wheel also for the provided REQUIREMENT_SPEC instead of only downloading
# the required dependencies. See https://github.com/pypa/pip/issues/7863 for details.
# To provide a sane minimum, setuptools and wheel packages are always downloaded.
#
# The provided PYTHON_VERSION is used to discover a Python interpreter matching the version
# specification when calling pip. To download wheels for specific platforms or Python versions,
# use the PIP_PLATFORM, PIP_PYTHON_VERSION, PIP_IMPLEMENTATION, or PIP_ABI environment variables.
#
# It is safe to call this function during an offline build, as long as all wheels are already
# available in the wheelhouse. A dry-run call to ``pip install`` is used to determine the need
# for any wheel downloads before executing the ``pip download`` command.
#
# Options
# -------
#
# :REQUIREMENT_SPEC: The requirement spec as given to ``pip download`` and ``pip wheel``
# :WHEELS_DIR: The path of the wheelhouse directory to cache the wheels. Defaults to
# ``${CMAKE_CURRENT_BINARY_DIR}/wheelhouse``
# :PYTHON_VERSION: Optional specification of permissible Python versions for find_package
# :WHEEL_ARCH: Optional specification of architecture for which to download non-pure Python wheels
# :WHEEL_PYTHON_VERSION: Optional specification of Python version for which to download wheels
#
##############################################################################
function( download_python_wheels )
set( options "" )
set( oneValueArgs REQUIREMENT_SPEC WHEELS_DIR PYTHON_VERSION WHEEL_ARCH WHEEL_PYTHON_VERSION )
set( multiValueArgs "" )
cmake_parse_arguments( _PAR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if( _PAR_UNPARSED_ARGUMENTS )
message( FATAL_ERROR "Unknown keywords given to download_python_wheels(): \"${_PAR_UNPARSED_ARGUMENTS}\"" )
endif()
if( NOT _PAR_REQUIREMENT_SPEC )
message( FATAL_ERROR "No REQUIREMENT_SPEC provided to download_python_wheels()" )
endif()
message( STATUS "Checking for cached wheels in ${WHEELS_DIR}" )
# Check for a suitable python interpreter
find_package( Python3 ${_PAR_PYTHON_VERSION} COMPONENTS Interpreter REQUIRED QUIET )
# If no wheelhouse dir is given, create one in the current binary directory
if( _PAR_WHEELS_DIR )
set( WHEELS_DIR "${_PAR_WHEELS_DIR}" )
else()
set( WHEELS_DIR "${CMAKE_CURRENT_BINARY_DIR}/wheelhouse" )
endif()
file( MAKE_DIRECTORY "${WHEELS_DIR}" )
unset( PIP_OPTIONS )
if( DEFINED _PAR_WHEEL_ARCH AND NOT _PAR_WHEEL_ARCH MATCHES None|NONE )
string(REPLACE "\"" "" _WHEEL_ARCH ${_PAR_WHEEL_ARCH})
list( APPEND PIP_OPTIONS "--platform=${_WHEEL_ARCH}" )
endif()
if( DEFINED _PAR_WHEEL_PYTHON_VERSION AND NOT _PAR_WHEEL_PYTHON_VERSION MATCHES None|NONE )
string(REPLACE "\"" "" _PYTHON_VERSION ${_PAR_WHEEL_PYTHON_VERSION})
list( APPEND PIP_OPTIONS "--python-version=${_PYTHON_VERSION}" )
endif()
# We use a dry-run installation to check if all dependencies have already been downloaded
execute_process(
COMMAND
${Python3_EXECUTABLE} -m pip install
--dry-run --break-system-packages
--no-index --find-links "${WHEELS_DIR}" --only-binary :all:
${PIP_OPTIONS}
${_PAR_REQUIREMENT_SPEC}
OUTPUT_QUIET ERROR_QUIET
RESULT_VARIABLE _RET_VAL
)
if( "${_RET_VAL}" EQUAL "0" )
message( STATUS "All dependency wheels for ${_PAR_REQUIREMENT_SPEC} found in cache" )
else()
message( STATUS "Downloading dependency wheels for ${_PAR_REQUIREMENT_SPEC} to ${WHEELS_DIR}" )
# Download typical build dependencies for wheels: setuptools and wheel
execute_process(
COMMAND
${Python3_EXECUTABLE} -m pip download
--disable-pip-version-check --only-binary :all: --dest "${WHEELS_DIR}"
${PIP_OPTIONS}
setuptools>=75.0.0 wheel
OUTPUT_QUIET
)
# Download dependencies for the specified REQUIREMENT_SPEC
execute_process(
COMMAND
${Python3_EXECUTABLE} -m pip download
--disable-pip-version-check --only-binary :all: --dest "${WHEELS_DIR}"
${PIP_OPTIONS}
${_PAR_REQUIREMENT_SPEC}
OUTPUT_QUIET
)
endif()
endfunction()
download_python_wheels( REQUIREMENT_SPEC ${REQUIREMENT_SPEC}
WHEELS_DIR ${WHEELS_DIR}
WHEEL_ARCH ${FCKIT_WHEEL_ARCH}
WHEEL_PYTHON_VERSION ${FCKIT_WHEEL_PYTHON_VERSION} )
|