wheel: support for 'plugin' type entry_points (#349)
* wheel: support for 'plugin' type entry_points
See https://packaging.python.org/guides/creating-and-discovering-plugins/\#using-package-metadata
diff --git a/experimental/examples/wheel/BUILD b/experimental/examples/wheel/BUILD
index 528e3f5..06371fe 100644
--- a/experimental/examples/wheel/BUILD
+++ b/experimental/examples/wheel/BUILD
@@ -77,6 +77,10 @@
description_file = "README.md",
# Package data. We're building "example_customized-0.0.1-py3-none-any.whl"
distribution = "example_customized",
+ entry_points = {
+ "console_scripts": ["another = foo.bar:baz"],
+ "group2": ["second = second.main:s", "first = first.main:f"]
+ },
homepage = "www.example.com",
license = "Apache 2.0",
python_tag = "py3",
diff --git a/experimental/examples/wheel/wheel_test.py b/experimental/examples/wheel/wheel_test.py
index ea535de..f5d9b19 100644
--- a/experimental/examples/wheel/wheel_test.py
+++ b/experimental/examples/wheel/wheel_test.py
@@ -70,12 +70,13 @@
'example_customized-0.0.1.dist-info/WHEEL')
metadata_contents = zf.read(
'example_customized-0.0.1.dist-info/METADATA')
+ entry_point_contents = zf.read('example_customized-0.0.1.dist-info/entry_points.txt')
# The entries are guaranteed to be sorted.
self.assertEquals(record_contents, b"""\
example_customized-0.0.1.dist-info/METADATA,sha256=TeeEmokHE2NWjkaMcVJuSAq4_AXUoIad2-SLuquRmbg,372
example_customized-0.0.1.dist-info/RECORD,,
example_customized-0.0.1.dist-info/WHEEL,sha256=F01lGfVCzcXUzzQHzUkBmXAcu_TXd5zqMLrvrspncJo,85
-example_customized-0.0.1.dist-info/entry_points.txt,sha256=olLJ8FK88aft2pcdj4BD05F8Xyz83Mo51I93tRGT2Yk,74
+example_customized-0.0.1.dist-info/entry_points.txt,sha256=mEWsq4sMoyqR807QV8Z3KPocGfKvtgTo1lBFTRb6b78,150
experimental/examples/wheel/lib/data.txt,sha256=9vJKEdfLu8bZRArKLroPZJh1XKkK3qFMXiM79MBL2Sg,12
experimental/examples/wheel/lib/module_with_data.py,sha256=K_IGAq_CHcZX0HUyINpD1hqSKIEdCn58d9E9nhWF2EA,636
experimental/examples/wheel/lib/simple_module.py,sha256=72-91Dm6NB_jw-7wYQt7shzdwvk5RB0LujIah8g7kr8,636
@@ -101,6 +102,14 @@
This is a sample description of a wheel.
""")
+ self.assertEquals(entry_point_contents, b"""\
+[console_scripts]
+another = foo.bar:baz
+customized_wheel = experimental.examples.wheel.main:main
+
+[group2]
+first = first.main:f
+second = second.main:s""")
def test_custom_package_root_wheel(self):
filename = os.path.join(os.environ['TEST_SRCDIR'],
diff --git a/experimental/python/wheel.bzl b/experimental/python/wheel.bzl
index e4fb810..d1c46d6 100644
--- a/experimental/python/wheel.bzl
+++ b/experimental/python/wheel.bzl
@@ -99,11 +99,11 @@
other_inputs = []
# Wrap the inputs into a file to reduce command line length.
- packageinputfile = ctx.actions.declare_file(ctx.attr.name + '_target_wrapped_inputs.txt')
- content = ''
+ packageinputfile = ctx.actions.declare_file(ctx.attr.name + "_target_wrapped_inputs.txt")
+ content = ""
for input_file in inputs_to_package.to_list():
- content += _input_file_to_arg(input_file) + '\n'
- ctx.actions.write(output = packageinputfile, content=content)
+ content += _input_file_to_arg(input_file) + "\n"
+ ctx.actions.write(output = packageinputfile, content = content)
other_inputs.append(packageinputfile)
args = ctx.actions.args()
@@ -140,8 +140,30 @@
for r in requirements:
args.add("--extra_requires", r + ";" + option)
- for name, ref in ctx.attr.console_scripts.items():
- args.add("--console_script", name + " = " + ref)
+ # Merge console_scripts into entry_points.
+ entrypoints = dict(ctx.attr.entry_points) # Copy so we can mutate it
+ if ctx.attr.console_scripts:
+ # Copy a console_scripts group that may already exist, so we can mutate it.
+ console_scripts = list(entrypoints.get("console_scripts", []))
+ entrypoints["console_scripts"] = console_scripts
+ for name, ref in ctx.attr.console_scripts.items():
+ console_scripts.append("{name} = {ref}".format(name = name, ref = ref))
+
+ # If any entry_points are provided, construct the file here and add it to the files to be packaged.
+ # see: https://packaging.python.org/specifications/entry-points/
+ if entrypoints:
+ lines = []
+ for group, entries in sorted(entrypoints.items()):
+ if lines:
+ # Blank line between groups
+ lines.append("")
+ lines.append("[{group}]".format(group = group))
+ lines += sorted(entries)
+ entry_points_file = ctx.actions.declare_file(ctx.attr.name + "_entry_points.txt")
+ content = "\n".join(lines)
+ ctx.actions.write(output = entry_points_file, content = content)
+ other_inputs.append(entry_points_file)
+ args.add("--entry_points_file", entry_points_file)
if ctx.attr.description_file:
description_file = ctx.file.description_file
@@ -208,7 +230,14 @@
_entrypoint_attrs = {
"console_scripts": attr.string_dict(
doc = """\
-console_script entry points, e.g. 'experimental.examples.wheel.main:main'.
+Deprecated console_script entry points, e.g. {'main': 'experimental.examples.wheel.main:main'}.
+
+Deprecated: prefer the `entry_points` attribute, which supports `console_scripts` as well as other entry points.
+""",
+ ),
+ "entry_points": attr.string_list_dict(
+ doc = """\
+entry_points, e.g. {'console_scripts': ['main = experimental.examples.wheel.main:main']}.
""",
),
}
@@ -281,7 +310,7 @@
The targets to package are usually `py_library` rules or filesets (for packaging data files).
Note it's usually better to package `py_library` targets and use
-`console_scripts` attribute to specify entry points than to package
+`entry_points` attribute to specify `console_scripts` than to package
`py_binary` rules. `py_binary` targets would wrap a executable script that
tries to locate `.runfiles` directory which is not packaged in the wheel.
""",
diff --git a/experimental/rules_python/wheelmaker.py b/experimental/rules_python/wheelmaker.py
index 1b3261d..4f69b62 100644
--- a/experimental/rules_python/wheelmaker.py
+++ b/experimental/rules_python/wheelmaker.py
@@ -92,6 +92,7 @@
def add_file(self, package_filename, real_filename):
"""Add given file to the distribution."""
+
def arcname_from(name):
# Always use unix path separators.
normalized_arcname = name.replace(os.path.sep, '/')
@@ -157,15 +158,6 @@
metadata += "\n"
self.add_string(self.distinfo_path('METADATA'), metadata)
- def add_entry_points(self, console_scripts):
- """Write entry_points.txt file to the distribution."""
- # https://packaging.python.org/specifications/entry-points/
- if not console_scripts:
- return
- lines = ["[console_scripts]"] + console_scripts
- contents = '\n'.join(lines)
- self.add_string(self.distinfo_path('entry_points.txt'), contents)
-
def add_recordfile(self):
"""Write RECORD file to the distribution."""
record_path = self.distinfo_path('RECORD')
@@ -235,6 +227,8 @@
"Can be supplied multiple times")
wheel_group.add_argument('--description_file',
help="Path to the file with package description")
+ wheel_group.add_argument('--entry_points_file',
+ help="Path to a correctly-formatted entry_points.txt file")
contents_group = parser.add_argument_group("Wheel contents")
contents_group.add_argument(
@@ -246,10 +240,6 @@
'--input_file_list', action='append',
help='A file that has all the input files defined as a list to avoid the long command'
)
- contents_group.add_argument(
- '--console_script', action='append',
- help="Defines a 'console_script' entry point. "
- "Can be supplied multiple times.")
requirements_group = parser.add_argument_group("Package requirements")
requirements_group.add_argument(
@@ -314,14 +304,16 @@
classifiers = arguments.classifier or []
requires = arguments.requires or []
extra_headers = arguments.header or []
- console_scripts = arguments.console_script or []
maker.add_metadata(extra_headers=extra_headers,
description=description,
classifiers=classifiers,
requires=requires,
extra_requires=extra_requires)
- maker.add_entry_points(console_scripts=console_scripts)
+
+ if arguments.entry_points_file:
+ maker.add_file(maker.distinfo_path("entry_points.txt"), arguments.entry_points_file)
+
maker.add_recordfile()