pybind_library_test: fix PYTHONHOME setup

build_defs.bzl:
* Added a new pybind_py_env_test rule. This rule resolves the Python toolchain at analysis time, extracts the runtime
  path, and generates a shell wrapper script.
* The wrapper script dynamically identifies the absolute path to the Python runtime within the Bazel runfiles and sets
  the PYTHONHOME environment variable before executing the actual C++ test binary.
* Updated the pybind_library_test macro to build the C++ code as a private cc_binary and then wrap it with
  pybind_py_env_test.
diff --git a/build_defs.bzl b/build_defs.bzl
index fca4bca..6b0fd2b 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -10,6 +10,80 @@
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
 load("@rules_cc//cc:cc_test.bzl", "cc_test")
 
+def _pybind_py_env_test_impl(ctx):
+    toolchain = ctx.toolchains["@rules_python//python:toolchain_type"]
+    py3_runtime = toolchain.py3_runtime
+    if not py3_runtime:
+        fail("No python3 runtime found in toolchain")
+
+    # On Windows, we cannot use the shell script wrapper.
+    if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
+        # On Windows, we need to return an executable created by this rule.
+        # We create a symlink to the actual binary.
+        # We use the same extension as the original binary (usually .exe).
+        extension = ctx.executable.binary.extension
+        executable = ctx.actions.declare_file(ctx.label.name + ("." + extension if extension else ""))
+        ctx.actions.symlink(output = executable, target_file = ctx.executable.binary, is_executable = True)
+        return [
+            DefaultInfo(
+                executable = executable,
+                runfiles = ctx.runfiles(files = [executable])
+                    .merge(ctx.attr.binary[DefaultInfo].default_runfiles)
+                    .merge(ctx.runfiles(transitive_files = py3_runtime.files)),
+            ),
+        ]
+
+    interpreter = py3_runtime.interpreter
+
+    # Generate a wrapper script that sets PYTHONHOME and runs the C++ binary.
+    script = ctx.actions.declare_file(ctx.label.name + ".sh")
+
+    content = "#!/bin/bash\n"
+    content += "if [ -z \"$RUNFILES_DIR\" ]; then\n"
+    content += "  if [ -d \"$0.runfiles\" ]; then\n"
+    content += "    RUNFILES_DIR=\"$0.runfiles\"\n"
+    content += "  else\n"
+    content += "    RUNFILES_DIR=\"$(dirname \"$0\")/../..\"\n"
+    content += "  fi\n"
+    content += "fi\n"
+    content += "INTERPRETER_PATH=\"$RUNFILES_DIR/" + ctx.workspace_name + "/" + interpreter.short_path + "\"\n"
+    content += "if [ ! -f \"$INTERPRETER_PATH\" ]; then\n"
+    content += "  INTERPRETER_PATH=$(find \"$RUNFILES_DIR\" -path \"*/" + interpreter.short_path + "\" | head -n 1)\n"
+    content += "fi\n"
+    content += "export PYTHONHOME=$(dirname $(dirname $(readlink -f \"$INTERPRETER_PATH\")))\n"
+    content += "BINARY_PATH=\"$RUNFILES_DIR/" + ctx.workspace_name + "/" + ctx.executable.binary.short_path + "\"\n"
+    content += "if [ ! -f \"$BINARY_PATH\" ]; then\n"
+    content += "  BINARY_PATH=$(find \"$RUNFILES_DIR\" -path \"*/" + ctx.executable.binary.short_path + "\" | head -n 1)\n"
+    content += "fi\n"
+    content += "exec \"$BINARY_PATH\" \"$@\"\n"
+
+    ctx.actions.write(script, content, is_executable = True)
+
+    runfiles = ctx.runfiles(files = [script, ctx.executable.binary])
+    runfiles = runfiles.merge(ctx.attr.binary[DefaultInfo].default_runfiles)
+    runfiles = runfiles.merge(ctx.runfiles(transitive_files = py3_runtime.files))
+
+    return [
+        DefaultInfo(
+            executable = script,
+            runfiles = runfiles,
+        ),
+    ]
+
+pybind_py_env_test = rule(
+    implementation = _pybind_py_env_test_impl,
+    test = True,
+    attrs = {
+        "binary": attr.label(
+            executable = True,
+            cfg = "target",
+            mandatory = True,
+        ),
+        "_windows_constraint": attr.label(default = "@platforms//os:windows"),
+    },
+    toolchains = ["@rules_python//python:toolchain_type"],
+)
+
 def register_extension_info(**kwargs):
     pass
 
@@ -148,17 +222,35 @@
     # Mark common dependencies as required for build_cleaner.
     tags = tags + ["req_dep=%s" % dep for dep in PYBIND_DEPS]
 
-    cc_test(
-        name = name,
+    # Pop test-only attributes that cc_binary doesn't support.
+    test_kwargs = {}
+    for attr in ["size", "timeout", "flaky", "shard_count", "local"]:
+        if attr in kwargs:
+            test_kwargs[attr] = kwargs.pop(attr)
+
+    # Build the actual C++ binary.
+    cc_binary(
+        name = name + "_bin",
         copts = copts + PYBIND_COPTS,
         features = features + PYBIND_FEATURES,
-        tags = tags,
+        testonly = True,
+        visibility = ["//visibility:private"],
         deps = deps + PYBIND_DEPS + [
             "@rules_python//python/cc:current_py_cc_libs",
         ],
         **kwargs
     )
 
+    # Use a wrapper rule to set PYTHONHOME and run the binary.
+    pybind_py_env_test(
+        name = name,
+        binary = ":" + name + "_bin",
+        testonly = True,
+        tags = tags,
+        visibility = kwargs.get("visibility"),
+        **test_kwargs
+    )
+
 # Register extension with build_cleaner.
 register_extension_info(
     extension = pybind_extension,