|
| 1 | +# Copyright 2020 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +# Sets up a Python interpreter, installing required dependencies. |
| 16 | +# |
| 17 | +# This function does the following: |
| 18 | +# 1. Uses the best-available built-in cmake mechanism to find a Python |
| 19 | +# interpreter (a.k.a. the "host" interpreter). |
| 20 | +# 2. Creates a Python virtualenv in the cmake binary directory using the |
| 21 | +# Python interpreter found in the previous step. |
| 22 | +# 3. Locates the Python interpreter in the virtualenv and sets its path in |
| 23 | +# the specified variable. |
| 24 | +# 4. Runs pip install to install the required dependencies in the virtualenv. |
| 25 | +# |
| 26 | +# This function also writes "stamp files" into the virtualenv. These files |
| 27 | +# are used to determine if the virtualenv is up-to-date from a previous cmake |
| 28 | +# run or if it needs to be recreated from scratch. |
| 29 | +# |
| 30 | +# If any errors are detected (e.g. cannot install one of the given requirements) |
| 31 | +# then a fatal error is logged, causing the cmake processing to terminate. |
| 32 | +# |
| 33 | +# Arguments: |
| 34 | +# OUTVAR - The name of the variable into which to store the path of the |
| 35 | +# Python executable from the virtualenv. |
| 36 | +# KEY - A unique key to ensure isolation from other Python virtualenv |
| 37 | +# environments created by this function. This value will be used in the |
| 38 | +# path of the virtualenv on the file system and in the name of the cmake |
| 39 | +# cache variable that stores its path. |
| 40 | +# REQUIREMENTS - (Optional) A list of Python packages to install in the |
| 41 | +# virtualenv using pip. |
| 42 | +# |
| 43 | +# Example: |
| 44 | +# include(python_setup) |
| 45 | +# FirebaseSetupPythonInterpreter( |
| 46 | +# OUTVAR MY_PYTHON_EXECUTABLE |
| 47 | +# KEY ScanStuff |
| 48 | +# REQUIREMENTS six absl-py |
| 49 | +# ) |
| 50 | +# execute_process(COMMAND "${MY_PYTHON_EXECUTABLE}" scan_stuff.py) |
| 51 | +function(FirebaseSetupPythonInterpreter) |
| 52 | + cmake_parse_arguments( |
| 53 | + PARSE_ARGV 0 |
| 54 | + ARG |
| 55 | + "" # zero-value arguments |
| 56 | + "OUTVAR;KEY" # single-value arguments |
| 57 | + "REQUIREMENTS" # multi-value arguments |
| 58 | + ) |
| 59 | + |
| 60 | + # Validate this function's arguments. |
| 61 | + if("${ARG_OUTVAR}" STREQUAL "") |
| 62 | + message(FATAL_ERROR "OUTVAR must be specified to ${CMAKE_CURRENT_FUNCTION}") |
| 63 | + elseif("${ARG_KEY}" STREQUAL "") |
| 64 | + message(FATAL_ERROR "KEY must be specified to ${CMAKE_CURRENT_FUNCTION}") |
| 65 | + endif() |
| 66 | + |
| 67 | + # Calculate the name of the cmake cache variable where to store the |
| 68 | + # Python interpreter path. |
| 69 | + set(CACHEVAR "FIREBASE_PYTHON_EXECUTABLE_${ARG_KEY}") |
| 70 | + |
| 71 | + # Find a Python interpreter using the best available mechanism. |
| 72 | + if(${CMAKE_VERSION} VERSION_LESS "3.12") |
| 73 | + include(FindPythonInterp) |
| 74 | + set(DEFAULT_PYTHON_HOST_EXECUTABLE "${PYTHON_EXECUTABLE}") |
| 75 | + else() |
| 76 | + find_package(Python3 COMPONENTS Interpreter REQUIRED) |
| 77 | + set(DEFAULT_PYTHON_HOST_EXECUTABLE "${Python3_EXECUTABLE}") |
| 78 | + endif() |
| 79 | + |
| 80 | + # Get the Python interpreter on the host system to use. |
| 81 | + set( |
| 82 | + FIREBASE_PYTHON_HOST_EXECUTABLE |
| 83 | + "${DEFAULT_PYTHON_HOST_EXECUTABLE}" |
| 84 | + CACHE FILEPATH |
| 85 | + "The Python interpreter on the host system to use" |
| 86 | + ) |
| 87 | + |
| 88 | + # Create a virtualenv into which to install Python's dependencies. |
| 89 | + # https://docs.python.org/3/library/venv.html |
| 90 | + set(PYVENV_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pyvenv/${ARG_KEY}") |
| 91 | + set(STAMP_FILE1 "${PYVENV_DIRECTORY}/cmake_firebase_python_stamp1.txt") |
| 92 | + set(STAMP_FILE2 "${PYVENV_DIRECTORY}/cmake_firebase_python_stamp2.txt") |
| 93 | + |
| 94 | + # Check if the virtualenv is already up-to-date by examining the contents of |
| 95 | + # the stamp files. The stamp files store the path of the host Python |
| 96 | + # interpreter and the dependencies that were installed by pip. If both of |
| 97 | + # these files exist and contain the same Python interpreter and dependencies |
| 98 | + # then just re-use the virtualenv; otherwise, re-create it. |
| 99 | + if(EXISTS "${STAMP_FILE1}" AND EXISTS "${STAMP_FILE2}") |
| 100 | + file(READ "${STAMP_FILE1}" STAMP_FILE1_CONTENTS) |
| 101 | + file(READ "${STAMP_FILE2}" STAMP_FILE2_CONTENTS) |
| 102 | + if( |
| 103 | + ("${STAMP_FILE1_CONTENTS}" STREQUAL "${FIREBASE_PYTHON_HOST_EXECUTABLE}") |
| 104 | + AND |
| 105 | + ("${STAMP_FILE2_CONTENTS}" STREQUAL "${ARG_REQUIREMENTS}") |
| 106 | + ) |
| 107 | + set("${ARG_OUTVAR}" "$CACHE{${CACHEVAR}}" PARENT_SCOPE) |
| 108 | + message(STATUS |
| 109 | + "${CMAKE_CURRENT_FUNCTION}: Using Python interpreter: $CACHE{${CACHEVAR}}" |
| 110 | + ) |
| 111 | + return() |
| 112 | + endif() |
| 113 | + endif() |
| 114 | + |
| 115 | + # Create the virtualenv. |
| 116 | + message(STATUS |
| 117 | + "${CMAKE_CURRENT_FUNCTION}: Creating Python virtualenv in " |
| 118 | + "${PYVENV_DIRECTORY} using ${FIREBASE_PYTHON_HOST_EXECUTABLE}" |
| 119 | + ) |
| 120 | + file(REMOVE_RECURSE "${PYVENV_DIRECTORY}") |
| 121 | + execute_process( |
| 122 | + COMMAND |
| 123 | + "${FIREBASE_PYTHON_HOST_EXECUTABLE}" |
| 124 | + -m |
| 125 | + venv |
| 126 | + "${PYVENV_DIRECTORY}" |
| 127 | + --upgrade-deps |
| 128 | + RESULT_VARIABLE |
| 129 | + FIREBASE_PYVENV_CREATE_RESULT |
| 130 | + ) |
| 131 | + if(NOT FIREBASE_PYVENV_CREATE_RESULT EQUAL 0) |
| 132 | + message(FATAL_ERROR |
| 133 | + "Failed to create a Python virtualenv in ${PYVENV_DIRECTORY} " |
| 134 | + "using ${FIREBASE_PYTHON_HOST_EXECUTABLE}") |
| 135 | + endif() |
| 136 | + |
| 137 | + # Find the Python interpreter in the virtualenv. |
| 138 | + find_program( |
| 139 | + "${CACHEVAR}" |
| 140 | + DOC "The Python interpreter to use for ${ARG_KEY}" |
| 141 | + NAMES python3 python |
| 142 | + PATHS "${PYVENV_DIRECTORY}" |
| 143 | + PATH_SUFFIXES bin Scripts |
| 144 | + NO_DEFAULT_PATH |
| 145 | + ) |
| 146 | + if(NOT ${CACHEVAR}) |
| 147 | + message(FATAL_ERROR "Unable to find Python executable in ${PYVENV_DIRECTORY}") |
| 148 | + else() |
| 149 | + set(PYTHON_EXECUTABLE "$CACHE{${CACHEVAR}}") |
| 150 | + message(STATUS |
| 151 | + "${CMAKE_CURRENT_FUNCTION}: Found Python executable in virtualenv: " |
| 152 | + "${PYTHON_EXECUTABLE}" |
| 153 | + ) |
| 154 | + endif() |
| 155 | + |
| 156 | + # Install the dependencies in the virtualenv, if any are requested. |
| 157 | + if(NOT ("${ARG_REQUIREMENTS}" STREQUAL "")) |
| 158 | + message(STATUS |
| 159 | + "${CMAKE_CURRENT_FUNCTION}: Installing Python dependencies into " |
| 160 | + "${PYVENV_DIRECTORY}: ${ARG_REQUIREMENTS}" |
| 161 | + ) |
| 162 | + execute_process( |
| 163 | + COMMAND |
| 164 | + "${PYTHON_EXECUTABLE}" |
| 165 | + -m |
| 166 | + pip |
| 167 | + install |
| 168 | + ${ARG_REQUIREMENTS} |
| 169 | + RESULT_VARIABLE |
| 170 | + PIP_INSTALL_RESULT |
| 171 | + ) |
| 172 | + if(NOT PIP_INSTALL_RESULT EQUAL 0) |
| 173 | + message(FATAL_ERROR |
| 174 | + "Failed to install Python dependencies into " |
| 175 | + "${PYVENV_DIRECTORY}: ${ARG_REQUIREMENTS}" |
| 176 | + ) |
| 177 | + endif() |
| 178 | + endif() |
| 179 | + |
| 180 | + # Write the stamp files. |
| 181 | + file(WRITE "${STAMP_FILE1}" "${FIREBASE_PYTHON_HOST_EXECUTABLE}") |
| 182 | + file(WRITE "${STAMP_FILE2}" "${ARG_REQUIREMENTS}") |
| 183 | + |
| 184 | + set("${ARG_OUTVAR}" "${PYTHON_EXECUTABLE}" PARENT_SCOPE) |
| 185 | +endfunction(FirebaseSetupPythonInterpreter) |
0 commit comments