feat(py_wheel): Add support for specifying Description-Content-Type and Summary in METADATA (#1274)
`py_wheel` allows to supply a description file, but it does not allow
specifying the content type of that file.
By default, the type is "text/x-rst", but many packages are using a
markdown description.
https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type
This change added the support.
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/docs/packaging.md b/docs/packaging.md
index 74d68da..091e1a0 100755
--- a/docs/packaging.md
+++ b/docs/packaging.md
@@ -57,9 +57,10 @@
## py_wheel_rule
<pre>
-py_wheel_rule(<a href="#py_wheel_rule-name">name</a>, <a href="#py_wheel_rule-abi">abi</a>, <a href="#py_wheel_rule-author">author</a>, <a href="#py_wheel_rule-author_email">author_email</a>, <a href="#py_wheel_rule-classifiers">classifiers</a>, <a href="#py_wheel_rule-console_scripts">console_scripts</a>, <a href="#py_wheel_rule-deps">deps</a>, <a href="#py_wheel_rule-description_file">description_file</a>,
- <a href="#py_wheel_rule-distribution">distribution</a>, <a href="#py_wheel_rule-entry_points">entry_points</a>, <a href="#py_wheel_rule-extra_distinfo_files">extra_distinfo_files</a>, <a href="#py_wheel_rule-extra_requires">extra_requires</a>, <a href="#py_wheel_rule-homepage">homepage</a>, <a href="#py_wheel_rule-license">license</a>,
- <a href="#py_wheel_rule-platform">platform</a>, <a href="#py_wheel_rule-python_requires">python_requires</a>, <a href="#py_wheel_rule-python_tag">python_tag</a>, <a href="#py_wheel_rule-requires">requires</a>, <a href="#py_wheel_rule-stamp">stamp</a>, <a href="#py_wheel_rule-strip_path_prefixes">strip_path_prefixes</a>, <a href="#py_wheel_rule-version">version</a>)
+py_wheel_rule(<a href="#py_wheel_rule-name">name</a>, <a href="#py_wheel_rule-abi">abi</a>, <a href="#py_wheel_rule-author">author</a>, <a href="#py_wheel_rule-author_email">author_email</a>, <a href="#py_wheel_rule-classifiers">classifiers</a>, <a href="#py_wheel_rule-console_scripts">console_scripts</a>, <a href="#py_wheel_rule-deps">deps</a>,
+ <a href="#py_wheel_rule-description_content_type">description_content_type</a>, <a href="#py_wheel_rule-description_file">description_file</a>, <a href="#py_wheel_rule-distribution">distribution</a>, <a href="#py_wheel_rule-entry_points">entry_points</a>,
+ <a href="#py_wheel_rule-extra_distinfo_files">extra_distinfo_files</a>, <a href="#py_wheel_rule-extra_requires">extra_requires</a>, <a href="#py_wheel_rule-homepage">homepage</a>, <a href="#py_wheel_rule-license">license</a>, <a href="#py_wheel_rule-platform">platform</a>, <a href="#py_wheel_rule-python_requires">python_requires</a>,
+ <a href="#py_wheel_rule-python_tag">python_tag</a>, <a href="#py_wheel_rule-requires">requires</a>, <a href="#py_wheel_rule-stamp">stamp</a>, <a href="#py_wheel_rule-strip_path_prefixes">strip_path_prefixes</a>, <a href="#py_wheel_rule-summary">summary</a>, <a href="#py_wheel_rule-version">version</a>)
</pre>
Internal rule used by the [py_wheel macro](/docs/packaging.md#py_wheel).
@@ -81,6 +82,7 @@
| <a id="py_wheel_rule-classifiers"></a>classifiers | A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers | List of strings | optional | <code>[]</code> |
| <a id="py_wheel_rule-console_scripts"></a>console_scripts | Deprecated console_script entry points, e.g. <code>{'main': 'examples.wheel.main:main'}</code>.<br><br>Deprecated: prefer the <code>entry_points</code> attribute, which supports <code>console_scripts</code> as well as other entry points. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
| <a id="py_wheel_rule-deps"></a>deps | Targets to be included in the distribution.<br><br>The targets to package are usually <code>py_library</code> rules or filesets (for packaging data files).<br><br>Note it's usually better to package <code>py_library</code> targets and use <code>entry_points</code> attribute to specify <code>console_scripts</code> than to package <code>py_binary</code> rules. <code>py_binary</code> targets would wrap a executable script that tries to locate <code>.runfiles</code> directory which is not packaged in the wheel. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+| <a id="py_wheel_rule-description_content_type"></a>description_content_type | The type of contents in description_file. If not provided, the type will be inferred from the extension of description_file. Also see https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type | String | optional | <code>""</code> |
| <a id="py_wheel_rule-description_file"></a>description_file | A file containing text describing the package. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
| <a id="py_wheel_rule-distribution"></a>distribution | Name of the distribution.<br><br>This should match the project name onm PyPI. It's also the name that is used to refer to the package in other packages' dependencies.<br><br>Workspace status keys are expanded using <code>{NAME}</code> format, for example: - <code>distribution = "package.{CLASSIFIER}"</code> - <code>distribution = "{DISTRIBUTION}"</code><br><br>For the available keys, see https://bazel.build/docs/user-manual#workspace-status | String | required | |
| <a id="py_wheel_rule-entry_points"></a>entry_points | entry_points, e.g. <code>{'console_scripts': ['main = examples.wheel.main:main']}</code>. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | <code>{}</code> |
@@ -94,6 +96,7 @@
| <a id="py_wheel_rule-requires"></a>requires | List of requirements for this package. See the section on [Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) for details and examples of the format of this argument. | List of strings | optional | <code>[]</code> |
| <a id="py_wheel_rule-stamp"></a>stamp | Whether to encode build information into the wheel. Possible values:<br><br>- <code>stamp = 1</code>: Always stamp the build information into the wheel, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it potentially kills remote caching for the target and any downstream actions that depend on it.<br><br>- <code>stamp = 0</code>: Always replace build information by constant values. This gives good build result caching.<br><br>- <code>stamp = -1</code>: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.<br><br>Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | <code>-1</code> |
| <a id="py_wheel_rule-strip_path_prefixes"></a>strip_path_prefixes | path prefixes to strip from files added to the generated package | List of strings | optional | <code>[]</code> |
+| <a id="py_wheel_rule-summary"></a>summary | A one-line summary of what the distribution does | String | optional | <code>""</code> |
| <a id="py_wheel_rule-version"></a>version | Version number of the package.<br><br>Note that this attribute supports stamp format strings as well as 'make variables'. For example: - <code>version = "1.2.3-{BUILD_TIMESTAMP}"</code> - <code>version = "{BUILD_EMBED_LABEL}"</code> - <code>version = "$(VERSION)"</code><br><br>Note that Bazel's output filename cannot include the stamp information, as outputs must be known during the analysis phase and the stamp data is available only during the action execution.<br><br>The [<code>py_wheel</code>](/docs/packaging.md#py_wheel) macro produces a <code>.dist</code>-suffix target which creates a <code>dist/</code> folder containing the wheel with the stamped name, suitable for publishing.<br><br>See [<code>py_wheel_dist</code>](/docs/packaging.md#py_wheel_dist) for more info. | String | required | |
diff --git a/examples/wheel/BUILD.bazel b/examples/wheel/BUILD.bazel
index 49a212b..72cc3d4 100644
--- a/examples/wheel/BUILD.bazel
+++ b/examples/wheel/BUILD.bazel
@@ -160,6 +160,7 @@
python_tag = "py3",
# Requirements embedded into the wheel metadata.
requires = ["pytest"],
+ summary = "A one-line summary of this test package",
version = "0.0.1",
deps = [":example_pkg"],
)
diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py
index 591e057..6869e77 100644
--- a/examples/wheel/wheel_test.py
+++ b/examples/wheel/wheel_test.py
@@ -99,7 +99,7 @@
record_contents,
# The entries are guaranteed to be sorted.
b"""\
-example_customized-0.0.1.dist-info/METADATA,sha256=YUnzQ9gTMXspIBURe90Ct3aL_CCn8fwC3SiZe6MMTs8,372
+example_customized-0.0.1.dist-info/METADATA,sha256=vRiyyV45PC5fzK_40nSTtIn3yYzDdsbBAbUvkZiRyc8,461
example_customized-0.0.1.dist-info/NOTICE,sha256=Xpdw-FXET1IRgZ_wTkx1YQfo1-alET0FVf6V1LXO4js,76
example_customized-0.0.1.dist-info/README,sha256=WmOFwZ3Jga1bHG3JiGRsUheb4UbLffUxyTdHczS27-o,40
example_customized-0.0.1.dist-info/RECORD,,
@@ -129,6 +129,8 @@
Author-email: example@example.com
Home-page: www.example.com
License: Apache 2.0
+Description-Content-Type: text/markdown
+Summary: A one-line summary of this test package
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Intended Audience :: Developers
Requires-Dist: pytest
diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl
index 84fdeac..edafa3e 100644
--- a/python/private/py_wheel.bzl
+++ b/python/private/py_wheel.bzl
@@ -155,6 +155,11 @@
"classifiers": attr.string_list(
doc = "A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers",
),
+ "description_content_type": attr.string(
+ doc = ("The type of contents in description_file. " +
+ "If not provided, the type will be inferred from the extension of description_file. " +
+ "Also see https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type"),
+ ),
"description_file": attr.label(
doc = "A file containing text describing the package.",
allow_single_file = True,
@@ -181,8 +186,17 @@
default = [],
doc = "path prefixes to strip from files added to the generated package",
),
+ "summary": attr.string(
+ doc = "A one-line summary of what the distribution does",
+ ),
}
+_DESCRIPTION_FILE_EXTENSION_TO_TYPE = {
+ "md": "text/markdown",
+ "rst": "text/x-rst",
+}
+_DEFAULT_DESCRIPTION_FILE_TYPE = "text/plain"
+
def _escape_filename_segment(segment):
"""Escape a segment of the wheel filename.
@@ -275,6 +289,17 @@
metadata_contents.append("Home-page: %s" % ctx.attr.homepage)
if ctx.attr.license:
metadata_contents.append("License: %s" % ctx.attr.license)
+ if ctx.attr.description_content_type:
+ metadata_contents.append("Description-Content-Type: %s" % ctx.attr.description_content_type)
+ elif ctx.attr.description_file:
+ # infer the content type from description file extension.
+ description_file_type = _DESCRIPTION_FILE_EXTENSION_TO_TYPE.get(
+ ctx.file.description_file.extension,
+ _DEFAULT_DESCRIPTION_FILE_TYPE,
+ )
+ metadata_contents.append("Description-Content-Type: %s" % description_file_type)
+ if ctx.attr.summary:
+ metadata_contents.append("Summary: %s" % ctx.attr.summary)
for c in ctx.attr.classifiers:
metadata_contents.append("Classifier: %s" % c)
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
index edc25cc..63b833f 100644
--- a/tools/wheelmaker.py
+++ b/tools/wheelmaker.py
@@ -290,6 +290,9 @@
"--description_file", help="Path to the file with package description"
)
wheel_group.add_argument(
+ "--description_content_type", help="Content type of the package description"
+ )
+ wheel_group.add_argument(
"--entry_points_file",
help="Path to a correctly-formatted entry_points.txt file",
)