Skip to content

Commit 6ea6608

Browse files
simpkinsfacebook-github-bot
authored andcommitted
getdeps: update FBPythonBinary.cmake to generate executable files on Windows
Summary: On Windows, compile a small C executable to prepend to the zip file, to allow the resulting executable files to be run directly on Windows, without needing to explicitly invoke the Python interpreter to run the output file. Reviewed By: wez Differential Revision: D17733616 fbshipit-source-id: 989a93851412d0bbe1e7857aa9111db082f67a4c
1 parent 6a688be commit 6ea6608

File tree

2 files changed

+92
-16
lines changed

2 files changed

+92
-16
lines changed

build/fbcode_builder/CMake/FBPythonBinary.cmake

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ include(FBCMakeParseArgs)
2727
# If we fail to find python now we won't fail immediately, but
2828
# add_fb_python_executable() or add_fb_python_library() will fatal out if they
2929
# are used.
30-
if(NOT Python3_EXECUTABLE)
30+
if(NOT TARGET Python3::Interpreter)
3131
# CMake 3.12+ ships with a FindPython3.cmake module. Try using it first.
3232
# We find with QUIET here, since otherwise this generates some noisy warnings
3333
# on versions of CMake before 3.12
34-
find_package(Python3 COMPONENTS Interpreter QUIET)
34+
if (WIN32)
35+
# On Windows we need both the Intepreter as well as the Development
36+
# libraries.
37+
find_package(Python3 COMPONENTS Interpreter Development QUIET)
38+
else()
39+
find_package(Python3 COMPONENTS Interpreter QUIET)
40+
endif()
3541
if(Python3_Interpreter_FOUND)
3642
message(STATUS "Found Python 3: ${Python3_EXECUTABLE}")
3743
else()
@@ -41,10 +47,15 @@ if(NOT Python3_EXECUTABLE)
4147
if(NOT PYTHONINTERP_FOUND)
4248
set(Python_ADDITIONAL_VERSIONS 3 3.6 3.5 3.4 3.3 3.2 3.1)
4349
find_package(PythonInterp)
50+
# TODO: On Windows we require the Python libraries as well.
51+
# We currently do not search for them on this code path.
52+
# For now we require building with CMake 3.12+ on Windows, so that the
53+
# FindPython3 code path above is available.
4454
endif()
4555
if(PYTHONINTERP_FOUND)
4656
if("${PYTHON_VERSION_MAJOR}" GREATER_EQUAL 3)
4757
set(Python3_EXECUTABLE "${PYTHON_EXECUTABLE}")
58+
add_custom_target(Python3::Interpreter)
4859
else()
4960
string(
5061
CONCAT FBPY_FIND_PYTHON_ERR
@@ -67,6 +78,10 @@ set(
6778
FB_PY_TEST_DISCOVER_SCRIPT
6879
"${CMAKE_CURRENT_LIST_DIR}/FBPythonTestAddTests.cmake"
6980
)
81+
set(
82+
FB_PY_WIN_MAIN_C
83+
"${CMAKE_CURRENT_LIST_DIR}/fb_py_win_main.c"
84+
)
7085

7186
# An option to control the default installation location for
7287
# install_fb_python_library(). This is relative to ${CMAKE_INSTALL_PREFIX}
@@ -85,7 +100,7 @@ set(
85100
# run. If left unspecified, a __main__.py script must be present in the
86101
# manifest.
87102
#
88-
function(add_fb_python_executable EXE_NAME)
103+
function(add_fb_python_executable TARGET)
89104
fb_py_check_available()
90105

91106
# Parse the arguments
@@ -98,7 +113,7 @@ function(add_fb_python_executable EXE_NAME)
98113

99114
# Use add_fb_python_library() to perform most of our source handling
100115
add_fb_python_library(
101-
"${EXE_NAME}.main_lib"
116+
"${TARGET}.main_lib"
102117
BASE_DIR "${ARG_BASE_DIR}"
103118
NAMESPACE "${ARG_NAMESPACE}"
104119
SOURCES ${ARG_SOURCES}
@@ -107,11 +122,11 @@ function(add_fb_python_executable EXE_NAME)
107122

108123
set(
109124
manifest_files
110-
"$<TARGET_PROPERTY:${EXE_NAME}.main_lib.py_lib,INTERFACE_INCLUDE_DIRECTORIES>"
125+
"$<TARGET_PROPERTY:${TARGET}.main_lib.py_lib,INTERFACE_INCLUDE_DIRECTORIES>"
111126
)
112127
set(
113128
source_files
114-
"$<TARGET_PROPERTY:${EXE_NAME}.main_lib.py_lib,INTERFACE_SOURCES>"
129+
"$<TARGET_PROPERTY:${TARGET}.main_lib.py_lib,INTERFACE_SOURCES>"
115130
)
116131

117132
# The command to build the executable archive.
@@ -127,16 +142,25 @@ function(add_fb_python_executable EXE_NAME)
127142
set(make_py_args --manifest-separator "::" "$<JOIN:${manifest_files},::>")
128143
endif()
129144

130-
set(output_file "${EXE_NAME}")
145+
set(output_file "${TARGET}${CMAKE_EXECUTABLE_SUFFIX}")
146+
if(WIN32)
147+
set(zipapp_output "${TARGET}.py_zipapp")
148+
else()
149+
set(zipapp_output "${output_file}")
150+
endif()
151+
set(zipapp_output_file "${zipapp_output}")
152+
153+
set(is_dir_output FALSE)
131154
if(DEFINED ARG_TYPE)
132155
list(APPEND make_py_args "--type" "${ARG_TYPE}")
133156
if ("${ARG_TYPE}" STREQUAL "dir")
157+
set(is_dir_output TRUE)
134158
# CMake doesn't really seem to like having a directory specified as an
135159
# output; specify the __main__.py file as the output instead.
136-
set(output_file "${EXE_NAME}/__main__.py")
160+
set(zipapp_output_file "${zipapp_output}/__main__.py")
137161
list(APPEND
138162
extra_cmd_params
139-
COMMAND "${CMAKE_COMMAND}" -E remove_directory "${EXE_NAME}"
163+
COMMAND "${CMAKE_COMMAND}" -E remove_directory "${zipapp_output}"
140164
)
141165
endif()
142166
endif()
@@ -146,26 +170,51 @@ function(add_fb_python_executable EXE_NAME)
146170
endif()
147171

148172
add_custom_command(
149-
OUTPUT "${output_file}"
173+
OUTPUT "${zipapp_output_file}"
150174
${extra_cmd_params}
151175
COMMAND
152176
"${Python3_EXECUTABLE}" "${FB_MAKE_PYTHON_ARCHIVE}"
153-
-o "${EXE_NAME}"
177+
-o "${zipapp_output}"
154178
${make_py_args}
155179
DEPENDS
156180
${source_files}
157-
"${EXE_NAME}.main_lib.py_sources_built"
181+
"${TARGET}.main_lib.py_sources_built"
158182
"${FB_MAKE_PYTHON_ARCHIVE}"
159183
)
160184

161-
# Add an "ALL" target that depends on force ${EXE_NAME},
162-
# so that ${EXE_NAME} will be included in the default list of build targets.
163-
add_custom_target("${EXE_NAME}.GEN_PY_EXE" ALL DEPENDS "${output_file}")
185+
if(WIN32)
186+
if(is_dir_output)
187+
# TODO: generate a main executable that will invoke Python3
188+
# with the correct main module inside the output directory
189+
else()
190+
add_executable("${TARGET}.winmain" "${FB_PY_WIN_MAIN_C}")
191+
target_link_libraries("${TARGET}.winmain" Python3::Python)
192+
# The Python3::Python target doesn't seem to be set up completely
193+
# correctly on Windows for some reason, and we have to explicitly add
194+
# ${Python3_LIBRARY_DIRS} to the target link directories.
195+
target_link_directories(
196+
"${TARGET}.winmain"
197+
PUBLIC ${Python3_LIBRARY_DIRS}
198+
)
199+
add_custom_command(
200+
OUTPUT "${output_file}"
201+
DEPENDS "${TARGET}.winmain" "${zipapp_output_file}"
202+
COMMAND
203+
"cmd.exe" "/c" "copy" "/b"
204+
"${TARGET}.winmain${CMAKE_EXECUTABLE_SUFFIX}+${zipapp_output}"
205+
"${output_file}"
206+
)
207+
endif()
208+
endif()
209+
210+
# Add an "ALL" target that depends on force ${TARGET},
211+
# so that ${TARGET} will be included in the default list of build targets.
212+
add_custom_target("${TARGET}.GEN_PY_EXE" ALL DEPENDS "${output_file}")
164213

165214
# Allow resolving the executable path for the target that we generate
166215
# via a generator expression like:
167216
# "WATCHMAN_WAIT_PATH=$<TARGET_PROPERTY:watchman-wait.GEN_PY_EXE,EXECUTABLE>"
168-
set_property(TARGET "${EXE_NAME}.GEN_PY_EXE"
217+
set_property(TARGET "${TARGET}.GEN_PY_EXE"
169218
PROPERTY EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/${output_file}")
170219
endfunction()
171220

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Facebook, Inc. and its affiliates.
2+
3+
#define Py_LIMITED_API 1
4+
#define WIN32_LEAN_AND_MEAN
5+
6+
#include <Windows.h>
7+
#include <Python.h>
8+
#include <stdio.h>
9+
10+
int wmain() {
11+
/*
12+
* This executable will be prepended to the start of a Python ZIP archive.
13+
* Python will be able to directly execute the ZIP archive, so we simply
14+
* need to tell Py_Main() to run our own file. Duplicate the argument list
15+
* and add our file name to the beginning to tell Python what file to invoke.
16+
*/
17+
wchar_t** pyargv = malloc(sizeof(wchar_t*) * (__argc + 1));
18+
if (!pyargv) {
19+
fprintf(stderr, "error: failed to allocate argument vector\n");
20+
return 1;
21+
}
22+
pyargv[0] = __wargv[0];
23+
for (int n = 0; n < __argc; ++n) {
24+
pyargv[n + 1] = __wargv[n];
25+
}
26+
return Py_Main(__argc + 1, pyargv);
27+
}

0 commit comments

Comments
 (0)