From 750a3833d097c89535beb10d9d27854dc7940d9a Mon Sep 17 00:00:00 2001 From: chua Date: Thu, 8 May 2025 14:35:37 +0000 Subject: [PATCH 01/12] allow custom shebang --- python/private/py_console_script_binary.bzl | 4 ++++ python/private/py_console_script_gen.bzl | 5 +++++ python/private/py_console_script_gen.py | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/python/private/py_console_script_binary.bzl b/python/private/py_console_script_binary.bzl index 154fa3bf2f..647c3973fe 100644 --- a/python/private/py_console_script_binary.bzl +++ b/python/private/py_console_script_binary.bzl @@ -52,6 +52,7 @@ def py_console_script_binary( entry_points_txt = None, script = None, binary_rule = py_binary, + shebang = None, **kwargs): """Generate a py_binary for a console_script entry_point. @@ -68,6 +69,8 @@ def py_console_script_binary( binary_rule: {type}`callable`, The rule/macro to use to instantiate the target. It's expected to behave like {obj}`py_binary`. Defaults to {obj}`py_binary`. + shebang: [`str`], The shebang to use for the entry point python file. + Defaults to empty string. **kwargs: Extra parameters forwarded to `binary_rule`. """ main = "rules_python_entry_point_{}.py".format(name) @@ -81,6 +84,7 @@ def py_console_script_binary( out = main, console_script = script, console_script_guess = name, + shebang = shebang, visibility = ["//visibility:private"], ) diff --git a/python/private/py_console_script_gen.bzl b/python/private/py_console_script_gen.bzl index 7dd4dd2dad..de016036b2 100644 --- a/python/private/py_console_script_gen.bzl +++ b/python/private/py_console_script_gen.bzl @@ -42,6 +42,7 @@ def _py_console_script_gen_impl(ctx): args = ctx.actions.args() args.add("--console-script", ctx.attr.console_script) args.add("--console-script-guess", ctx.attr.console_script_guess) + args.add("--shebang", ctx.attr.shebang) args.add(entry_points_txt) args.add(ctx.outputs.out) @@ -81,6 +82,10 @@ py_console_script_gen = rule( doc = "Output file location.", mandatory = True, ), + "shebang": attr.string( + doc = "The shebang to use for the entry point python file.", + default = "", + ), "_tool": attr.label( default = ":py_console_script_gen_py", executable = True, diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index ffc4e81b3a..50a4b5d073 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -44,6 +44,8 @@ _ENTRY_POINTS_TXT = "entry_points.txt" _TEMPLATE = """\ +{shebang} + import sys # See @rules_python//python/private:py_console_script_gen.py for explanation @@ -87,6 +89,7 @@ def run( out: pathlib.Path, console_script: str, console_script_guess: str, + shebang: str = "#!/usr/bin/env python3", ): """Run the generator @@ -94,6 +97,8 @@ def run( entry_points: The entry_points.txt file to be parsed. out: The output file. console_script: The console_script entry in the entry_points.txt file. + console_script_guess: The string used for guessing the console_script if it is not provided. + shebang: The shebang to use for the entry point python file. Defaults to "#!/usr/bin/env python3". """ config = EntryPointsParser() config.read(entry_points) @@ -136,6 +141,7 @@ def run( with open(out, "w") as f: f.write( _TEMPLATE.format( + shebang=shebang, module=module, attr=attr, entry_point=entry_point, @@ -154,6 +160,11 @@ def main(): required=True, help="The string used for guessing the console_script if it is not provided.", ) + parser.add_argument( + "--shebang", + default="#!/usr/bin/env python3", + help="The shebang to use for the entry point python file.", + ) parser.add_argument( "entry_points", metavar="ENTRY_POINTS_TXT", @@ -173,6 +184,7 @@ def main(): out=args.out, console_script=args.console_script, console_script_guess=args.console_script_guess, + shebang=args.shebang, ) From 0fc7179b50384d94741d3128a5706b1f15ca7a0c Mon Sep 17 00:00:00 2001 From: chua Date: Thu, 8 May 2025 15:10:23 +0000 Subject: [PATCH 02/12] remove defaults --- python/private/py_console_script_gen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index 50a4b5d073..67a8526970 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -89,7 +89,7 @@ def run( out: pathlib.Path, console_script: str, console_script_guess: str, - shebang: str = "#!/usr/bin/env python3", + shebang: str, ): """Run the generator @@ -162,7 +162,6 @@ def main(): ) parser.add_argument( "--shebang", - default="#!/usr/bin/env python3", help="The shebang to use for the entry point python file.", ) parser.add_argument( From 969e5a6dc7c951f5ebfd648aacedeb4143e00ccb Mon Sep 17 00:00:00 2001 From: chua Date: Thu, 8 May 2025 17:23:01 +0000 Subject: [PATCH 03/12] fix tests --- python/private/py_console_script_gen.py | 1 - tests/entry_points/py_console_script_gen_test.py | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index 67a8526970..c78b8bc2c0 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -45,7 +45,6 @@ _TEMPLATE = """\ {shebang} - import sys # See @rules_python//python/private:py_console_script_gen.py for explanation diff --git a/tests/entry_points/py_console_script_gen_test.py b/tests/entry_points/py_console_script_gen_test.py index a5fceb67f9..9c2a6b83b5 100644 --- a/tests/entry_points/py_console_script_gen_test.py +++ b/tests/entry_points/py_console_script_gen_test.py @@ -47,6 +47,7 @@ def test_no_console_scripts_error(self): out=outfile, console_script=None, console_script_guess="", + shebang="", ) self.assertEqual( @@ -76,6 +77,7 @@ def test_no_entry_point_selected_error(self): out=outfile, console_script=None, console_script_guess="bar-baz", + shebang="", ) self.assertEqual( @@ -106,6 +108,7 @@ def test_incorrect_entry_point(self): out=outfile, console_script="baz", console_script_guess="", + shebang="", ) self.assertEqual( @@ -134,12 +137,14 @@ def test_a_single_entry_point(self): out=out, console_script=None, console_script_guess="foo", + shebang="", ) got = out.read_text() want = textwrap.dedent( """\ + import sys # See @rules_python//python/private:py_console_script_gen.py for explanation @@ -185,6 +190,7 @@ def test_a_second_entry_point_class_method(self): out=out, console_script="bar", console_script_guess="", + shebang="", ) got = out.read_text() From 45aed03fc2cbe19fa4d2b422155234ea35d5c5f4 Mon Sep 17 00:00:00 2001 From: chua Date: Fri, 9 May 2025 01:08:55 +0000 Subject: [PATCH 04/12] remove default in comment --- python/private/py_console_script_gen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index c78b8bc2c0..56db9a973c 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -97,7 +97,7 @@ def run( out: The output file. console_script: The console_script entry in the entry_points.txt file. console_script_guess: The string used for guessing the console_script if it is not provided. - shebang: The shebang to use for the entry point python file. Defaults to "#!/usr/bin/env python3". + shebang: The shebang to use for the entry point python file. Defaults to empty string (no shebang). """ config = EntryPointsParser() config.read(entry_points) From 1ab11819b9c4e18b3981b638670f001d5ed17469 Mon Sep 17 00:00:00 2001 From: chua Date: Fri, 9 May 2025 03:22:49 +0000 Subject: [PATCH 05/12] add test for shebang --- .../py_console_script_gen_test.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/entry_points/py_console_script_gen_test.py b/tests/entry_points/py_console_script_gen_test.py index 9c2a6b83b5..ce5ebf3d25 100644 --- a/tests/entry_points/py_console_script_gen_test.py +++ b/tests/entry_points/py_console_script_gen_test.py @@ -198,6 +198,35 @@ def test_a_second_entry_point_class_method(self): self.assertRegex(got, "from foo\.baz import Bar") self.assertRegex(got, "sys\.exit\(Bar\.baz\(\)\)") + def test_shebang_included(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = pathlib.Path(tmpdir) + given_contents = ( + textwrap.dedent( + """ + [console_scripts] + foo = foo.bar:baz + """ + ).strip() + + "\n" + ) + entry_points = tmpdir / "entry_points.txt" + entry_points.write_text(given_contents) + out = tmpdir / "foo.py" + + shebang = "#!/usr/bin/env python3" + run( + entry_points=entry_points, + out=out, + console_script=None, + console_script_guess="foo", + shebang=shebang, + ) + + got = out.read_text() + + self.assertTrue(got.startswith(shebang + "\n")) + if __name__ == "__main__": unittest.main() From ada9936b6aabda1632c074a5db6982607cd0904b Mon Sep 17 00:00:00 2001 From: chua Date: Fri, 9 May 2025 03:23:36 +0000 Subject: [PATCH 06/12] don't introduce newline by default --- python/private/py_console_script_gen.py | 5 ++--- tests/entry_points/py_console_script_gen_test.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index 56db9a973c..41b62aa4af 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -44,8 +44,7 @@ _ENTRY_POINTS_TXT = "entry_points.txt" _TEMPLATE = """\ -{shebang} -import sys +{shebang}import sys # See @rules_python//python/private:py_console_script_gen.py for explanation if getattr(sys.flags, "safe_path", False): @@ -140,7 +139,7 @@ def run( with open(out, "w") as f: f.write( _TEMPLATE.format( - shebang=shebang, + shebang=shebang + "\n" if shebang else "", module=module, attr=attr, entry_point=entry_point, diff --git a/tests/entry_points/py_console_script_gen_test.py b/tests/entry_points/py_console_script_gen_test.py index ce5ebf3d25..1bbf5fbf25 100644 --- a/tests/entry_points/py_console_script_gen_test.py +++ b/tests/entry_points/py_console_script_gen_test.py @@ -144,7 +144,6 @@ def test_a_single_entry_point(self): want = textwrap.dedent( """\ - import sys # See @rules_python//python/private:py_console_script_gen.py for explanation From 3bf1fa34a6345ec5edea04441f5ba30531641a6d Mon Sep 17 00:00:00 2001 From: chua Date: Wed, 21 May 2025 06:11:13 +0000 Subject: [PATCH 07/12] use consistent typing convention --- python/private/py_console_script_binary.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/py_console_script_binary.bzl b/python/private/py_console_script_binary.bzl index 647c3973fe..f2c6305311 100644 --- a/python/private/py_console_script_binary.bzl +++ b/python/private/py_console_script_binary.bzl @@ -69,7 +69,7 @@ def py_console_script_binary( binary_rule: {type}`callable`, The rule/macro to use to instantiate the target. It's expected to behave like {obj}`py_binary`. Defaults to {obj}`py_binary`. - shebang: [`str`], The shebang to use for the entry point python file. + shebang: {type}`str`, The shebang to use for the entry point python file. Defaults to empty string. **kwargs: Extra parameters forwarded to `binary_rule`. """ From cd46d9facf852e526d5187423e21a1a15b954c53 Mon Sep 17 00:00:00 2001 From: chua Date: Wed, 21 May 2025 06:12:57 +0000 Subject: [PATCH 08/12] Missed a previous change --- python/private/py_console_script_binary.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/py_console_script_binary.bzl b/python/private/py_console_script_binary.bzl index f2c6305311..d98457dbe1 100644 --- a/python/private/py_console_script_binary.bzl +++ b/python/private/py_console_script_binary.bzl @@ -52,7 +52,7 @@ def py_console_script_binary( entry_points_txt = None, script = None, binary_rule = py_binary, - shebang = None, + shebang = "", **kwargs): """Generate a py_binary for a console_script entry_point. From 21c51f44c424f706e1b0441fca0447acc7eff6e7 Mon Sep 17 00:00:00 2001 From: chua Date: Wed, 21 May 2025 06:15:08 +0000 Subject: [PATCH 09/12] use f-string to improve readability --- python/private/py_console_script_gen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py index 41b62aa4af..4b4f2f6986 100644 --- a/python/private/py_console_script_gen.py +++ b/python/private/py_console_script_gen.py @@ -139,7 +139,7 @@ def run( with open(out, "w") as f: f.write( _TEMPLATE.format( - shebang=shebang + "\n" if shebang else "", + shebang=f"{shebang}\n" if shebang else "", module=module, attr=attr, entry_point=entry_point, From 2320282bf68e93493384e05ca73ed5a267c4a0fe Mon Sep 17 00:00:00 2001 From: chua Date: Wed, 21 May 2025 06:28:20 +0000 Subject: [PATCH 10/12] first cut at doc --- docs/_includes/py_console_script_binary.md | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/_includes/py_console_script_binary.md b/docs/_includes/py_console_script_binary.md index aa356e0e94..c064abe02c 100644 --- a/docs/_includes/py_console_script_binary.md +++ b/docs/_includes/py_console_script_binary.md @@ -48,6 +48,28 @@ py_console_script_binary( ) ``` +#### Adding a Shebang Line + +You can specify a shebang line for the generated binary, which is particularly +useful for Unix-like systems where the shebang line determines which interpreter +is used to execute the script: + +```starlark +load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary") + +py_console_script_binary( + name = "black", + pkg = "@pip//black", + interpreter = "/usr/bin/env python3", +) +``` + +The shebang line enables direct script execution with the correct Python version +across different systems, aligning with principles in [PEP441]. Note that when +executing directly via the shebang line, you must ensure the specified +Python interpreter is available in the user's environment. + + #### Using a specific Python Version directly from a Toolchain :::{deprecated} 1.1.0 The toolchain specific `py_binary` and `py_test` symbols are aliases to the regular rules. @@ -70,4 +92,5 @@ py_console_script_binary( ``` [specification]: https://packaging.python.org/en/latest/specifications/entry-points/ -[`py_console_script_binary.binary_rule`]: #py_console_script_binary_binary_rule \ No newline at end of file +[`py_console_script_binary.binary_rule`]: #py_console_script_binary_binary_rule +[PEP441]: https://peps.python.org/pep-0441/#minimal-tooling-the-zipapp-module From 1b43ecc1f96e956dd10ffc723aee86b819fba421 Mon Sep 17 00:00:00 2001 From: chua Date: Wed, 21 May 2025 06:29:43 +0000 Subject: [PATCH 11/12] clean up the doc --- docs/_includes/py_console_script_binary.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/_includes/py_console_script_binary.md b/docs/_includes/py_console_script_binary.md index c064abe02c..ca0b111cee 100644 --- a/docs/_includes/py_console_script_binary.md +++ b/docs/_includes/py_console_script_binary.md @@ -50,9 +50,9 @@ py_console_script_binary( #### Adding a Shebang Line -You can specify a shebang line for the generated binary, which is particularly -useful for Unix-like systems where the shebang line determines which interpreter -is used to execute the script: +You can specify a shebang line for the generated binary, useful for Unix-like +systems where the shebang line determines which interpreter is used to execute +the script, per [PEP441]: ```starlark load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary") @@ -64,10 +64,8 @@ py_console_script_binary( ) ``` -The shebang line enables direct script execution with the correct Python version -across different systems, aligning with principles in [PEP441]. Note that when -executing directly via the shebang line, you must ensure the specified -Python interpreter is available in the user's environment. +Note that to execute via the shebang line, you need to ensure the specified +Python interpreter is available in the environment. #### Using a specific Python Version directly from a Toolchain From f814d32dfab3af704b8ead088e4cb469c53872d6 Mon Sep 17 00:00:00 2001 From: Chris Chua Date: Sun, 25 May 2025 13:14:29 +0800 Subject: [PATCH 12/12] Update docs/_includes/py_console_script_binary.md Co-authored-by: Ignas Anikevicius <240938+aignas@users.noreply.github.com> --- docs/_includes/py_console_script_binary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/py_console_script_binary.md b/docs/_includes/py_console_script_binary.md index ca0b111cee..d327091630 100644 --- a/docs/_includes/py_console_script_binary.md +++ b/docs/_includes/py_console_script_binary.md @@ -60,7 +60,7 @@ load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_cons py_console_script_binary( name = "black", pkg = "@pip//black", - interpreter = "/usr/bin/env python3", + shebang = "#!/usr/bin/env python3", ) ```