fix(sphinx): prefix sphinx binary paths with ./ in run script (#4201) (#4261)

When the sphinx binary target is in the root package, its short_path
contains no directory separator. Executing it via `env` in the `.run`
script searches `$PATH` instead of the current directory and fails with
`No such file or directory`.

Prefix `%SPHINX_RUNFILES_PATH%` and `%SPHINX_EXEC_PATH%` with `./` in
`sphinx_run_template.sh` so `env` resolves the binary relative to the
working directory.

Fixes #4201
diff --git a/news/4201.fixed.md b/news/4201.fixed.md
new file mode 100644
index 0000000..f686516
--- /dev/null
+++ b/news/4201.fixed.md
@@ -0,0 +1,3 @@
+(sphinxdocs) Fixed `sphinx_docs` `.run` targets failing to locate the `sphinx`
+binary when defined in the root package
+([#4201](https://github.com/bazel-contrib/rules_python/issues/4201)).
diff --git a/sphinxdocs/sphinxdocs/private/sphinx_run_template.sh b/sphinxdocs/sphinxdocs/private/sphinx_run_template.sh
index aa83757..b2371f6 100644
--- a/sphinxdocs/sphinxdocs/private/sphinx_run_template.sh
+++ b/sphinxdocs/sphinxdocs/private/sphinx_run_template.sh
@@ -18,7 +18,7 @@
     exit 1
 fi
 
-for path in "%SPHINX_RUNFILES_PATH%" "%SPHINX_EXEC_PATH%"; do
+for path in "./%SPHINX_RUNFILES_PATH%" "./%SPHINX_EXEC_PATH%"; do
   if [[ -e $path ]]; then
     sphinx=$path
     break
diff --git a/sphinxdocs/tests/sphinx_docs/BUILD.bazel b/sphinxdocs/tests/sphinx_docs/BUILD.bazel
index 99b84ce..87e014f 100644
--- a/sphinxdocs/tests/sphinx_docs/BUILD.bazel
+++ b/sphinxdocs/tests/sphinx_docs/BUILD.bazel
@@ -89,6 +89,9 @@
 py_test(
     name = "sphinx_docs_output_test",
     srcs = ["sphinx_docs_output_test.py"],
-    data = [":docs"],
+    data = [
+        ":docs",
+        ":docs.run",
+    ],
     deps = ["@pypi//absl_py"],
 )
diff --git a/sphinxdocs/tests/sphinx_docs/sphinx_docs_output_test.py b/sphinxdocs/tests/sphinx_docs/sphinx_docs_output_test.py
index f4e9104..a4a7067 100644
--- a/sphinxdocs/tests/sphinx_docs/sphinx_docs_output_test.py
+++ b/sphinxdocs/tests/sphinx_docs/sphinx_docs_output_test.py
@@ -42,6 +42,18 @@
         )
         self.assertTrue(os.path.exists(str(page_path)), f"Not found at {page_path}")
 
+    def test_sphinx_run_script_uses_relative_sphinx_paths(self):
+        # Verify the generated .run script prefixes the sphinx binary paths with "./"
+        # so `env` resolves the binary relative to the working directory instead of
+        # searching $PATH when the target is in the root package (where short_path
+        # has no slash). See https://github.com/bazel-contrib/rules_python/issues/4201.
+        docs_run_path = importlib.resources.files(sphinx_docs).joinpath("docs.run")
+        script_content = docs_run_path.read_text()
+        self.assertIn(
+            'for path in "./tests/sphinx_docs/sphinx-build" "./bazel-out/',
+            script_content,
+        )
+
 
 if __name__ == "__main__":
     absltest.main()