Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PEP 639: support for Metadata-Version: 2.4; Part II #26

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions make_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import logging
import io
import os
import re
import json
import hashlib
import tarfile
from warnings import warn
import urllib.request
from pathlib import Path
from pathlib import Path, PurePath
from email.message import EmailMessage
from wheel.wheelfile import WheelFile
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
Expand All @@ -28,6 +30,8 @@
'powerpc64le-linux': 'manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le',
}

_YELLOW = "\033[93m"


class ReproducibleWheelFile(WheelFile):
def writestr(self, zinfo_or_arcname, data, *args, **kwargs):
Expand Down Expand Up @@ -70,13 +74,17 @@ def write_wheel_file(filename, contents):
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'
filtered_metadata = []
for header, value in metadata:
filtered_metadata.append((header, value))

return write_wheel_file(os.path.join(out_dir, wheel_name), {
**contents,
f'{dist_info}/METADATA': make_message([
('Metadata-Version', '2.4'),
('Name', name),
('Version', version),
*metadata,
*filtered_metadata,
], description),
f'{dist_info}/WHEEL': make_message([
('Wheel-Version', '1.0'),
Expand Down Expand Up @@ -107,17 +115,75 @@ def write_ziglang_wheel(out_dir, *, version, platform, archive):
contents = {}
contents['ziglang/__init__.py'] = b''

license_files = {}
found_license_files = set()
potential_extra_licenses = set()

# A bunch of standard license file patterns. If a file matches any of
# these, we need to add them to required_license_paths and metadata.
license_patterns = [
r'COPYING.*',
r'COPYRIGHT.*',
r'COPYLEFT.*',
r'LICEN[CS]E.*',
r'LICEN[CS]E-.*',
r'LICEN[CS]E\..*',
r'PATENTS.*',
r'NOTICE.*',
r'LEGAL.*',
r'AUTHORS.*',
r'RIGHT*',
r'PERMISSION*',
r'THIRD[-_]PARTY[-_]LICENSES?.*',
r'EULA*',
r'MIT*',
r'GPL*',
r'AGPL*',
r'LGPL*',
r'APACHE*',
]
license_regex = re.compile('|'.join(f'^{pattern}$' for pattern in license_patterns), re.IGNORECASE)

# These file paths MUST remain in sync with the paths in the official
# Zig tarballs and with the ones defined below in the metadata. The
# script will raise an error if any of these files are not found in
# the archive.
required_license_paths = [
'LICENSE',
'lib/libc/glibc/LICENSES',
'lib/libc/mingw/COPYING',
'lib/libc/musl/COPYRIGHT',
'lib/libc/wasi/LICENSE',
'lib/libc/wasi/LICENSE-APACHE',
'lib/libc/wasi/LICENSE-APACHE-LLVM',
'lib/libc/wasi/LICENSE-MIT',
'lib/libc/wasi/libc-bottom-half/cloudlibc/LICENSE',
'lib/libc/wasi/libc-top-half/musl/COPYRIGHT',
'lib/libcxx/LICENSE.TXT',
'lib/libcxxabi/LICENSE.TXT',
'lib/libunwind/LICENSE.TXT',
]

for entry_name, entry_mode, entry_data in iter_archive_contents(archive):
entry_name = '/'.join(entry_name.split('/')[1:])
if not entry_name:
continue
if entry_name.startswith('doc/'):
continue

# Check for additional license-like files
potential_license_filename = PurePath(entry_name).name
if license_regex.match(potential_license_filename):
potential_extra_licenses.add(entry_name)

zip_info = ZipInfo(f'ziglang/{entry_name}')
zip_info.external_attr = (entry_mode & 0xFFFF) << 16
contents[zip_info] = entry_data

if entry_name in required_license_paths:
license_files[entry_name] = entry_data
found_license_files.add(entry_name)

if entry_name.startswith('zig'):
contents['ziglang/__main__.py'] = f'''\
import os, sys
Expand All @@ -128,25 +194,54 @@ def write_ziglang_wheel(out_dir, *, version, platform, archive):
import subprocess; sys.exit(subprocess.call(argv))
'''.encode('ascii')

# 1. Check for missing required licenses paths
missing_licenses = set(required_license_paths) - found_license_files
if missing_licenses:
error_message = (
f"{_YELLOW}The following required license files were not found in the Zig archive: {', '.join(sorted(missing_licenses))} "
f"\nThis may indicate a change in Zig's license file structure or an error in the listing of license files and/or paths.{_YELLOW}"
)
raise RuntimeError(error_message)

# 2. Check for potentially missing license files
extra_licenses = potential_extra_licenses - set(required_license_paths)
if extra_licenses:
error_message = (
f"{_YELLOW}Found additional potential license files in the Zig archive but not included in the metadata: {', '.join(sorted(extra_licenses))} "
f"\nPlease consider adding these to the license paths if they should be included.{_YELLOW}"
)
raise RuntimeError(error_message)

with open('README.pypi.md') as f:
description = f.read()

dist_info = f'ziglang-{version}.dist-info'
for license_path, license_data in license_files.items():
contents[f'{dist_info}/licenses/ziglang/{license_path}'] = license_data

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'; charset=UTF-8; variant=GFM"),
# The license expression and the file paths MUST remain in sync
# with the paths in the official Zig tarballs and with the ones
# defined above in the contents. The difference is that these
# are prefixed with "ziglang/" to match the paths in the wheel
# for metadata compliance.
('License-Expression', 'MIT'),
('License-File', 'LICENSE'),
('License-File', 'ziglang/LICENSE'),
('License-File', 'ziglang/lib/libc/glibc/LICENSES'),
('License-File', 'ziglang/lib/libc/mingw/COPYING'),
('License-File', 'ziglang/lib/libc/musl/COPYRIGHT'),
('License-File', 'ziglang/lib/libc/wasi/LICENSE'),
('License-File', 'ziglang/lib/libc/wasi/LICENSE-APACHE'),
('License-File', 'ziglang/lib/libc/wasi/LICENSE-APACHE-LLVM'),
('License-File', 'ziglang/lib/libc/wasi/LICENSE-MIT'),
('License-File', 'ziglang/lib/libc/wasi/libc-bottom-half/cloudlibc/LICENSE'),
('License-File', 'ziglang/lib/libc/wasi/libc-top-half/musl/COPYRIGHT'),
('License-File', 'ziglang/lib/libcxx/LICENSE.TXT'),
('License-File', 'ziglang/lib/libcxxabi/LICENSE.TXT'),
('License-File', 'ziglang/lib/libunwind/LICENSE.TXT'),
Expand Down