Skip to content

Instantly share code, notes, and snippets.

@lifthrasiir
Last active September 11, 2023 10:17
Show Gist options
  • Save lifthrasiir/0da11dacf24e14cb32960b1b92dc4212 to your computer and use it in GitHub Desktop.
Save lifthrasiir/0da11dacf24e14cb32960b1b92dc4212 to your computer and use it in GitHub Desktop.
Custom Zig PyPI packaging script
# This script builds a custom Zig distribution into a Python wheel.
#
# The "custom" distribution refers to any directory resulting from
# zig-bootstrap that doesn't make into public nightly or stable releases.
# The existing ziglang/zig-pypi doesn't work with such cases, hence
# this script. It still makes use of a large portion of make_wheels.py,
# so should be put into the same directory in order to function.
#
# This script is derived from make_wheels.py from zig-pypi (commit
# 5b30070bc), and thus distributed under the same license (MIT/Expat).
#
# Kang Seonghoon, 2023-09-11
import sys
import os
import subprocess
import json
from zipfile import ZipInfo
try:
import libarchive
except ImportError as e: # this script works without libarchive
import types
sys.modules['libarchive'] = types.ModuleType('libarchive')
from make_wheels import write_wheel, ZIG_PYTHON_PLATFORMS
def write_ziglang_wheel_from_dir(out_dir, *, version, platform, built_dir):
built_dir = built_dir.rstrip(os.sep)
contents = {}
contents['ziglang/__init__.py'] = b''
for base, dirs, files in os.walk(built_dir):
assert base.startswith(built_dir)
base_in_wheel = 'ziglang' + base[len(built_dir):].replace(os.sep, '/')
for name in files:
path = os.path.join(base, name)
path_in_wheel = f'{base_in_wheel}/{name}'
stat = os.stat(path)
zip_info = ZipInfo(path_in_wheel)
zip_info.external_attr = (stat.st_mode & 0xFFFF) << 16
with open(path, 'rb') as f:
contents[zip_info] = f.read()
if path_in_wheel.startswith('ziglang/zig'):
contents['ziglang/__main__.py'] = f'''\
import os, sys
argv = [os.path.join(os.path.dirname(__file__), "{name}"), *sys.argv[1:]]
if os.name == 'posix':
os.execv(argv[0], argv)
else:
import subprocess; sys.exit(subprocess.call(argv))
'''.encode('ascii')
with open('README.pypi.md') as f:
description = f.read()
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,
)
def main(built_dir, out_dir='.'):
built_dir = os.path.realpath(built_dir)
env_json = subprocess.check_output([os.path.join(built_dir, 'zig'), 'env'])
env = json.loads(env_json)
# ensure that we are not packaging the usual development binary
for key in ('zig_exe', 'lib_dir', 'std_dir'):
if os.path.commonpath([built_dir, os.path.realpath(env[key])]) != built_dir:
raise RuntimeError(f'`zig env` returned {key}={env[key]!r}, which is outside of the built directory {built_dir!r}')
version = env['version'].replace('-dev', '.dev')
target, _, _ = env['target'].partition('.')
platform = ZIG_PYTHON_PLATFORMS[target]
write_ziglang_wheel_from_dir(out_dir, version=version, platform=platform, built_dir=built_dir)
if __name__ == '__main__':
if not (2 <= len(sys.argv) <= 3):
print(f'Usage: {sys.argv[0]} <directory to the built zig> [<directory to write a wheel (default `.`)>]', file=sys.stderr)
raise SystemExit(1)
else:
main(*sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment