8000 feat! add entry_point macro to rules_python · aignas/rules_python@b4ff67b · GitHub
[go: up one dir, main page]

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)
0