workflows: publish sphinxdocs in a separate BCR pull request (#4409)

Publishing `rules_python`, `rules_python_gazelle_plugin`, and
`sphinxdocs` in a single BCR pull request couples `sphinxdocs` presubmit
and review to `rules_python` releases.

Bump `bazel-contrib/publish-to-bcr` to `v1.7.0`, restrict the `publish`
job to `.` and `gazelle`, and add a `publish_sphinxdocs` job with
`module_roots: sphinxdocs` so `sphinxdocs` is submitted in its own BCR
pull request.
diff --git a/.bcr/BUILD.bazel b/.bcr/BUILD.bazel
new file mode 100644
index 0000000..1966da7
--- /dev/null
+++ b/.bcr/BUILD.bazel
@@ -0,0 +1,5 @@
+filegroup(
+    name = "config_yml",
+    srcs = ["config.yml"],
+    visibility = ["//:__subpackages__"],
+)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index a64ed33..146bdde 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -24,9 +24,30 @@
 
 jobs:
   publish:
-    uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.5.1
+    uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.7.0
     with:
       tag_name: ${{ inputs.tag_name }}
+      module_roots: |
+        .
+        gazelle
+      # GitHub repository which is a fork of the upstream where the Pull Request will be opened.
+      registry_fork: bazel-contrib/bazel-central-registry
+      attest: false
+      # Create non-draft PRs so the BCR bazel-io bot processes them. Otherwise,
+      # since a bazel-contrib bot, we have to wait for BCR maintainers to mark it
+      # non-draft or approve it.
+      draft: false
+    permissions:
+      contents: write
+    secrets:
+      # Necessary to push to the BCR fork, and to open a pull request against a registry
+      publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }}
+
+  publish_sphinxdocs:
+    uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.7.0
+    with:
+      tag_name: ${{ inputs.tag_name }}
+      module_roots: sphinxdocs
       # GitHub repository which is a fork of the upstream where the Pull Request will be opened.
       registry_fork: bazel-contrib/bazel-central-registry
       attest: false
diff --git a/tests/workflows/BUILD.bazel b/tests/workflows/BUILD.bazel
index 3fdcb98..1f99e5a 100644
--- a/tests/workflows/BUILD.bazel
+++ b/tests/workflows/BUILD.bazel
@@ -55,7 +55,10 @@
 pytest_test(
     name = "workflow_permissions_test",
     srcs = ["workflow_permissions_test.py"],
-    data = ["//.github/workflows:all_files"],
+    data = [
+        "//.bcr:config_yml",
+        "//.github/workflows:all_files",
+    ],
     target_compatible_with = NOT_WINDOWS,
     deps = [
         "//python/runfiles",
diff --git a/tests/workflows/workflow_permissions_test.py b/tests/workflows/workflow_permissions_test.py
index 67b1ee4..81153bd 100644
--- a/tests/workflows/workflow_permissions_test.py
+++ b/tests/workflows/workflow_permissions_test.py
@@ -143,3 +143,27 @@
     report_failure_needs = set(jobs["report_failure"]["needs"])
     expected_needs = set(jobs.keys()) - {"noop", "report_failure"}
     assert report_failure_needs == expected_needs
+
+
+def test_publish_bcr_splits_sphinxdocs_pr():
+    """Validates that publish.yml publishes sphinxdocs in a separate job."""
+    rf = runfiles.CreateOrRaise()
+    publish_path = rf.root() / "rules_python/.github/workflows/publish.yml"
+    bcr_config_path = rf.root() / "rules_python/.bcr/config.yml"
+
+    publish_content = yaml.safe_load(publish_path.read_text(encoding="utf-8"))
+    bcr_config = yaml.safe_load(bcr_config_path.read_text(encoding="utf-8"))
+
+    jobs = publish_content["jobs"]
+    assert set(jobs.keys()) == {"publish", "publish_sphinxdocs"}
+
+    def _parse_module_roots(job: dict) -> list[str]:
+        raw = job["with"]["module_roots"]
+        return [line.strip() for line in raw.splitlines() if line.strip()]
+
+    publish_roots = _parse_module_roots(jobs["publish"])
+    sphinxdocs_roots = _parse_module_roots(jobs["publish_sphinxdocs"])
+
+    assert publish_roots == [".", "gazelle"]
+    assert sphinxdocs_roots == ["sphinxdocs"]
+    assert set(publish_roots) | set(sphinxdocs_roots) == set(bcr_config["moduleRoots"])