Skip to content

Commit b4e29c3

Browse files
committed
Create individual Python virtualenv's in cmake builds (copied from firebase/firebase-ios-sdk#9662)
1 parent c2619dc commit b4e29c3

File tree

7 files changed

+222
-9
lines changed

7 files changed

+222
-9
lines changed

CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ if (NOT FIREBASE_ANDROID_STL STREQUAL "")
8686
set(ANDROID_STL ${FIREBASE_ANDROID_STL})
8787
endif()
8888

89-
set(FIREBASE_PYTHON_EXECUTABLE "python" CACHE FILEPATH
90-
"The Python interpreter to use, such as one from a venv")
91-
9289
set(FIREBASE_XCODE_TARGET_FORMAT "frameworks" CACHE STRING
9390
"Format to output, 'frameworks' or 'libraries'")
9491

analytics/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
# CMake file for the firebase_analytics library
1616

17+
include(python_setup)
18+
FirebaseSetupPythonInterpreter(
19+
OUTVAR MY_PYTHON_EXECUTABLE
20+
KEY Analytics
21+
REQUIREMENTS absl-py
22+
)
23+
1724
# Analytics generates header files for default events, parameters, and
1825
# properties based on the iOS SDK, that are used across all platforms.
1926
set(analytics_generated_headers_dir
@@ -29,7 +36,7 @@ file(MAKE_DIRECTORY ${analytics_generated_headers_dir})
2936
function(generate_analytics_header OBJC_FILE CPP_FILE)
3037
add_custom_command(
3138
OUTPUT ${CPP_FILE}
32-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/generate_constants.py"
39+
COMMAND ${MY_PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/generate_constants.py"
3340
"--objc_header=${OBJC_FILE}"
3441
"--cpp_header=${CPP_FILE}"
3542
DEPENDS ${OBJC_FILE}

app/CMakeLists.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
# CMake file for the firebase_app library
1616

17+
include(python_setup)
18+
FirebaseSetupPythonInterpreter(
19+
OUTVAR MY_PYTHON_EXECUTABLE
20+
KEY App
21+
REQUIREMENTS absl-py
22+
)
23+
1724
# Define how to generate google_services_resource_(source/header)
1825
binary_to_array("google_services_resource"
1926
"${CMAKE_CURRENT_LIST_DIR}/google_services.fbs"
@@ -47,7 +54,7 @@ add_custom_target(FIREBASE_APP_GENERATED_HEADERS DEPENDS "${version_header}")
4754
file(MAKE_DIRECTORY ${version_header_dir})
4855
add_custom_command(
4956
OUTPUT ${version_header}
50-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${FIREBASE_SCRIPT_DIR}/version_header.py"
57+
COMMAND ${MY_PYTHON_EXECUTABLE} "${FIREBASE_SCRIPT_DIR}/version_header.py"
5158
"--input_file=${FIREBASE_SCRIPT_DIR}/cpp_sdk_version.json"
5259
"--output_file=${version_header}"
5360
"--build_type=released"
@@ -430,7 +437,7 @@ if (IOS)
430437
function(generate_analytics_header OBJC_FILE CPP_FILE)
431438
add_custom_command(
432439
OUTPUT ${CPP_FILE}
433-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${FIREBASE_SOURCE_DIR}/analytics/generate_constants.py"
440+
COMMAND ${MY_PYTHON_EXECUTABLE} "${FIREBASE_SOURCE_DIR}/analytics/generate_constants.py"
434441
"--objc_header=${OBJC_FILE}"
435442
"--cpp_header=${CPP_FILE}"
436443
DEPENDS ${OBJC_FILE}

cmake/binary_to_array.cmake

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
# CPP_NAMESPACE: The namespace to use in the generated files.
2727
# OUTPUT_DIRECTORY: Where the generated files should be written to.
2828
function(binary_to_array NAME INPUT CPP_NAMESPACE OUTPUT_DIRECTORY)
29+
include(python_setup)
30+
FirebaseSetupPythonInterpreter(
31+
OUTVAR MY_PYTHON_EXECUTABLE
32+
KEY BinaryToArray
33+
REQUIREMENTS absl-py
34+
)
35+
2936
# Guarantee the output directory exists
3037
file(MAKE_DIRECTORY ${OUTPUT_DIRECTORY})
3138

@@ -39,7 +46,7 @@ function(binary_to_array NAME INPUT CPP_NAMESPACE OUTPUT_DIRECTORY)
3946
OUTPUT ${output_source}
4047
${output_header}
4148
DEPENDS ${INPUT}
42-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${FIREBASE_SCRIPT_DIR}/binary_to_array.py"
49+
COMMAND ${MY_PYTHON_EXECUTABLE} "${FIREBASE_SCRIPT_DIR}/binary_to_array.py"
4350
"--input=${INPUT}"
4451
"--output_header=${output_header}"
4552
"--output_source=${output_source}"

cmake/python_setup.cmake

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Copyright 2022 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 an isolated Python interpreter, installing required dependencies.
16+
#
17+
# This function does the following:
18+
# 1. Finds a Python interpreter using the best-available built-in cmake
19+
# mechanism do do so. This is referred to as the "host" interpreter.
20+
# 2. Creates a Python virtualenv in the cmake binary directory using the
21+
# host Python interpreter found in the previous step.
22+
# 3. Locates the Python interpreter in the virtualenv and sets its path in
23+
# the specified OUTVAR variable.
24+
# 4. Runs `pip install` to install the specified required dependencies, if any,
25+
# in the virtualenv.
26+
#
27+
# This function also writes "stamp files" into the virtualenv. These files
28+
# are used to determine if the virtualenv is up-to-date from a previous cmake
29+
# run or if it needs to be recreated from scratch. It will simply be re-used if
30+
# possible.
31+
#
32+
# If any errors occur (e.g. cannot install one of the given requirements) then a
33+
# fatal error is logged, causing the cmake processing to terminate.
34+
#
35+
# See https://docs.python.org/3/library/venv.html for details about virtualenv.
36+
#
37+
# Arguments:
38+
# OUTVAR - The name of the variable into which to store the path of the
39+
# Python executable from the virtualenv.
40+
# KEY - A unique key to ensure isolation from other Python virtualenv
41+
# environments created by this function. This value will be incorporated
42+
# into the path of the virtualenv and incorporated into the name of the
43+
# cmake cache variable that stores its path.
44+
# REQUIREMENTS - (Optional) A list of Python packages to install in the
45+
# virtualenv. These will be given as arguments to `pip install`.
46+
#
47+
# Example:
48+
# include(python_setup)
49+
# FirebaseSetupPythonInterpreter(
50+
# OUTVAR MY_PYTHON_EXECUTABLE
51+
# KEY ScanStuff
52+
# REQUIREMENTS six absl-py
53+
# )
54+
# execute_process(COMMAND "${MY_PYTHON_EXECUTABLE}" scan_stuff.py)
55+
function(FirebaseSetupPythonInterpreter)
56+
cmake_parse_arguments(
57+
PARSE_ARGV 0
58+
ARG
59+
"" # zero-value arguments
60+
"OUTVAR;KEY" # single-value arguments
61+
"REQUIREMENTS" # multi-value arguments
62+
)
63+
64+
# Validate this function's arguments.
65+
if("${ARG_OUTVAR}" STREQUAL "")
66+
message(FATAL_ERROR "OUTVAR must be specified to ${CMAKE_CURRENT_FUNCTION}")
67+
elseif("${ARG_KEY}" STREQUAL "")
68+
message(FATAL_ERROR "KEY must be specified to ${CMAKE_CURRENT_FUNCTION}")
69+
endif()
70+
71+
# Calculate the name of the cmake *cache* variable into which to store the
72+
# path of the Python interpreter from the virtualenv.
73+
set(CACHEVAR "FIREBASE_PYTHON_EXECUTABLE_${ARG_KEY}")
74+
75+
set(LOG_PREFIX "${CMAKE_CURRENT_FUNCTION}(${ARG_KEY})")
76+
77+
# Find a "host" Python interpreter using the best available mechanism.
78+
if(${CMAKE_VERSION} VERSION_LESS "3.12")
79+
include(FindPythonInterp)
80+
set(DEFAULT_PYTHON_HOST_EXECUTABLE "${PYTHON_EXECUTABLE}")
81+
else()
82+
find_package(Python3 COMPONENTS Interpreter REQUIRED)
83+
set(DEFAULT_PYTHON_HOST_EXECUTABLE "${Python3_EXECUTABLE}")
84+
endif()
85+
86+
# Get the host Python interpreter on the host system to use.
87+
set(
88+
FIREBASE_PYTHON_HOST_EXECUTABLE
89+
"${DEFAULT_PYTHON_HOST_EXECUTABLE}"
90+
CACHE FILEPATH
91+
"The Python interpreter on the host system to use"
92+
)
93+
94+
# Check if the virtualenv is already up-to-date by examining the contents of
95+
# its 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+
set(PYVENV_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pyvenv/${ARG_KEY}")
100+
set(STAMP_FILE1 "${PYVENV_DIRECTORY}/cmake_firebase_python_stamp1.txt")
101+
set(STAMP_FILE2 "${PYVENV_DIRECTORY}/cmake_firebase_python_stamp2.txt")
102+
103+
if(EXISTS "${STAMP_FILE1}" AND EXISTS "${STAMP_FILE2}")
104+
file(READ "${STAMP_FILE1}" STAMP_FILE1_CONTENTS)
105+
file(READ "${STAMP_FILE2}" STAMP_FILE2_CONTENTS)
106+
if(
107+
("${STAMP_FILE1_CONTENTS}" STREQUAL "${FIREBASE_PYTHON_HOST_EXECUTABLE}")
108+
AND
109+
("${STAMP_FILE2_CONTENTS}" STREQUAL "${ARG_REQUIREMENTS}")
110+
)
111+
set("${ARG_OUTVAR}" "$CACHE{${CACHEVAR}}" PARENT_SCOPE)
112+
message(STATUS "${LOG_PREFIX}: Using Python interpreter: $CACHE{${CACHEVAR}}")
113+
return()
114+
endif()
115+
endif()
116+
117+
# Create the virtualenv.
118+
message(STATUS
119+
"${LOG_PREFIX}: Creating Python virtualenv in ${PYVENV_DIRECTORY} "
120+
"using ${FIREBASE_PYTHON_HOST_EXECUTABLE}"
121+
)
122+
file(REMOVE_RECURSE "${PYVENV_DIRECTORY}")
123+
execute_process(
124+
COMMAND
125+
"${FIREBASE_PYTHON_HOST_EXECUTABLE}"
126+
-m
127+
venv
128+
"${PYVENV_DIRECTORY}"
129+
RESULT_VARIABLE
130+
FIREBASE_PYVENV_CREATE_RESULT
131+
)
132+
if(NOT FIREBASE_PYVENV_CREATE_RESULT EQUAL 0)
133+
message(FATAL_ERROR
134+
"Failed to create a Python virtualenv in ${PYVENV_DIRECTORY} "
135+
"using ${FIREBASE_PYTHON_HOST_EXECUTABLE}")
136+
endif()
137+
138+
# Find the Python interpreter in the virtualenv.
139+
find_program(
140+
"${CACHEVAR}"
141+
DOC "The Python interpreter to use for ${ARG_KEY}"
142+
NAMES python3 python
143+
PATHS "${PYVENV_DIRECTORY}"
144+
PATH_SUFFIXES bin Scripts
145+
NO_DEFAULT_PATH
146+
)
147+
if(NOT ${CACHEVAR})
148+
message(FATAL_ERROR "Unable to find Python executable in ${PYVENV_DIRECTORY}")
149+
else()
150+
set(PYTHON_EXECUTABLE "$CACHE{${CACHEVAR}}")
151+
message(STATUS "${LOG_PREFIX}: Found Python executable in virtualenv: ${PYTHON_EXECUTABLE}")
152+
endif()
153+
154+
# Install the dependencies in the virtualenv, if any are requested.
155+
if(NOT ("${ARG_REQUIREMENTS}" STREQUAL ""))
156+
message(STATUS
157+
"${LOG_PREFIX}: Installing Python dependencies into "
158+
"${PYVENV_DIRECTORY}: ${ARG_REQUIREMENTS}"
159+
)
160+
execute_process(
161+
COMMAND
162+
"${PYTHON_EXECUTABLE}"
163+
-m
164+
pip
165+
install
166+
${ARG_REQUIREMENTS}
167+
RESULT_VARIABLE
168+
PIP_INSTALL_RESULT
169+
)
170+
if(NOT PIP_INSTALL_RESULT EQUAL 0)
171+
message(FATAL_ERROR
172+
"Failed to install Python dependencies into "
173+
"${PYVENV_DIRECTORY}: ${ARG_REQUIREMENTS}"
174+
)
175+
endif()
176+
endif()
177+
178+
# Write the stamp files.
179+
file(WRITE "${STAMP_FILE1}" "${FIREBASE_PYTHON_HOST_EXECUTABLE}")
180+
file(WRITE "${STAMP_FILE2}" "${ARG_REQUIREMENTS}")
181+
182+
set("${ARG_OUTVAR}" "${PYTHON_EXECUTABLE}" PARENT_SCOPE)
183+
endfunction(FirebaseSetupPythonInterpreter)

external/vcpkg_custom_data/toolchains/linux_32.cmake

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
include(python_setup)
16+
FirebaseSetupPythonInterpreter(
17+
OUTVAR MY_PYTHON_EXECUTABLE
18+
KEY VcpkgLinux32
19+
)
20+
1521
# Toolchain settings for building 32-bit Linux libraries
1622
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
1723
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
@@ -436,7 +442,7 @@ function(add_executable name)
436442
elseif(_VCPKG_TARGET_TRIPLET_PLAT MATCHES "osx")
437443
if (NOT MACOSX_BUNDLE_IDX EQUAL -1)
438444
add_custom_command(TARGET ${name} POST_BUILD
439-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} ${_VCPKG_TOOLCHAIN_DIR}/osx/applocal.py
445+
COMMAND ${MY_PYTHON_EXECUTABLE} ${_VCPKG_TOOLCHAIN_DIR}/osx/applocal.py
440446
$<TARGET_FILE:${name}>
441447
"${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$<CONFIG:Debug>:/debug>"
442448
)

external/vcpkg_custom_data/toolchains/macos_arm64.cmake

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
include(python_setup)
16+
FirebaseSetupPythonInterpreter(
17+
OUTVAR MY_PYTHON_EXECUTABLE
18+
KEY VcpkgMacOSArm64
19+
)
20+
1521
# Toolchain settings for building ARM64 MacOS libraries
1622
set(VCPKG_TARGET_ARCHITECTURE arm64)
1723
set(VCPKG_CRT_LINKAGE dynamic)
@@ -438,7 +444,7 @@ function(add_executable name)
438444
elseif(_VCPKG_TARGET_TRIPLET_PLAT MATCHES "osx")
439445
if (NOT MACOSX_BUNDLE_IDX EQUAL -1)
440446
add_custom_command(TARGET ${name} POST_BUILD
441-
COMMAND ${FIREBASE_PYTHON_EXECUTABLE} ${_VCPKG_TOOLCHAIN_DIR}/osx/applocal.py
447+
COMMAND ${MY_PYTHON_EXECUTABLE} ${_VCPKG_TOOLCHAIN_DIR}/osx/applocal.py
442448
$<TARGET_FILE:${name}>
443449
"${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$<CONFIG:Debug>:/debug>"
444450
)

0 commit comments

Comments
 (0)