@@ -10,6 +10,74 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
1010load ("@rules_cc//cc:cc_library.bzl" , "cc_library" )
1111load ("@rules_cc//cc:cc_test.bzl" , "cc_test" )
1212
13+ def _pybind_py_env_test_impl (ctx ):
14+ toolchain = ctx .toolchains ["@rules_python//python:toolchain_type" ]
15+ py3_runtime = toolchain .py3_runtime
16+ if not py3_runtime :
17+ fail ("No python3 runtime found in toolchain" )
18+
19+ # On Windows, we cannot use the shell script wrapper.
20+ if ctx .target_platform_has_constraint (ctx .attr ._windows_constraint [platform_common .ConstraintValueInfo ]):
21+ return [
22+ DefaultInfo (
23+ executable = ctx .executable .binary ,
24+ runfiles = ctx .runfiles (files = [ctx .executable .binary ])
25+ .merge (ctx .attr .binary [DefaultInfo ].default_runfiles )
26+ .merge (ctx .runfiles (transitive_files = py3_runtime .files )),
27+ ),
28+ ]
29+
30+ interpreter = py3_runtime .interpreter
31+
32+ # Generate a wrapper script that sets PYTHONHOME and runs the C++ binary.
33+ script = ctx .actions .declare_file (ctx .label .name + ".sh" )
34+
35+ content = "#!/bin/bash\n "
36+ content += "if [ -z \" $RUNFILES_DIR\" ]; then\n "
37+ content += " if [ -d \" $0.runfiles\" ]; then\n "
38+ content += " RUNFILES_DIR=\" $0.runfiles\" \n "
39+ content += " else\n "
40+ content += " RUNFILES_DIR=\" $(dirname \" $0\" )/../..\" \n "
41+ content += " fi\n "
42+ content += "fi\n "
43+ content += "INTERPRETER_PATH=\" $RUNFILES_DIR/" + ctx .workspace_name + "/" + interpreter .short_path + "\" \n "
44+ content += "if [ ! -f \" $INTERPRETER_PATH\" ]; then\n "
45+ content += " INTERPRETER_PATH=$(find \" $RUNFILES_DIR\" -path \" */" + interpreter .short_path + "\" | head -n 1)\n "
46+ content += "fi\n "
47+ content += "export PYTHONHOME=$(dirname $(dirname $(readlink -f \" $INTERPRETER_PATH\" )))\n "
48+ content += "BINARY_PATH=\" $RUNFILES_DIR/" + ctx .workspace_name + "/" + ctx .executable .binary .short_path + "\" \n "
49+ content += "if [ ! -f \" $BINARY_PATH\" ]; then\n "
50+ content += " BINARY_PATH=$(find \" $RUNFILES_DIR\" -path \" */" + ctx .executable .binary .short_path + "\" | head -n 1)\n "
51+ content += "fi\n "
52+ content += "exec \" $BINARY_PATH\" \" $@\" \n "
53+
54+ ctx .actions .write (script , content , is_executable = True )
55+
56+ runfiles = ctx .runfiles (files = [script , ctx .executable .binary ])
57+ runfiles = runfiles .merge (ctx .attr .binary [DefaultInfo ].default_runfiles )
58+ runfiles = runfiles .merge (ctx .runfiles (transitive_files = py3_runtime .files ))
59+
60+ return [
61+ DefaultInfo (
62+ executable = script ,
63+ runfiles = runfiles ,
64+ ),
65+ ]
66+
67+ pybind_py_env_test = rule (
68+ implementation = _pybind_py_env_test_impl ,
69+ test = True ,
70+ attrs = {
71+ "binary" : attr .label (
72+ executable = True ,
73+ cfg = "target" ,
74+ mandatory = True ,
75+ ),
76+ "_windows_constraint" : attr .label (default = "@platforms//os:windows" ),
77+ },
78+ toolchains = ["@rules_python//python:toolchain_type" ],
79+ )
80+
1381def register_extension_info (** kwargs ):
1482 pass
1583
@@ -148,17 +216,35 @@ def pybind_library_test(
148216 # Mark common dependencies as required for build_cleaner.
149217 tags = tags + ["req_dep=%s" % dep for dep in PYBIND_DEPS ]
150218
151- cc_test (
152- name = name ,
219+ # Pop test-only attributes that cc_binary doesn't support.
220+ test_kwargs = {}
221+ for attr in ["size" , "timeout" , "flaky" , "shard_count" , "local" ]:
222+ if attr in kwargs :
223+ test_kwargs [attr ] = kwargs .pop (attr )
224+
225+ # Build the actual C++ binary.
226+ cc_binary (
227+ name = name + "_bin" ,
153228 copts = copts + PYBIND_COPTS ,
154229 features = features + PYBIND_FEATURES ,
155- tags = tags ,
230+ testonly = True ,
231+ visibility = ["//visibility:private" ],
156232 deps = deps + PYBIND_DEPS + [
157233 "@rules_python//python/cc:current_py_cc_libs" ,
158234 ],
159235 ** kwargs
160236 )
161237
238+ # Use a wrapper rule to set PYTHONHOME and run the binary.
239+ pybind_py_env_test (
240+ name = name ,
241+ binary = ":" + name + "_bin" ,
242+ testonly = True ,
243+ tags = tags ,
244+ visibility = kwargs .get ("visibility" ),
245+ ** test_kwargs
246+ )
247+
162248# Register extension with build_cleaner.
163249register_extension_info (
164250 extension = pybind_extension ,
0 commit comments