Skip to content

Commit 9a10fdb

Browse files
authored
Escape characters in the wheel filename. (bazel-contrib#518)
Fixes bazel-contrib#517.
1 parent dfbf9bf commit 9a10fdb

File tree

4 files changed

+73
-9
lines changed

4 files changed

+73
-9
lines changed

examples/wheel/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ py_wheel(
189189
version = "0.0.1",
190190
)
191191

192+
py_wheel(
193+
name = "filename_escaping",
194+
# Per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode
195+
# runs of non-alphanumeric, non-digit symbols should be replaced with a single underscore.
196+
# Unicode non-ascii letters should *not* be replaced with underscore.
197+
distribution = "file~~name-escaping",
198+
python_tag = "py3",
199+
version = "0.0.1-r7",
200+
deps = [":example_pkg"],
201+
)
202+
192203
py_test(
193204
name = "wheel_test",
194205
srcs = ["wheel_test.py"],
@@ -197,6 +208,7 @@ py_test(
197208
":custom_package_root_multi_prefix",
198209
":custom_package_root_multi_prefix_reverse_order",
199210
":customized",
211+
":filename_escaping",
200212
":minimal_with_py_library",
201213
":minimal_with_py_package",
202214
":python_abi3_binary_wheel",

examples/wheel/wheel_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,34 @@ def test_customized_wheel(self):
112112
first = first.main:f
113113
second = second.main:s""")
114114

115+
def test_filename_escaping(self):
116+
filename = os.path.join(os.environ['TEST_SRCDIR'],
117+
'rules_python',
118+
'examples', 'wheel',
119+
'file_name_escaping-0.0.1_r7-py3-none-any.whl')
120+
with zipfile.ZipFile(filename) as zf:
121+
self.assertEquals(
122+
zf.namelist(),
123+
['examples/wheel/lib/data.txt',
124+
'examples/wheel/lib/module_with_data.py',
125+
'examples/wheel/lib/simple_module.py',
126+
'examples/wheel/main.py',
127+
# PEP calls for replacing only in the archive filename.
128+
# Alas setuptools also escapes in the dist-info directory
129+
# name, so let's be compatible.
130+
'file_name_escaping-0.0.1_r7.dist-info/WHEEL',
131+
'file_name_escaping-0.0.1_r7.dist-info/METADATA',
132+
'file_name_escaping-0.0.1_r7.dist-info/RECORD'])
133+
metadata_contents = zf.read(
134+
'file_name_escaping-0.0.1_r7.dist-info/METADATA')
135+
self.assertEquals(metadata_contents, b"""\
136+
Metadata-Version: 2.1
137+
Name: file~~name-escaping
138+
Version: 0.0.1-r7
139+
140+
UNKNOWN
141+
""")
142+
115143
def test_custom_package_root_wheel(self):
116144
filename = os.path.join(os.environ['TEST_SRCDIR'],
117145
'rules_python',

python/packaging.bzl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,31 @@ Sub-packages are automatically included.
8383
},
8484
)
8585

86+
def _escape_filename_segment(segment):
87+
"""Escape a segment of the wheel filename.
88+
89+
See https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode
90+
"""
91+
92+
# TODO: this is wrong, isalnum replaces non-ascii letters, while we should
93+
# not replace them.
94+
# TODO: replace this with a regexp once starlark supports them.
95+
escaped = ""
96+
for character in segment.elems():
97+
# isalnum doesn't handle unicode characters properly.
98+
if character.isalnum() or character == ".":
99+
escaped += character
100+
elif not escaped.endswith("_"):
101+
escaped += "_"
102+
return escaped
103+
86104
def _py_wheel_impl(ctx):
87105
outfile = ctx.actions.declare_file("-".join([
88-
ctx.attr.distribution,
89-
ctx.attr.version,
90-
ctx.attr.python_tag,
91-
ctx.attr.abi,
92-
ctx.attr.platform,
106+
_escape_filename_segment(ctx.attr.distribution),
107+
_escape_filename_segment(ctx.attr.version),
108+
_escape_filename_segment(ctx.attr.python_tag),
109+
_escape_filename_segment(ctx.attr.abi),
110+
_escape_filename_segment(ctx.attr.platform),
93111
]) + ".whl")
94112

95113
inputs_to_package = depset(

tools/wheelmaker.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import hashlib
1919
import os
2020
import os.path
21+
import re
2122
import sys
2223
import zipfile
2324

@@ -31,6 +32,11 @@ def commonpath(path1, path2):
3132
return os.path.sep.join(ret)
3233

3334

35+
def escape_filename_segment(segment):
36+
"""Escapes a filename segment per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode"""
37+
return re.sub(r"[^\w\d.]+", "_", segment, re.UNICODE)
38+
39+
3440
class WheelMaker(object):
3541
def __init__(self, name, version, build_tag, python_tag, abi, platform,
3642
outfile=None, strip_path_prefixes=None):
@@ -43,6 +49,9 @@ def __init__(self, name, version, build_tag, python_tag, abi, platform,
4349
self._outfile = outfile
4450
self._strip_path_prefixes = strip_path_prefixes if strip_path_prefixes is not None else []
4551

52+
self._distinfo_dir = (escape_filename_segment(self._name) + '-' +
53+
escape_filename_segment(self._version) +
54+
'.dist-info/')
4655
self._zipfile = None
4756
self._record = []
4857

@@ -64,14 +73,11 @@ def filename(self):
6473
components += [self._python_tag, self._abi, self._platform]
6574
return '-'.join(components) + '.whl'
6675

67-
def distname(self):
68-
return self._name + '-' + self._version
69-
7076
def disttags(self):
7177
return ['-'.join([self._python_tag, self._abi, self._platform])]
7278

7379
def distinfo_path(self, basename):
74-
return self.distname() + '.dist-info/' + basename
80+
return self._distinfo_dir + basename
7581

7682
def _serialize_digest(self, hash):
7783
# https://www.python.org/dev/peps/pep-0376/#record

0 commit comments

Comments
 (0)