Skip to content

Commit b4ff67b

Browse files
committed
feat! add entry_point macro to rules_python
Introduce a new `entry_point` macro to `rules_python` as opposed to the `hub` repository which allows users to generate an `entry_point` script for a given package. This will check the `console_scripts` key in the `entry_points.txt` dist-info file and avoids eager fetching of third party repositories because it is a `genrule` and a `py_binary` underneath the hood and exists in `rules_python`. This is a breaking change for bzlmod users as they will have to start using `@rules_python//python:entry_point.bzl`. For others this new macro is available to be used, but the old code is still present. Fixes bazel-contrib#1362 Fixes bazel-contrib#543 Fixes bazel-contrib#979 Fixes bazel-contrib#1262 Closes bazel-contrib#980 Closes bazel-contrib#1294
1 parent b4ab34e commit b4ff67b

16 files changed

+637
-9
lines changed

docs/BUILD.bazel

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package(default_visibility = ["//visibility:public"])
2222
licenses(["notice"]) # Apache 2.0
2323

2424
_DOCS = {
25+
"entry_point": "//docs:entry-point",
2526
"packaging": "//docs:packaging-docs",
2627
"pip": "//docs:pip-docs",
2728
"pip_repository": "//docs:pip-repository",
@@ -87,6 +88,15 @@ bzl_library(
8788
],
8889
)
8990

91+
bzl_library(
92+
name = "entry_point_bzl",
93+
srcs = [
94+
"//python:entry_point.bzl",
95+
"//python:py_binary.bzl",
96+
"//python/private:util_bzl",
97+
],
98+
)
99+
90100
# TODO: Stardoc does not guarantee consistent outputs accross platforms (Unix/Windows).
91101
# As a result we do not build or test docs on Windows.
92102
_NOT_WINDOWS = select({
@@ -128,6 +138,16 @@ stardoc(
128138
],
129139
)
130140

141+
stardoc(
142+
name = "entry-point",
143+
out = "entry_point.md_",
144+
input = "//python:entry_point.bzl",
145+
target_compatible_with = _NOT_WINDOWS,
146+
deps = [
147+
":entry_point_bzl",
148+
],
149+
)
150+
131151
stardoc(
132152
name = "packaging-docs",
133153
out = "packaging.md_",

docs/entry_point.md

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/bzlmod/MODULE.bazel

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ pip.parse(
113113
"@whl_mods_hub//:wheel.json": "wheel",
114114
},
115115
)
116-
117-
# NOTE: The pip_39 repo is only used because the plain `@pip` repo doesn't
118-
# yet support entry points; see https://github.com/bazelbuild/rules_python/issues/1262
119-
use_repo(pip, "pip", "pip_39")
116+
use_repo(pip, "pip")
120117

121118
bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
122119
local_path_override(

examples/bzlmod/entry_point/BUILD.bazel

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
1-
load("@pip_39//:requirements.bzl", "entry_point")
21
load("@rules_python//python:defs.bzl", "py_test")
2+
load("@rules_python//python:entry_point.bzl", "entry_point")
33

4-
alias(
4+
entry_point(
55
name = "yamllint",
6-
actual = entry_point("yamllint"),
6+
pkg = "@pip//yamllint",
7+
# yamllint does not have any other scripts except 'yamllint' so the
8+
# user does not have to specify which console script we should chose from
9+
# the package.
10+
)
11+
12+
entry_point(
13+
name = "pylint",
14+
pkg = "@pip//pylint",
15+
# Because `pylint` has multiple console_scripts available, we have to
16+
# specify which we want
17+
script = "pylint",
18+
deps = [
19+
# One can add extra dependencies to the entry point.
20+
"@pip//pylint_print",
21+
],
722
)
823

924
py_test(
1025
name = "entry_point_test",
1126
srcs = ["test_entry_point.py"],
1227
data = [
28+
":pylint",
1329
":yamllint",
1430
],
1531
env = {
32+
"PYLINT_ENTRY_POINT": "$(rlocationpath :pylint)",
1633
"YAMLLINT_ENTRY_POINT": "$(rlocationpath :yamllint)",
1734
},
1835
main = "test_entry_point.py",

examples/bzlmod/entry_point/test_entry_point.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
import os
1616
import pathlib
1717
import subprocess
18+
import tempfile
1819
import unittest
1920

2021
from python.runfiles import runfiles
2122

2223

2324
class ExampleTest(unittest.TestCase):
24-
def test_entry_point(self):
25+
def test_yamllint_entry_point(self):
2526
rlocation_path = os.environ.get("YAMLLINT_ENTRY_POINT")
2627
assert (
2728
rlocation_path is not None
@@ -38,6 +39,61 @@ def test_entry_point(self):
3839
)
3940
self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
4041

42+
def test_pylint_entry_point(self):
43+
rlocation_path = os.environ.get("PYLINT_ENTRY_POINT")
44+
assert (
45+
rlocation_path is not None
46+
), "expected 'PYLINT_ENTRY_POINT' env variable to be set to rlocation of the tool"
47+
48+
entry_point = pathlib.Path(runfiles.Create().Rlocation(rlocation_path))
49+
self.assertTrue(entry_point.exists(), f"'{entry_point}' does not exist")
50+
51+
proc = subprocess.run(
52+
[str(entry_point), "--version"],
53+
check=True,
54+
stdout=subprocess.PIPE,
55+
stderr=subprocess.PIPE,
56+
)
57+
self.assertRegex(proc.stdout.decode("utf-8").strip(), "^pylint 2\.15\.9")
58+
59+
def test_pylint_entry_point_deps(self):
60+
rlocation_path = os.environ.get("PYLINT_ENTRY_POINT")
61+
entry_point = pathlib.Path(runfiles.Create().Rlocation(rlocation_path))
62+
63+
with tempfile.TemporaryDirectory() as tmpdir:
64+
tmpdir = pathlib.Path(tmpdir)
65+
script = tmpdir / "hello_world.py"
66+
script.write_text(
67+
"""\
68+
\"\"\"
69+
a module to demonstrate the pylint-print checker
70+
\"\"\"
71+
72+
if __name__ == "__main__":
73+
print("Hello, World!")
74+
"""
75+
)
76+
77+
proc = subprocess.run(
78+
[
79+
str(entry_point),
80+
str(script),
81+
"--output-format=text",
82+
"--load-plugins=pylint_print",
83+
],
84+
stdout=subprocess.PIPE,
85+
stderr=subprocess.PIPE,
86+
cwd=tmpdir,
87+
)
88+
89+
want = """\
90+
************* Module hello_world
91+
hello_world.py:6:4: W8201: Logging should be used instead of the print() function. (print-function)
92+
93+
-----------------------------------
94+
Your code has been rated at 5.00/10"""
95+
self.assertEqual(want, proc.stdout.decode("utf-8").strip())
96+
4197

4298
if __name__ == "__main__":
4399
unittest.main()

examples/bzlmod/requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ s3cmd~=2.1.0
77
yamllint>=1.28.0
88
tabulate~=0.9.0
99
pylint~=2.15.5
10+
pylint-print
1011
python-dateutil>=2.8.2

examples/bzlmod/requirements_lock_3_10.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ platformdirs==3.5.1 \
8383
pylint==2.15.10 \
8484
--hash=sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e \
8585
--hash=sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5
86+
# via
87+
# -r requirements.in
88+
# pylint-print
89+
pylint-print==1.0.1 \
90+
--hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
91+
--hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
8692
# via -r requirements.in
8793
python-dateutil==2.8.2 \
8894
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \

examples/bzlmod/requirements_lock_3_9.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ platformdirs==2.6.0 \
6666
pylint==2.15.9 \
6767
--hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
6868
--hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
69+
# via
70+
# -r requirements.in
71+
# pylint-print
72+
pylint-print==1.0.1 \
73+
--hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
74+
--hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
6975
# via -r requirements.in
7076
python-dateutil==2.8.2 \
7177
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \

python/entry_point.bzl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2023 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
A macro to generate an entry_point from reading the 'console_scripts'.
17+
"""
18+
19+
load("//python:py_binary.bzl", "py_binary")
20+
21+
_tool = Label("//python/pip_install/tools/entry_point_generator")
22+
23+
def entry_point(*, name, pkg, script = None, deps = None, main = None, **kwargs):
24+
"""Generate an entry_point for a given package
25+
26+
Args:
27+
name: The name of the resultant py_binary target.
28+
pkg: The package for which to generate the script.
29+
script: The console script that the entry_point is going to be
30+
generated. Mandatory if there are more than 1 console_script in the
31+
package.
32+
deps: The extra dependencies to add to the py_binary rule.
33+
main: The file to be written by the templating engine. Defaults to
34+
`rules_python_entry_point_{name}.py`.
35+
**kwargs: Extra parameters forwarded to py_binary.
36+
"""
37+
main = main or "rules_python_entry_point_{}.py".format(name)
38+
39+
# TODO @aignas 2023-08-05: Ideally this could be implemented as a rule that is using
40+
# the Python toolchain, but this should be functional and establish the API.
41+
native.genrule(
42+
name = name + "_gen",
43+
cmd = "$(location {tool}) {args} $(SRCS) --out $@".format(
44+
tool = _tool,
45+
args = "--script=" + script if script else "",
46+
),
47+
# NOTE @aignas 2023-08-05: This should work with
48+
# `incompatible_generate_aliases` and without.
49+
srcs = [
50+
pkg.replace(":pkg", "") + ":dist_info",
51+
],
52+
outs = [main],
53+
tools = [_tool],
54+
executable = True,
55+
visibility = ["//visibility:private"],
56+
)
57+
58+
entry_point_deps = [pkg]
59+
if deps:
60+
entry_point_deps.extend(deps)
61+
62+
py_binary(
63+
name = name,
64+
srcs = [main],
65+
main = main,
66+
deps = entry_point_deps,
67+
**kwargs
68+
)

python/pip_install/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ filegroup(
44
"BUILD.bazel",
55
"//python/pip_install/private:distribution",
66
"//python/pip_install/tools/dependency_resolver:distribution",
7+
"//python/pip_install/tools/entry_point_generator:distribution",
78
"//python/pip_install/tools/wheel_installer:distribution",
89
],
910
visibility = ["//:__pkg__"],

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy