diff options
author | Daniel Holth <dholth@fastmail.fm> | 2014-11-12 21:35:50 -0500 |
---|---|---|
committer | Daniel Holth <dholth@fastmail.fm> | 2014-11-12 21:35:50 -0500 |
commit | 9ae55eea1a70044e482190a46dae3aff568ad9d1 (patch) | |
tree | 792aba5e971812ae92b96acf4bc5da0b540e6c66 | |
parent | 764378f045d4fe03a6773d0f0c295588a36613b6 (diff) | |
parent | e0f1cd0546041cef1c963d3fe755e1a1cdf64f93 (diff) | |
download | wheel-9ae55eea1a70044e482190a46dae3aff568ad9d1.tar.gz |
merge
-rw-r--r-- | CHANGES.txt | 14 | ||||
-rw-r--r-- | METADATA.in | 30 | ||||
-rw-r--r-- | docs/index.rst | 32 | ||||
-rw-r--r-- | entry_points.txt | 5 | ||||
-rw-r--r-- | setup.cfg | 9 | ||||
-rw-r--r-- | setup.py | 23 | ||||
-rw-r--r-- | tox.ini | 1 | ||||
-rw-r--r-- | wheel/__init__.py | 2 | ||||
-rw-r--r-- | wheel/bdist_wheel.py | 9 | ||||
-rw-r--r-- | wheel/metadata.py | 72 | ||||
-rw-r--r-- | wheel/pep425tags.py | 5 | ||||
-rw-r--r-- | wheel/test/pydist-schema.json | 195 | ||||
-rw-r--r-- | wheel/tool/__init__.py | 2 | ||||
-rw-r--r-- | wheel/util.py | 21 | ||||
-rw-r--r-- | wscript | 133 |
15 files changed, 403 insertions, 150 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index d5b74e2..26754c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,17 @@ +0.24.0 +====== +- The python tag used for pure-python packages is now .pyN (major version + only). This change actually occurred in 0.23.0 when the --python-tag + option was added, but was not explicitly mentioned in the changelog then. +- wininst2wheel and egg2wheel removed. Use "wheel convert [archive]" + instead. +- Wheel now supports setuptools style conditional requirements via the + extras_require={} syntax. Separate 'extra' names from conditions using + the : character. Wheel's own setup.py does this. (The empty-string + extra is the same as install_requires.) These conditional requirements + should work the same whether the package is installed by wheel or + by setup.py. + 0.23.0 ====== - Compatibiltiy tag flags added to the bdist_wheel command diff --git a/METADATA.in b/METADATA.in new file mode 100644 index 0000000..2827551 --- /dev/null +++ b/METADATA.in @@ -0,0 +1,30 @@ +Metadata-Version: 2.0 +Name: wheel +Version: ${VERSION} +Summary: A built-package format for Python. +Home-page: http://bitbucket.org/pypa/wheel/ +Author: Daniel Holth +Author-email: dholth@fastmail.fm +License: MIT +Keywords: wheel,packaging +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Provides-Extra: tool +Provides-Extra: signatures +Requires-Dist: keyring; extra == 'signatures' +Provides-Extra: faster-signatures +Requires-Dist: ed25519ll; extra == 'faster-signatures' +Requires-Dist: argparse; python_version=="2.6" +Provides-Extra: signatures +Requires-Dist: pyxdg; sys_platform!="win32" and extra == 'signatures' + +${DESCRIPTION} diff --git a/docs/index.rst b/docs/index.rst index 7575c40..5b1b157 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -115,11 +115,19 @@ used to specify the Python version tag to use more precisely:: Neither of these two flags have any effect when used on a project that includes C extension code. +The default for a pure Python project (if no explicit flags are given) is "pyN" +where N is the major version of the Python interpreter used to build the wheel. +This is generally the correct choice, as projects would not typically ship +different wheels for different minor versions of Python. + A reasonable use of the `--python-tag` argument would be for a project that uses Python syntax only introduced in a particular Python version. There are no current examples of this, but if wheels had been available when Python 2.5 was released (the first version containing the `with` statement), wheels for a project that used the `with` statement would typically use `--python-tag py25`. +However, unless a separate version of the wheel was shipped which avoided the +use of the new syntax, there is little benefit in explicitly marking the tag in +this manner. Typically, projects would not specify Python tags on the command line, but would use `setup.cfg` to set them as a project default:: @@ -132,6 +140,30 @@ or:: [bdist_wheel] python-tag = py32 +Defining conditional dependencies +--------------------------------- + +In wheel, the only way to have conditional dependencies (that might only be +needed on certain platforms) is to use environment markers as defined by +PEP 426. + +As of wheel 0.24.0, the recommended way to do this is in the setuptools +`extras_require` parameter. A `:` separates the extra name from the marker. +Wheel's own setup.py has an example:: + extras_require={ + ':python_version=="2.6"': ['argparse'], + 'signatures': ['keyring'], + 'signatures:sys_platform!="win32"': ['pyxdg'], + 'faster-signatures': ['ed25519ll'], + 'tool': [] + }, + +The extra named '' signifies a default requirement, as if it was passed to +`install_requires`. + +Older versions of bdist_wheel supported passing requirements in a +now-deprecated [metadata] section in setup.cfg. + Automatically sign wheel files ------------------------------ diff --git a/entry_points.txt b/entry_points.txt new file mode 100644 index 0000000..f57b8c0 --- /dev/null +++ b/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +wheel = wheel.tool:main + +[distutils.commands] +bdist_wheel = wheel.bdist_wheel:bdist_wheel
\ No newline at end of file @@ -2,15 +2,6 @@ addopts=--ignore=dist --ignore=build --cov=wheel [metadata] -provides-extra = - tool - signatures - faster-signatures -requires-dist = - argparse; python_version == '2.6' - keyring; extra == 'signatures' - pyxdg; sys.platform != 'win32' and extra == 'signatures' - ed25519ll; extra == 'faster-signatures' license-file = LICENSE.txt [bdist_wheel] @@ -1,4 +1,4 @@ -import os.path, sys, codecs, re +import os.path, codecs, re from setuptools import setup @@ -10,17 +10,6 @@ with codecs.open(os.path.join(os.path.dirname(__file__), 'wheel', '__init__.py') encoding='utf8') as version_file: metadata = dict(re.findall(r"""__([a-z]+)__ = "([^"]+)""", version_file.read())) -# -# All these requirements are overridden by setup.cfg when wheel is built -# as a wheel: -# -signature_reqs = ['keyring'] -if sys.platform != 'win32': - signature_reqs.append('pyxdg') -install_requires = [] -if sys.version_info[:2] < (2, 7): - install_requires.append('argparse') - setup(name='wheel', version=metadata['version'], description='A built-package format for Python.', @@ -35,10 +24,11 @@ setup(name='wheel', "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", ], author='Daniel Holth', author_email='dholth@fastmail.fm', - url='http://bitbucket.org/dholth/wheel/', + url='https://bitbucket.org/pypa/wheel/', keywords=['wheel', 'packaging'], license='MIT', packages=[ @@ -47,9 +37,10 @@ setup(name='wheel', 'wheel.tool', 'wheel.signatures' ], - install_requires=install_requires, extras_require={ - 'signatures': signature_reqs, + ':python_version=="2.6"': ['argparse'], + 'signatures': ['keyring'], + 'signatures:sys_platform!="win32"': ['pyxdg'], 'faster-signatures': ['ed25519ll'], 'tool': [] }, @@ -58,8 +49,6 @@ setup(name='wheel', zip_safe=False, entry_points = """\ [console_scripts] -wininst2wheel = wheel.wininst2wheel:main -egg2wheel = wheel.egg2wheel:main wheel = wheel.tool:main [distutils.commands] @@ -11,7 +11,6 @@ commands = py.test deps = jsonschema - coverage pytest pytest-cov wheel[tool,signatures] diff --git a/wheel/__init__.py b/wheel/__init__.py index df7d28d..b84d0e4 100644 --- a/wheel/__init__.py +++ b/wheel/__init__.py @@ -1,2 +1,2 @@ # __variables__ with double-quoted values will be available in setup.py: -__version__ = "0.23.0" +__version__ = "0.25.0" diff --git a/wheel/bdist_wheel.py b/wheel/bdist_wheel.py index 5e9318a..ac770e9 100644 --- a/wheel/bdist_wheel.py +++ b/wheel/bdist_wheel.py @@ -400,16 +400,14 @@ class bdist_wheel(Command): description_filename) with open(description_path, "wb") as description_file: description_file.write(description_text.encode('utf-8')) - pymeta['document_names'] = pymeta.get('document_names', {}) - pymeta['document_names']['description'] = description_filename + pymeta['extensions']['python.details']['document_names']['description'] = description_filename # XXX heuristically copy any LICENSE/LICENSE.txt? license = self.license_file() if license: license_filename = 'LICENSE.txt' shutil.copy(license, os.path.join(self.distinfo_dir, license_filename)) - pymeta['document_names'] = pymeta.get('document_names', {}) - pymeta['document_names']['license'] = license_filename + pymeta['extensions']['python.details']['document_names']['license'] = license_filename with open(metadata_json_path, "w") as metadata_json: json.dump(pymeta, metadata_json) @@ -424,7 +422,8 @@ class bdist_wheel(Command): def walk(): for dir, dirs, files in os.walk(bdist_dir): - for f in files: + dirs.sort() + for f in sorted(files): yield os.path.join(dir, f) def skip(path): diff --git a/wheel/metadata.py b/wheel/metadata.py index 423d9b4..02c0663 100644 --- a/wheel/metadata.py +++ b/wheel/metadata.py @@ -2,8 +2,13 @@ Tools for converting old- to new-style metadata. """ -from collections import defaultdict, namedtuple +from collections import namedtuple from .pkginfo import read_pkg_info +from .util import OrderedDefaultDict +try: + from collections import OrderedDict +except ImportError: + OrderedDict = dict import re import os.path @@ -49,10 +54,10 @@ def unique(iterable): def handle_requires(metadata, pkg_info, key): """ - Place the runtime requirements from pkg_info into metadata. + Place the runtime requirements from pkg_info into metadata. """ - may_requires = defaultdict(list) - for value in pkg_info.get_all(key): + may_requires = OrderedDefaultDict(list) + for value in sorted(pkg_info.get_all(key)): extra_match = EXTRA_RE.search(value) if extra_match: groupdict = extra_match.groupdict() @@ -70,7 +75,7 @@ def handle_requires(metadata, pkg_info, key): if may_requires: metadata['run_requires'] = [] for key, value in may_requires.items(): - may_requirement = {'requires':value} + may_requirement = OrderedDict((('requires', value),)) if key.extra: may_requirement['extra'] = key.extra if key.condition: @@ -85,15 +90,16 @@ def handle_requires(metadata, pkg_info, key): def pkginfo_to_dict(path, distribution=None): """ Convert PKG-INFO to a prototype Metadata 2.0 (PEP 426) dict. - - The description is included under the key ['description'] rather than + + The description is included under the key ['description'] rather than being written to a separate file. - + path: path to PKG-INFO file distribution: optional distutils Distribution() """ - metadata = {"generator":"bdist_wheel (" + wheel.__version__ + ")"} + metadata = OrderedDefaultDict(lambda: OrderedDefaultDict(lambda: OrderedDefaultDict(OrderedDict))) + metadata["generator"] = "bdist_wheel (" + wheel.__version__ + ")" try: unicode pkg_info = read_pkg_info(path) @@ -120,7 +126,7 @@ def pkginfo_to_dict(path, distribution=None): if description: pkg_info['description'] = description - for key in unique(k.lower() for k in pkg_info.keys()): + for key in sorted(unique(k.lower() for k in pkg_info.keys())): low_key = key.replace('-', '_') if low_key in SKIP_FIELDS: @@ -129,7 +135,7 @@ def pkginfo_to_dict(path, distribution=None): if low_key in UNKNOWN_FIELDS and pkg_info.get(key) == 'UNKNOWN': continue - if low_key in PLURAL_FIELDS: + if low_key in sorted(PLURAL_FIELDS): metadata[PLURAL_FIELDS[low_key]] = pkg_info.get_all(key) elif low_key == "requires_dist": @@ -141,7 +147,10 @@ def pkginfo_to_dict(path, distribution=None): metadata['extras'].extend(pkg_info.get_all(key)) elif low_key == 'home_page': - metadata['project_urls'] = {'Home':pkg_info[key]} + metadata['extensions']['python.details']['project_urls'] = {'Home':pkg_info[key]} + + elif low_key == 'keywords': + metadata['keywords'] = KEYWORDS_RE.split(pkg_info[key]) else: metadata[low_key] = pkg_info[key] @@ -157,7 +166,7 @@ def pkginfo_to_dict(path, distribution=None): try: requirements = getattr(distribution, attr) if isinstance(requirements, list): - new_requirements = list(convert_requirements(requirements)) + new_requirements = sorted(convert_requirements(requirements)) metadata[requires] = [{'requires':new_requirements}] except AttributeError: pass @@ -165,38 +174,38 @@ def pkginfo_to_dict(path, distribution=None): # handle contacts contacts = [] for contact_type, role in CONTACT_FIELDS: - contact = {} - for key in contact_type: + contact = OrderedDict() + for key in sorted(contact_type): if contact_type[key] in metadata: contact[key] = metadata.pop(contact_type[key]) if contact: contact['role'] = role contacts.append(contact) if contacts: - metadata['contacts'] = contacts + metadata['extensions']['python.details']['contacts'] = contacts # convert entry points to exports try: with open(os.path.join(os.path.dirname(path), "entry_points.txt"), "r") as ep_file: ep_map = pkg_resources.EntryPoint.parse_map(ep_file.read()) - exports = {} - for group, items in ep_map.items(): - exports[group] = {} - for item in items.values(): + exports = OrderedDict() + for group, items in sorted(ep_map.items()): + exports[group] = OrderedDict() + for item in sorted(items.values()): name, export = str(item).split(' = ', 1) exports[group][name] = export if exports: - metadata['exports'] = exports + metadata['extensions']['python.exports'] = exports except IOError: pass # copy console_scripts entry points to commands - if 'exports' in metadata: + if 'python.exports' in metadata['extensions']: for (ep_script, wrap_script) in (('console_scripts', 'wrap_console'), ('gui_scripts', 'wrap_gui')): - if ep_script in metadata['exports']: - metadata['commands'] = metadata.get('commands', {}) - metadata['commands'][wrap_script] = metadata['exports'][ep_script] + if ep_script in metadata['extensions']['python.exports']: + metadata['extensions']['python.commands'][wrap_script] = \ + metadata['extensions']['python.exports'][ep_script] return metadata @@ -229,12 +238,19 @@ def pkginfo_to_metadata(egg_info_path, pkginfo_path): requires_path = os.path.join(egg_info_path, 'requires.txt') if os.path.exists(requires_path): requires = open(requires_path).read() - for extra, reqs in pkg_resources.split_sections(requires): + for extra, reqs in sorted(pkg_resources.split_sections(requires), + key=lambda x: x[0] or ''): condition = '' + if extra and ':' in extra: # setuptools extra:condition syntax + extra, condition = extra.split(':', 1) if extra: pkg_info['Provides-Extra'] = extra - condition = '; extra == %s' % repr(extra) - for new_req in convert_requirements(reqs): + if condition: + condition += " and " + condition += 'extra == %s' % repr(extra) + if condition: + condition = '; ' + condition + for new_req in sorted(convert_requirements(reqs)): pkg_info['Requires-Dist'] = new_req + condition description = pkg_info['Description'] diff --git a/wheel/pep425tags.py b/wheel/pep425tags.py index 3af04a1..a6220ea 100644 --- a/wheel/pep425tags.py +++ b/wheel/pep425tags.py @@ -85,7 +85,10 @@ def get_supported(versions=None): # Tagged specifically as being cross-version compatible # (with just the major version specified) supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) - + + # Major Python version + platform; e.g. binaries not using the Python API + supported.append(('py%s' % (versions[0][0]), 'none', arch)) + # No abi / arch, generic Python for i, version in enumerate(versions): supported.append(('py%s' % (version,), 'none', 'any')) diff --git a/wheel/test/pydist-schema.json b/wheel/test/pydist-schema.json index fc350bf..566f3a4 100644 --- a/wheel/test/pydist-schema.json +++ b/wheel/test/pydist-schema.json @@ -38,63 +38,6 @@ "description": "A one-line summary of what the distribution does.", "type": "string" }, - "document_names": { - "description": "Names of supporting metadata documents", - "type": "object", - "properties": { - "description": { - "type": "string", - "$ref": "#/definitions/document_name" - }, - "changelog": { - "type": "string", - "$ref": "#/definitions/document_name" - }, - "license": { - "type": "string", - "$ref": "#/definitions/document_name" - } - }, - "additionalProperties": false - }, - "keywords": { - "description": "A list of additional keywords to be used to assist searching for the distribution in a larger catalog.", - "type": "array", - "items": { - "type": "string" - } - }, - "license": { - "description": "A string indicating the license covering the distribution.", - "type": "string" - }, - "classifiers": { - "description": "A list of strings, with each giving a single classification value for the distribution.", - "type": "array", - "items": { - "type": "string" - } - }, - "contacts": { - "description": "A list of contributor entries giving the recommended contact points for getting more information about the project.", - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/contact" - } - }, - "contributors": { - "description": "A list of contributor entries for other contributors not already listed as current project points of contact.", - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/contact" - } - }, - "project_urls": { - "description": "A mapping of arbitrary text labels to additional URLs relevant to the project.", - "type": "object" - }, "extras": { "description": "A list of optional sets of dependencies that may be used to define conditional dependencies in \"may_require\" and similar fields.", "type": "array", @@ -152,16 +95,6 @@ "$ref": "#/definitions/qualified_name" } }, - "commands": { - "description": "Command line interfaces provided by this distribution", - "type": "object", - "$ref": "#/definitions/commands" - }, - "exports": { - "description": "Other exported interfaces provided by this distribution", - "type": "object", - "$ref": "#/definitions/exports" - }, "obsoleted_by": { "description": "A string that indicates that this project is no longer being developed. The named project provides a substitute or replacement.", "type": "string", @@ -248,6 +181,76 @@ "required": ["requires"], "additionalProperties": false }, + "extensions": { + "type": "object", + "patternProperties": { + "^[A-Za-z][0-9A-Za-z_]*([.][0-9A-Za-z_]*)*$": {} + }, + "properties": { + "python.details" : { + "description": "More information regarding the distribution.", + "type": "object", + "properties": { + "document_names": { + "description": "Names of supporting metadata documents", + "type": "object", + "properties": { + "description": { + "type": "string", + "$ref": "#/definitions/document_name" + }, + "changelog": { + "type": "string", + "$ref": "#/definitions/document_name" + }, + "license": { + "type": "string", + "$ref": "#/definitions/document_name" + } + }, + "additionalProperties": false + }, + "keywords": { + "description": "A list of additional keywords to be used to assist searching for the distribution in a larger catalog.", + "type": "array", + "items": { + "type": "string" + } + }, + "license": { + "description": "A string indicating the license covering the distribution.", + "type": "string" + }, + "classifiers": { + "description": "A list of strings, with each giving a single classification value for the distribution.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "python.project" : { + "description": "More information regarding the creation and maintenance of the distribution.", + "$ref": "#/definitions/project_or_integrator" + }, + "python.integrator" : { + "description": "More information regarding the downstream redistributor of the distribution.", + "$ref": "#/definitions/project_or_integrator" + }, + "python.commands" : { + "description": "Command line interfaces provided by this distribution", + "type": "object", + "$ref": "#/definitions/commands" + }, + "python.exports" : { + "description": "Other exported interfaces provided by this distribution", + "type": "object", + "$ref": "#/definitions/exports" + } + }, + "additionalProperties": false + }, "commands": { "type": "object", "properties": { @@ -285,13 +288,6 @@ }, "additionalProperties": false }, - "extensions": { - "type": "object", - "patternProperties": { - "^[A-Za-z][0-9A-Za-z_]*([.][0-9A-Za-z_]*)*$": {} - }, - "additionalProperties": false - }, "command_map": { "type": "object", "patternProperties": { @@ -302,40 +298,65 @@ }, "additionalProperties": false }, + "project_or_integrator" : { + "type": "object", + "properties" : { + "contacts": { + "description": "A list of contributor entries giving the recommended contact points for getting more information about the project.", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/contact" + } + }, + "contributors": { + "description": "A list of contributor entries for other contributors not already listed as current project points of contact.", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/contact" + } + }, + "project_urls": { + "description": "A mapping of arbitrary text labels to additional URLs relevant to the project.", + "type": "object" + } + } + }, "distribution_name": { - "type": "string", - "pattern": "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$" + "type": "string", + "pattern": "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$" }, "requirement": { - "type": "string" + "type": "string" }, "provides_declaration": { - "type": "string" + "type": "string" }, "environment_marker": { - "type": "string" + "type": "string" }, "document_name": { - "type": "string" + "type": "string" }, "extra_name" : { - "type": "string", - "pattern": "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$" + "type": "string", + "pattern": "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$" }, "relative_path" : { - "type": "string" + "type": "string" }, "export_specifier": { "type": "string", "pattern": "^([A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_][A-Za-z_0-9]*)*)(:[A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_][A-Za-z_0-9]*)*)?(\\[[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?\\])?$" }, "qualified_name" : { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_][A-Za-z_0-9]*)*$" + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_][A-Za-z_0-9]*)*$" }, "prefixed_name" : { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_0-9]*)*$" + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z_0-9]*([.][A-Za-z_0-9]*)*$" } } } diff --git a/wheel/tool/__init__.py b/wheel/tool/__init__.py index de1c70c..a997d1f 100644 --- a/wheel/tool/__init__.py +++ b/wheel/tool/__init__.py @@ -30,7 +30,7 @@ def get_keyring(): from ..signatures import keys import keyring except ImportError: - raise WheelError("Install wheel[signatures] (requires keyring, dirspec) for signatures.") + raise WheelError("Install wheel[signatures] (requires keyring, pyxdg) for signatures.") return keys.WheelKeys, keyring def keygen(get_keyring=get_keyring): diff --git a/wheel/util.py b/wheel/util.py index bc2eb5e..5268813 100644 --- a/wheel/util.py +++ b/wheel/util.py @@ -5,6 +5,10 @@ import os import base64 import json import hashlib +try: + from collections import OrderedDict +except ImportError: + OrderedDict = dict __all__ = ['urlsafe_b64encode', 'urlsafe_b64decode', 'utf8', 'to_json', 'from_json', 'matches_requirement'] @@ -91,6 +95,23 @@ class HashingFile(object): digest = self.hash.digest() return self.hashtype + '=' + native(urlsafe_b64encode(digest)) +class OrderedDefaultDict(OrderedDict): + def __init__(self, *args, **kwargs): + if not args: + self.default_factory = None + else: + if not (args[0] is None or callable(args[0])): + raise TypeError('first argument must be callable or None') + self.default_factory = args[0] + args = args[1:] + super(OrderedDefaultDict, self).__init__(*args, **kwargs) + + def __missing__ (self, key): + if self.default_factory is None: + raise KeyError(key) + self[key] = default = self.default_factory() + return default + if sys.platform == 'win32': import ctypes.wintypes # CSIDL_APPDATA for reference - not used here for compatibility with @@ -0,0 +1,133 @@ +APPNAME = 'wheel' +WHEEL_TAG = 'py2.py3-none-any' +VERSION = '0.24.0' + +top = '.' +out = 'build' + +from waflib import Utils + +def options(opt): + opt.load('python') + +def configure(ctx): + ctx.load('python') + ctx.check_python_version() + +def build(bld): + bld(features='py', + source=bld.path.ant_glob('wheel/**/*.py', excl="wheel/test"), + install_from='.') + + # build the wheel: + + DIST_INFO = '%s-%s.dist-info' % (APPNAME, VERSION) + + node = bld.path.get_bld().make_node(DIST_INFO) + if not os.path.exists(node.abspath()): + os.mkdir(node.abspath()) + + metadata = node.make_node('METADATA') + + import codecs, string + README = codecs.open('README.txt', encoding='utf8').read() + CHANGES = codecs.open('CHANGES.txt', encoding='utf8').read() + METADATA = codecs.open('METADATA.in', encoding='utf8').read() + METADATA = string.Template(METADATA).substitute( + VERSION=VERSION, + DESCRIPTION='\n\n'.join((README, CHANGES)) + ) + + bld(source='METADATA.in', + target=metadata, + rule=lambda tsk: Utils.writef(tsk.outputs[0].abspath(), METADATA)) + + wheel = node.make_node('WHEEL') + + WHEEL="""Wheel-Version: 1.0 +Generator: waf (0.0.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any +""" + bld(target=wheel, + rule=lambda tsk: Utils.writef(tsk.outputs[0].abspath(), WHEEL)) + + # globs don't work, since here the files may not exist: + bld.install_files('${PYTHONDIR}/'+DIST_INFO, [DIST_INFO+'/WHEEL', DIST_INFO+'/METADATA']) + + # only if entry_points.txt exists: + bld.install_files('${PYTHONDIR}/'+DIST_INFO, ['entry_points.txt']) + +import shutil, os, base64 + +def urlsafe_b64encode(data): + """urlsafe_b64encode without padding""" + return base64.urlsafe_b64encode(data).rstrip(b'=') + +from waflib import Scripting +class WheelDist(Scripting.Dist): + def manifest(self): + """ + Add the wheel manifest. + """ + import hashlib + files = self.get_files() + lines = [] + for f in files: + print("File: %s" % f.relpath()) + size = os.stat(f.abspath()).st_size + digest = hashlib.sha256(open(f.abspath(), 'rb').read()).digest() + digest = "sha256="+(urlsafe_b64encode(digest).decode('ascii')) + lines.append("%s,%s,%s" % (f.path_from(self.base_path).replace(',', ',,'), digest, size)) + + record_path = '%s-%s.dist-info/RECORD' % (APPNAME, VERSION) + lines.append(record_path+',,') + RECORD = '\n'.join(lines) + + import zipfile + zip = zipfile.ZipFile(self.get_arch_name(), 'a') + zip.writestr(record_path, RECORD, zipfile.ZIP_DEFLATED) + zip.close() + +from waflib import Build +class package_cls(Build.InstallContext): + cmd = 'package' + fun = 'build' + + def init_dirs(self, *k, **kw): + super(package_cls, self).init_dirs(*k, **kw) + self.tmp = self.bldnode.make_node('package_tmp_dir') + try: + shutil.rmtree(self.tmp.abspath()) + except: + pass + if os.path.exists(self.tmp.abspath()): + self.fatal('Could not remove the temporary directory %r' % self.tmp) + self.tmp.mkdir() + self.options.destdir = self.tmp.abspath() + + def execute(self, *k, **kw): + back = self.options.destdir + try: + super(package_cls, self).execute(*k, **kw) + finally: + self.options.destdir = back + + files = self.tmp.ant_glob('**', excl=" **/*.pyc **/*.pyo") + + # we could mess with multiple inheritance but this is probably unnecessary + ctx = WheelDist() + ctx.algo = 'zip' + ctx.arch_name = '%s-%s-%s.whl' % (APPNAME, VERSION, WHEEL_TAG) + ctx.files = files + ctx.tar_prefix = '' + ctx.base_path = self.tmp + ctx.base_name = '' + ctx.archive() + + # add manifest... + ctx.manifest() + + shutil.rmtree(self.tmp.abspath()) + |