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", )