11"""Implementation of the zipapp rules."""
22
33load ("@bazel_skylib//lib:paths.bzl" , "paths" )
4+ load ("@rules_python_internal//:rules_python_config.bzl" , rp_config = "config" )
45load ("//python/private:attributes.bzl" , "apply_config_settings_attr" )
56load ("//python/private:builders.bzl" , "builders" )
6- load ("//python/private:common.bzl" , "BUILTIN_BUILD_PYTHON_ZIP" , "actions_run" , "maybe_builtin_build_python_zip" , "maybe_create_repo_mapping" , "runfiles_root_path" )
7+ load ("//python/private:common.bzl" , "BUILTIN_BUILD_PYTHON_ZIP" , "actions_run" , "maybe_builtin_build_python_zip" , "maybe_create_repo_mapping" , "runfiles_root_path" , "target_platform_has_any_constraint" )
78load ("//python/private:common_labels.bzl" , "labels" )
89load ("//python/private:py_executable_info.bzl" , "PyExecutableInfo" )
910load ("//python/private:py_internal.bzl" , "py_internal" )
1011load ("//python/private:py_runtime_info.bzl" , "PyRuntimeInfo" )
1112load ("//python/private:toolchain_types.bzl" , "EXEC_TOOLS_TOOLCHAIN_TYPE" )
1213load ("//python/private:transition_labels.bzl" , "TRANSITION_LABELS" )
1314
15+ _LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type"
16+
17+ def _find_launcher_maker (ctx ):
18+ if rp_config .bazel_9_or_later :
19+ return (ctx .toolchains [_LAUNCHER_MAKER_TOOLCHAIN_TYPE ].binary , _LAUNCHER_MAKER_TOOLCHAIN_TYPE )
20+ return (ctx .executable ._windows_launcher_maker , None )
21+
22+ def _create_windows_exe_launcher (
23+ ctx ,
24+ * ,
25+ output ,
26+ python_binary_path ,
27+ use_zip_file ):
28+ launch_info = ctx .actions .args ()
29+ launch_info .use_param_file ("%s" , use_always = True )
30+ launch_info .set_param_file_format ("multiline" )
31+ launch_info .add ("binary_type=Python" )
32+ launch_info .add (ctx .workspace_name , format = "workspace_name=%s" )
33+ launch_info .add (
34+ "1" if py_internal .runfiles_enabled (ctx ) else "0" ,
35+ format = "symlink_runfiles_enabled=%s" ,
36+ )
37+ launch_info .add (python_binary_path , format = "python_bin_path=%s" )
38+ launch_info .add ("1" if use_zip_file else "0" , format = "use_zip_file=%s" )
39+
40+ launcher = ctx .attr ._launcher [DefaultInfo ].files_to_run .executable
41+ executable , toolchain = _find_launcher_maker (ctx )
42+ ctx .actions .run (
43+ executable = executable ,
44+ arguments = [launcher .path , launch_info , output .path ],
45+ inputs = [launcher ],
46+ outputs = [output ],
47+ mnemonic = "PyBuildLauncher" ,
48+ progress_message = "Creating launcher for %{label}" ,
49+ # Needed to inherit PATH when using non-MSVC compilers like MinGW
50+ use_default_shell_env = True ,
51+ toolchain = toolchain ,
52+ )
53+
1454def _is_symlink (f ):
1555 if hasattr (f , "is_symlink" ):
1656 return str (int (f .is_symlink ))
@@ -184,20 +224,39 @@ def _py_zipapp_executable_impl(ctx):
184224
185225 zip_file = _create_zip (ctx , py_runtime , py_executable , stage2_bootstrap )
186226 if ctx .attr .executable :
187- preamble = _create_shell_bootstrap (ctx , py_runtime , py_executable , stage2_bootstrap )
188- executable = _create_self_executable_zip (ctx , preamble , zip_file )
189- default_output = executable
227+ if target_platform_has_any_constraint (ctx , ctx .attr ._windows_constraints ):
228+ executable = ctx .actions .declare_file (ctx .label .name + ".exe" )
229+
230+ python_exe = py_executable .venv_python_exe
231+ if python_exe :
232+ python_exe_path = runfiles_root_path (ctx , python_exe .short_path )
233+ elif py_runtime .interpreter :
234+ python_exe_path = runfiles_root_path (ctx , py_runtime .interpreter .short_path )
235+ else :
236+ python_exe_path = py_runtime .interpreter_path
237+
238+ _create_windows_exe_launcher (
239+ ctx ,
240+ output = executable ,
241+ python_binary_path = python_exe_path ,
242+ use_zip_file = True ,
243+ )
244+ default_output = depset ([executable , zip_file ])
245+ else :
246+ preamble = _create_shell_bootstrap (ctx , py_runtime , py_executable , stage2_bootstrap )
247+ executable = _create_self_executable_zip (ctx , preamble , zip_file )
248+ default_output = depset ([executable ])
190249 else :
191250 # Bazel requires executable=True rules to have an executable given, so give
192251 # a fake one to satisfy that.
193- default_output = zip_file
252+ default_output = depset ([ zip_file ])
194253 executable = ctx .actions .declare_file (ctx .label .name + "-not-executable" )
195254 ctx .actions .write (executable , "echo 'ERROR: Non executable zip file'; exit 1" )
196255
197256 return [
198257 DefaultInfo (
199- files = depset ([ default_output ]) ,
200- runfiles = ctx .runfiles (files = [ default_output ] ),
258+ files = default_output ,
259+ runfiles = ctx .runfiles (files = default_output . to_list () ),
201260 executable = executable ,
202261 ),
203262 ]
@@ -277,6 +336,18 @@ Whether the output should be an executable zip file.
277336 cfg = "exec" ,
278337 default = "//tools/private/zipapp:exe_zip_maker" ,
279338 ),
339+ "_launcher" : attr .label (
340+ cfg = "target" ,
341+ # NOTE: This is an executable, but is only used for Windows. It
342+ # can't have executable=True because the backing target is an
343+ # empty target for other platforms.
344+ default = "//tools/launcher:launcher" ,
345+ ),
346+ "_windows_constraints" : attr .label_list (
347+ default = [
348+ "@platforms//os:windows" ,
349+ ],
350+ ),
280351 "_zip_shell_template" : attr .label (
281352 default = ":zip_shell_template" ,
282353 allow_single_file = True ,
@@ -285,8 +356,15 @@ Whether the output should be an executable zip file.
285356 cfg = "exec" ,
286357 default = "//tools/private/zipapp:zipper" ,
287358 ),
288- }
289- _TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE ]
359+ } | ({
360+ "_windows_launcher_maker" : attr .label (
361+ default = "@bazel_tools//tools/launcher:launcher_maker" ,
362+ cfg = "exec" ,
363+ executable = True ,
364+ ),
365+ } if not rp_config .bazel_9_or_later else {})
366+
367+ _TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE ] + ([_LAUNCHER_MAKER_TOOLCHAIN_TYPE ] if rp_config .bazel_9_or_later else [])
290368
291369py_zipapp_binary = rule (
292370 doc = """
0 commit comments