summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Holth <dholth@fastmail.fm>2014-11-12 21:35:50 -0500
committerDaniel Holth <dholth@fastmail.fm>2014-11-12 21:35:50 -0500
commit9ae55eea1a70044e482190a46dae3aff568ad9d1 (patch)
tree792aba5e971812ae92b96acf4bc5da0b540e6c66
parent764378f045d4fe03a6773d0f0c295588a36613b6 (diff)
parente0f1cd0546041cef1c963d3fe755e1a1cdf64f93 (diff)
downloadwheel-9ae55eea1a70044e482190a46dae3aff568ad9d1.tar.gz
merge
-rw-r--r--CHANGES.txt14
-rw-r--r--METADATA.in30
-rw-r--r--docs/index.rst32
-rw-r--r--entry_points.txt5
-rw-r--r--setup.cfg9
-rw-r--r--setup.py23
-rw-r--r--tox.ini1
-rw-r--r--wheel/__init__.py2
-rw-r--r--wheel/bdist_wheel.py9
-rw-r--r--wheel/metadata.py72
-rw-r--r--wheel/pep425tags.py5
-rw-r--r--wheel/test/pydist-schema.json195
-rw-r--r--wheel/tool/__init__.py2
-rw-r--r--wheel/util.py21
-rw-r--r--wscript133
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
diff --git a/setup.cfg b/setup.cfg
index 9a562c0..99207b9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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]
diff --git a/setup.py b/setup.py
index 643840c..62cd584 100644
--- a/setup.py
+++ b/setup.py
@@ -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]
diff --git a/tox.ini b/tox.ini
index 18fa2c8..f1e6a10 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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
diff --git a/wscript b/wscript
new file mode 100644
index 0000000..c1ee262
--- /dev/null
+++ b/wscript
@@ -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())
+