Skip to content

Instantly share code, notes, and snippets.

@meshula
Created July 13, 2021 17:25
Show Gist options
  • Save meshula/d3ab2a70cd98c385b7920c32ff470c1e to your computer and use it in GitHub Desktop.
Save meshula/d3ab2a70cd98c385b7920c32ff470c1e to your computer and use it in GitHub Desktop.
publish zig to pypi
# This is not original code, it's from https://github.com/ziglang/zig-pypi
# this copy's purpose is to add comments in order to understand how it works
import os
import hashlib
import urllib.request
from email.message import EmailMessage
from wheel.wheelfile import WheelFile, get_zipinfo_datetime
from zipfile import ZipInfo, ZIP_DEFLATED
import libarchive # from libarchive-c
# Apparently wheels as zip files need to have a date of Linux:0, and System 3
class ReproducibleWheelFile(WheelFile):
def writestr(self, zinfo, *args, **kwargs):
if not isinstance(zinfo, ZipInfo):
raise ValueError("ZipInfo required")
zinfo.date_time = (1980,1,1,0,0,0)
zinfo.create_system = 3
super().writestr(zinfo, *args, **kwargs)
# create an email message
def make_message(headers, payload=None):
msg = EmailMessage()
for name, value in headers.items():
if isinstance(value, list):
for value_part in value:
msg[name] = value_part
else:
msg[name] = value
if payload:
msg.set_payload(payload)
return msg
# stuff all the things in a zip file that's been conformed to Jan 1 1980, System 3
def write_wheel_file(filename, contents):
with ReproducibleWheelFile(filename, 'w') as wheel:
for member_info, member_source in contents.items():
if not isinstance(member_info, ZipInfo):
member_info = ZipInfo(member_info)
member_info.external_attr = 0o644 << 16
member_info.file_size = len(member_source)
member_info.compress_type = ZIP_DEFLATED
wheel.writestr(member_info, bytes(member_source))
return filename
# create a well formed wheel filled with magic wheel juice
def write_wheel(out_dir, *, name, version, tag, metadata, description, contents):
wheel_name = f'{name}-{version}-{tag}.whl'
dist_info = f'{name}-{version}.dist-info'
return write_wheel_file(os.path.join(out_dir, wheel_name), {
**contents,
f'{dist_info}/METADATA': make_message({
'Metadata-Version': '2.1',
'Name': name,
'Version': version,
**metadata,
}, description),
f'{dist_info}/WHEEL': make_message({
'Wheel-Version': '1.0',
'Generator': 'ziglang make_wheels.py',
'Root-Is-Purelib': 'false',
'Tag': tag,
}),
})
# create the squeamy guts of a wheel given an archive of contents
# external README.pypi.md is also copied in
def write_ziglang_wheel(out_dir, *, version, platform, archive):
contents = {}
contents['ziglang/__init__.py'] = b''
# run through the archive and add top level elements to the wheel
with libarchive.memory_reader(archive) as archive:
for entry in archive:
entry_name = '/'.join(entry.name.split('/')[1:])
if entry.isdir or not entry_name:
continue
zip_info = ZipInfo(f'ziglang/{entry_name}')
zip_info.external_attr = (entry.mode & 0xFFFF) << 16
contents[zip_info] = b''.join(entry.get_blocks())
# if the archive entry is zig, gin up some fakeness
# to satisfy pypi that it's a python wheel
if entry_name.startswith('zig'):
contents['ziglang/__main__.py'] = f'''\
import os, sys, subprocess
sys.exit(subprocess.call([
os.path.join(os.path.dirname(__file__), "{entry_name}"),
*sys.argv[1:]
]))
'''.encode('ascii')
with open('README.pypi.md') as f:
description = f.read()
# finish manufacturing the wheel
return write_wheel(out_dir,
name='ziglang',
version=version,
tag=f'py3-none-{platform}',
metadata={
'Summary': 'Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.',
'Description-Content-Type': 'text/markdown',
'License': 'MIT',
'Classifier': [
'License :: OSI Approved :: MIT License',
],
'Project-URL': [
'Homepage, https://ziglang.org',
'Source Code, https://github.com/ziglang/zig-pypi',
'Bug Tracker, https://github.com/ziglang/zig-pypi/issues',
],
'Requires-Python': '~=3.5',
},
description=description,
contents=contents,
)
zig_version = '0.8.0'
# download each of the zig targets from ziglang.org, and create a wheel for it
for zig_platform, python_platform in {
'windows-i386': 'win32',
'windows-x86_64': 'win_amd64',
'macos-x86_64': 'macosx_10_9_x86_64',
'macos-aarch64': 'macosx_11_0_arm64',
'linux-i386': 'manylinux_2_12_i686.manylinux2010_i686',
'linux-x86_64': 'manylinux_2_12_x86_64.manylinux2010_x86_64',
'linux-aarch64': 'manylinux_2_17_aarch64.manylinux2014_aarch64',
}.items():
zig_url = f'https://ziglang.org/download/{zig_version}/zig-{zig_platform}-{zig_version}.' + \
('zip' if zig_platform.startswith('windows-') else 'tar.xz')
with urllib.request.urlopen(zig_url) as request:
zig_archive = request.read()
print(f'{hashlib.sha256(zig_archive).hexdigest()} {zig_url}')
wheel_path = write_ziglang_wheel('dist/',
version=zig_version,
platform=python_platform,
archive=zig_archive)
with open(wheel_path, 'rb') as wheel:
print(f' {hashlib.sha256(wheel.read()).hexdigest()} {wheel_path}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment