Skip to content

Commit 2fb9a2a

Browse files
authored
feat(py_wheel): Add support for specifying Project-URL in METADATA (#1276)
`Project-URL` is a field available in core metadata since version 1.2, which allows specifying additional URLs and display as Project Links in PyPI package web page. https://packaging.python.org/en/latest/specifications/core-metadata/#project-url-multiple-use This change adds the support to specify that.
1 parent 643a14b commit 2fb9a2a

File tree

6 files changed

+78
-3
lines changed

6 files changed

+78
-3
lines changed

docs/packaging.md

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/wheel/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ py_wheel(
157157
},
158158
homepage = "www.example.com",
159159
license = "Apache 2.0",
160+
project_urls = {
161+
"Bug Tracker": "www.example.com/issues",
162+
"Documentation": "www.example.com/docs",
163+
},
160164
python_tag = "py3",
161165
# Requirements embedded into the wheel metadata.
162166
requires = ["pytest"],

examples/wheel/wheel_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_customized_wheel(self):
9999
record_contents,
100100
# The entries are guaranteed to be sorted.
101101
b"""\
102-
example_customized-0.0.1.dist-info/METADATA,sha256=vRiyyV45PC5fzK_40nSTtIn3yYzDdsbBAbUvkZiRyc8,461
102+
example_customized-0.0.1.dist-info/METADATA,sha256=QYQcDJFQSIqan8eiXqL67bqsUfgEAwf2hoK_Lgi1S-0,559
103103
example_customized-0.0.1.dist-info/NOTICE,sha256=Xpdw-FXET1IRgZ_wTkx1YQfo1-alET0FVf6V1LXO4js,76
104104
example_customized-0.0.1.dist-info/README,sha256=WmOFwZ3Jga1bHG3JiGRsUheb4UbLffUxyTdHczS27-o,40
105105
example_customized-0.0.1.dist-info/RECORD,,
@@ -131,6 +131,8 @@ def test_customized_wheel(self):
131131
License: Apache 2.0
132132
Description-Content-Type: text/markdown
133133
Summary: A one-line summary of this test package
134+
Project-URL: Bug Tracker, www.example.com/issues
135+
Project-URL: Documentation, www.example.com/docs
134136
Classifier: License :: OSI Approved :: Apache Software License
135137
Classifier: Intended Audience :: Developers
136138
Requires-Dist: pytest

python/private/py_wheel.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ _other_attrs = {
176176
doc = "A string specifying the license of the package.",
177177
default = "",
178178
),
179+
"project_urls": attr.string_dict(
180+
doc = ("A string dict specifying additional browsable URLs for the project and corresponding labels, " +
181+
"where label is the key and url is the value. " +
182+
'e.g `{{"Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/"}}`'),
183+
),
179184
"python_requires": attr.string(
180185
doc = (
181186
"Python versions required by this distribution, e.g. '>=3.5,<3.7'"
@@ -191,6 +196,7 @@ _other_attrs = {
191196
),
192197
}
193198

199+
_PROJECT_URL_LABEL_LENGTH_LIMIT = 32
194200
_DESCRIPTION_FILE_EXTENSION_TO_TYPE = {
195201
"md": "text/markdown",
196202
"rst": "text/x-rst",
@@ -301,6 +307,11 @@ def _py_wheel_impl(ctx):
301307
if ctx.attr.summary:
302308
metadata_contents.append("Summary: %s" % ctx.attr.summary)
303309

310+
for label, url in sorted(ctx.attr.project_urls.items()):
311+
if len(label) > _PROJECT_URL_LABEL_LENGTH_LIMIT:
312+
fail("`label` {} in `project_urls` is too long. It is limited to {} characters.".format(len(label), _PROJECT_URL_LABEL_LENGTH_LIMIT))
313+
metadata_contents.append("Project-URL: %s, %s" % (label, url))
314+
304315
for c in ctx.attr.classifiers:
305316
metadata_contents.append("Classifier: %s" % c)
306317

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
"""Tests for py_wheel."""
15+
16+
load(":py_wheel_tests.bzl", "py_wheel_test_suite")
17+
18+
py_wheel_test_suite(name = "py_wheel_tests")
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Test for py_wheel."""
2+
3+
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
4+
load("@rules_testing//lib:truth.bzl", "matching")
5+
load("@rules_testing//lib:util.bzl", rt_util = "util")
6+
load("//python:packaging.bzl", "py_wheel")
7+
load("//tools/build_defs/python/tests:util.bzl", pt_util = "util")
8+
9+
_tests = []
10+
11+
def _test_too_long_project_url_label(name, config):
12+
rt_util.helper_target(
13+
config.rule,
14+
name = name + "_wheel",
15+
distribution = name + "_wheel",
16+
python_tag = "py3",
17+
version = "0.0.1",
18+
project_urls = {"This is a label whose length is above the limit!": "www.example.com"},
19+
)
20+
analysis_test(
21+
name = name,
22+
target = name + "_wheel",
23+
impl = _test_too_long_project_url_label_impl,
24+
expect_failure = True,
25+
)
26+
27+
def _test_too_long_project_url_label_impl(env, target):
28+
env.expect.that_target(target).failures().contains_predicate(
29+
matching.str_matches("in `project_urls` is too long"),
30+
)
31+
32+
_tests.append(_test_too_long_project_url_label)
33+
34+
def py_wheel_test_suite(name):
35+
config = struct(rule = py_wheel, base_test_rule = py_wheel)
36+
native.test_suite(
37+
name = name,
38+
tests = pt_util.create_tests(_tests, config = config),
39+
)

0 commit comments

Comments
 (0)