feat(bzlmod): support entry_point macro (#1220)

Add `entry_point` macro to the repo generated by the `pip.parse`
extension.
This works by using the canonical label literal, so should work without
users
needing to add the spoke repos to the `use_repo` statement. We test this
by
having an extra folder in the `bzlmod` example.

Fixes #958.
diff --git a/.bazelrc b/.bazelrc
index e7e4af7..fe542b3 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -3,8 +3,8 @@
 # This lets us glob() up all the files inside the examples to make them inputs to tests
 # (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
 # To update these lines, run tools/bazel_integration_test/update_deleted_packages.sh
-build --deleted_packages=examples/build_file_generation,examples/build_file_generation/get_url,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/multi_python_versions,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_import,examples/py_proto_library,examples/relative_requirements,tests/compile_pip_requirements,tests/pip_repository_entry_points,tests/pip_deps
-query --deleted_packages=examples/build_file_generation,examples/build_file_generation/get_url,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/multi_python_versions,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_import,examples/py_proto_library,examples/relative_requirements,tests/compile_pip_requirements,tests/pip_repository_entry_points,tests/pip_deps
+build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_point,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
+query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_point,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
 
 test --test_output=errors
 
diff --git a/examples/bzlmod/BUILD.bazel b/examples/bzlmod/BUILD.bazel
index 3183608..e1f5790 100644
--- a/examples/bzlmod/BUILD.bazel
+++ b/examples/bzlmod/BUILD.bazel
@@ -35,6 +35,7 @@
 py_test(
     name = "test",
     srcs = ["test.py"],
+    main = "test.py",
     deps = [":lib"],
 )
 
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
index d2d7d63..145cebd 100644
--- a/examples/bzlmod/MODULE.bazel
+++ b/examples/bzlmod/MODULE.bazel
@@ -24,11 +24,19 @@
     "@python3_9_toolchains//:all",
 )
 
+interpreter = use_extension("@rules_python//python/extensions:interpreter.bzl", "interpreter")
+interpreter.install(
+    name = "interpreter_python3_9",
+    python_name = "python3_9",
+)
+use_repo(interpreter, "interpreter_python3_9")
+
 pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
 pip.parse(
     name = "pip",
     # Intentionally set it false because the "true" case is already covered by examples/bzlmod_build_file_generation
     incompatible_generate_aliases = False,
+    python_interpreter_target = "@interpreter_python3_9//:python",
     requirements_lock = "//:requirements_lock.txt",
     requirements_windows = "//:requirements_windows.txt",
 )
diff --git a/examples/bzlmod/entry_point/BUILD.bazel b/examples/bzlmod/entry_point/BUILD.bazel
new file mode 100644
index 0000000..dfc02b0
--- /dev/null
+++ b/examples/bzlmod/entry_point/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@pip//:requirements.bzl", "entry_point")
+load("@rules_python//python:defs.bzl", "py_test")
+
+alias(
+    name = "yamllint",
+    actual = entry_point("yamllint"),
+)
+
+py_test(
+    name = "entry_point_test",
+    srcs = ["test_entry_point.py"],
+    data = [
+        ":yamllint",
+    ],
+    env = {
+        "YAMLLINT_ENTRY_POINT": "$(rlocationpath :yamllint)",
+    },
+    main = "test_entry_point.py",
+    deps = ["@rules_python//python/runfiles"],
+)
diff --git a/examples/bzlmod/entry_point/test_entry_point.py b/examples/bzlmod/entry_point/test_entry_point.py
new file mode 100644
index 0000000..5a37458
--- /dev/null
+++ b/examples/bzlmod/entry_point/test_entry_point.py
@@ -0,0 +1,43 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import pathlib
+import subprocess
+import unittest
+
+from python.runfiles import runfiles
+
+
+class ExampleTest(unittest.TestCase):
+    def test_entry_point(self):
+        rlocation_path = os.environ.get("YAMLLINT_ENTRY_POINT")
+        assert (
+            rlocation_path is not None
+        ), "expected 'YAMLLINT_ENTRY_POINT' env variable to be set to rlocation of the tool"
+
+        entry_point = pathlib.Path(runfiles.Create().Rlocation(rlocation_path))
+        self.assertTrue(entry_point.exists(), f"'{entry_point}' does not exist")
+
+        proc = subprocess.run(
+            [str(entry_point), "--version"],
+            check=True,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        )
+        self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
index 406e121..5239fd5 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -387,6 +387,7 @@
             for p in bzl_packages
         ]),
         "%%MACRO_TMPL%%": macro_tmpl,
+        "%%NAME%%": rctx.attr.name,
         "%%REQUIREMENTS_LOCK%%": str(requirements_txt),
     })
 
diff --git a/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
index 1b2e217..b77bf39 100644
--- a/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
+++ b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
@@ -22,3 +22,10 @@
 
 def dist_info_requirement(name):
     return "%%MACRO_TMPL%%".format(_clean_name(name), "dist_info")
+
+def entry_point(pkg, script = None):
+    """entry_point returns the target of the canonical label of the package entrypoints.
+    """
+    if not script:
+        script = pkg
+    return "@@%%NAME%%_{}//:rules_python_wheel_entry_point_{}".format(_clean_name(pkg), script)