-
Notifications
You must be signed in to change notification settings - Fork 5
/
dist2wheel.py
executable file
·165 lines (137 loc) · 6.23 KB
/
dist2wheel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
# Assemble Python wheels for all artifacts built under dist/, according to the
# PEP 491 specification. Does the absolute bare minimum to get
# `pip install` and `pip uninstall` to place the gilt binary under bin/.
#
# Extraordinarily opinionated about where things live, and not
# expected to be usable by anything other than Gilt.
# These packages should be included in the Python stdlib. Nothing
# other than a basic Python interpreter should be needed.
import base64
import hashlib
import json
import os
import zipfile
# The METADATA file can likely be made even smaller, but explicit
# is better than implicit. ;-)
WHEEL_TEMPLATE = """Wheel-Version: 1.0
Generator: dist2wheel.py
Root-Is-Purelib: false
Tag: {py_tag}-{abi_tag}-{platform_tag}
"""
METADATA_TEMPLATE = """Metadata-Version: 2.1
Name: {distribution}
Version: {version}
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Go
Summary: {description}
License: {license}
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
{readme}
"""
class Wheels:
def __init__(self):
self.dist_dir = "dist"
self.wheel_dir = os.path.join(self.dist_dir, "whl")
# Pull in all the project metadata from known locations.
# No, this isn't customizable. Cope.
with open(os.path.join(self.dist_dir, "metadata.json")) as f:
self.metadata = json.load(f)
with open(os.path.join(self.dist_dir, "artifacts.json")) as f:
self.artifacts = json.load(f)
with open("README.md") as f:
self.readme = f.read()
self.distribution = f"python-{self.metadata['project_name']}"
self.version = self.metadata["version"]
self.py_tag = "py3"
self.abi_tag = "none"
def create_all(self):
"""Generate a Python wheel for every artifact in artifacts.json."""
# Burn a pass through the list to steal some useful bits from the Brew config
for artifact in self.artifacts:
if "BrewConfig" in artifact["extra"]:
self.description = artifact["extra"]["BrewConfig"]["description"]
self.license = artifact["extra"]["BrewConfig"]["license"]
# We're looking for "internal_type: 2" artifacts, but being an internal
# type, we'll avoid leaning on implementation details if we don't have to
for artifact in self.artifacts:
try:
self.path = artifact["path"]
self._set_platform_props(artifact)
self.checksum = self._fix_checksum(artifact["extra"]["Checksum"])
self.size = os.path.getsize(self.path)
except KeyError:
continue
self.wheel_file = WHEEL_TEMPLATE.format(**self.__dict__).encode()
self.metadata_file = METADATA_TEMPLATE.format(**self.__dict__).encode()
os.makedirs(self.wheel_dir, exist_ok=True)
self._emit()
def _matrix(self):
return {
"darwin": {
"amd64": {
"arch": "x86_64",
"platform": "macosx_10_15_x86_64",
"tags": [
f"{self.py_tag}-{self.abi_tag}-macosx_10_15_x86_64",
],
},
},
"linux": {
"amd64": {
"arch": "x86_64",
"platform": "manylinux2014_x86_64.manylinux_2_17_x86_64.musllinux_1_1_x86_64",
"tags": [
f"{self.py_tag}-{self.abi_tag}-manylinux2014_x86_64",
f"{self.py_tag}-{self.abi_tag}-manylinux_2_17_x86_64",
f"{self.py_tag}-{self.abi_tag}-musllinux_1_1_x86_64",
],
},
},
}
def _set_platform_props(self, artifact):
"""Convert Go binary nomenclature to Python wheel nomenclature."""
_map = self._matrix()
_platform = artifact["goos"]
_goarch = artifact["goarch"]
platform = _map[_platform][_goarch]["platform"]
self.platform = f"{platform}"
self.platform_tag = "\nTag: ".join(_map[_platform][_goarch]["tags"])
@staticmethod
def _fix_checksum(checksum):
"""Re-encode the checksum as base64, with no trailing = characters."""
if checksum.startswith("sha256:"):
checksum = checksum[7:]
return base64.urlsafe_b64encode(bytes.fromhex(checksum)).decode().rstrip("=")
def _emit(self):
pypi_name = self.distribution.replace("-", "_")
name_ver = f"{pypi_name}-{self.version}"
filename = os.path.join( self.wheel_dir, f"{name_ver}-{self.py_tag}-{self.abi_tag}-{self.platform}.whl")
with zipfile.ZipFile(filename, "w", compression=zipfile.ZIP_DEFLATED) as zf:
record = []
print(f"writing {zf.filename}")
# The actual binary on-disk, and the recorded checksum from artifacts.json
arcname = f"{name_ver}.data/scripts/{os.path.basename(self.path)}"
zf.write(self.path, arcname=arcname)
record.append(f"{arcname},sha256={self.checksum},{self.size}")
# The project metadata
arcname = f"{name_ver}.dist-info/METADATA"
zf.writestr(arcname, self.metadata_file)
digest = hashlib.sha256(self.metadata_file).hexdigest()
record.append(f"{arcname},sha256={self._fix_checksum(digest)},{len(self.metadata_file)}")
# The platform tags
arcname = f"{name_ver}.dist-info/WHEEL"
zf.writestr(arcname, self.wheel_file)
digest = hashlib.sha256(self.wheel_file).hexdigest()
record.append(f"{arcname},sha256={self._fix_checksum(digest)},{len(self.wheel_file)}")
# Write out the manifest last. The record of itself contains no checksum or size info
arcname = f"{name_ver}.dist-info/RECORD"
record.append(f"{arcname},,")
zf.writestr(arcname, "\n".join(record))
if __name__ == "__main__":
Wheels().create_all()