2
Wheel command-line utility.
10
from glob import iglob
11
from .. import signatures
12
from ..util import (urlsafe_b64decode, urlsafe_b64encode, native, binary,
14
from ..install import WheelFile, VerifyingZipFile
15
from ..paths import get_install_command
17
def require_pkgresources(name):
21
raise RuntimeError("'{0}' needs pkg_resources (part of setuptools).".format(name))
25
class WheelError(Exception): pass
30
from ..signatures import keys
32
assert keyring.get_keyring().priority
33
except (ImportError, AssertionError):
34
raise WheelError("Install wheel[signatures] (requires keyring, keyrings.alt, pyxdg) for signatures.")
35
return keys.WheelKeys, keyring
37
def keygen(get_keyring=get_keyring):
38
"""Generate a public/private key pair."""
39
WheelKeys, keyring = get_keyring()
41
ed25519ll = signatures.get_ed25519ll()
43
wk = WheelKeys().load()
45
keypair = ed25519ll.crypto_sign_keypair()
46
vk = native(urlsafe_b64encode(keypair.vk))
47
sk = native(urlsafe_b64encode(keypair.sk))
48
kr = keyring.get_keyring()
49
kr.set_password("wheel", vk, sk)
50
sys.stdout.write("Created Ed25519 keypair with vk={0}\n".format(vk))
51
sys.stdout.write("in {0!r}\n".format(kr))
53
sk2 = kr.get_password('wheel', vk)
55
raise WheelError("Keyring is broken. Could not retrieve secret key.")
57
sys.stdout.write("Trusting {0} to sign and verify all packages.\n".format(vk))
58
wk.add_signer('+', vk)
62
def sign(wheelfile, replace=False, get_keyring=get_keyring):
64
WheelKeys, keyring = get_keyring()
66
ed25519ll = signatures.get_ed25519ll()
68
wf = WheelFile(wheelfile, append=True)
69
wk = WheelKeys().load()
71
name = wf.parsed_filename.group('name')
72
sign_with = wk.signers(name)[0]
73
sys.stdout.write("Signing {0} with {1}\n".format(name, sign_with[1]))
76
kr = keyring.get_keyring()
77
sk = kr.get_password('wheel', vk)
78
keypair = ed25519ll.Keypair(urlsafe_b64decode(binary(vk)),
79
urlsafe_b64decode(binary(sk)))
82
record_name = wf.distinfo_name + '/RECORD'
83
sig_name = wf.distinfo_name + '/RECORD.jws'
84
if sig_name in wf.zipfile.namelist():
85
raise WheelError("Wheel is already signed.")
86
record_data = wf.zipfile.read(record_name)
87
payload = {"hash":"sha256=" + native(urlsafe_b64encode(hashlib.sha256(record_data).digest()))}
88
sig = signatures.sign(payload, keypair)
89
wf.zipfile.writestr(sig_name, json.dumps(sig, sort_keys=True))
92
def unsign(wheelfile):
94
Remove RECORD.jws from a wheel by truncating the zip file.
96
RECORD.jws must be at the end of the archive. The zip file must be an
97
ordinary archive, with the compressed files and the directory in the same
98
order, and without any non-zip content after the truncation point.
100
vzf = VerifyingZipFile(wheelfile, "a")
101
info = vzf.infolist()
102
if not (len(info) and info[-1].filename.endswith('/RECORD.jws')):
103
raise WheelError("RECORD.jws not found at end of archive.")
107
def verify(wheelfile):
110
The signature will be verified for internal consistency ONLY and printed.
111
Wheel's own unpack/install commands verify the manifest against the
112
signature and file contents.
114
wf = WheelFile(wheelfile)
115
sig_name = wf.distinfo_name + '/RECORD.jws'
116
sig = json.loads(native(wf.zipfile.open(sig_name).read()))
117
verified = signatures.verify(sig)
118
sys.stderr.write("Signatures are internally consistent.\n")
119
sys.stdout.write(json.dumps(verified, indent=2))
120
sys.stdout.write('\n')
122
def unpack(wheelfile, dest='.'):
125
Wheel content will be unpacked to {dest}/{name}-{ver}, where {name}
126
is the package name and {ver} its version.
128
:param wheelfile: The path to the wheel.
129
:param dest: Destination directory (default to current directory).
131
wf = WheelFile(wheelfile)
132
namever = wf.parsed_filename.group('namever')
133
destination = os.path.join(dest, namever)
134
sys.stderr.write("Unpacking to: %s\n" % (destination))
135
wf.zipfile.extractall(destination)
138
def install(requirements, requirements_file=None,
139
wheel_dirs=None, force=False, list_files=False,
143
:param requirements: A list of requirements or wheel files to install.
144
:param requirements_file: A file containing requirements to install.
145
:param wheel_dirs: A list of directories to search for wheels.
146
:param force: Install a wheel file even if it is not compatible.
147
:param list_files: Only list the files to install, don't install them.
148
:param dry_run: Do everything but the actual install.
151
# If no wheel directories specified, use the WHEELPATH environment
152
# variable, or the current directory if that is not set.
154
wheelpath = os.getenv("WHEELPATH")
156
wheel_dirs = wheelpath.split(os.pathsep)
158
wheel_dirs = [ os.path.curdir ]
160
# Get a list of all valid wheels in wheel_dirs
163
for w in os.listdir(d):
164
if w.endswith('.whl'):
165
wf = WheelFile(os.path.join(d, w))
167
all_wheels.append(wf)
169
# If there is a requirements file, add it to the list of requirements
170
if requirements_file:
171
# If the file doesn't exist, search for it in wheel_dirs
172
# This allows standard requirements files to be stored with the
174
if not os.path.exists(requirements_file):
176
name = os.path.join(d, requirements_file)
177
if os.path.exists(name):
178
requirements_file = name
181
with open(requirements_file) as fd:
182
requirements.extend(fd)
185
for req in requirements:
186
if req.endswith('.whl'):
187
# Explicitly specified wheel filename
188
if os.path.exists(req):
190
if wf.compatible or force:
191
to_install.append(wf)
193
msg = ("{0} is not compatible with this Python. "
194
"--force to install anyway.".format(req))
195
raise WheelError(msg)
197
# We could search on wheel_dirs, but it's probably OK to
198
# assume the user has made an error.
199
raise WheelError("No such wheel file: {}".format(req))
202
# We have a requirement spec
203
# If we don't have pkg_resources, this will raise an exception
204
matches = matches_requirement(req, all_wheels)
206
raise WheelError("No match for requirement {}".format(req))
207
to_install.append(max(matches))
209
# We now have a list of wheels to install
211
sys.stdout.write("Installing:\n")
216
for wf in to_install:
218
sys.stdout.write(" {0}\n".format(wf.filename))
220
wf.install(force=force)
223
def install_scripts(distributions):
225
Regenerate the entry_points console_scripts for the named distribution.
228
from setuptools.command import easy_install
231
raise RuntimeError("'wheel install_scripts' needs setuptools.")
233
for dist in distributions:
234
pkg_resources_dist = pkg_resources.get_distribution(dist)
235
install = get_install_command(dist)
236
command = easy_install.easy_install(install.distribution)
237
command.args = ['wheel'] # dummy argument
238
command.finalize_options()
239
command.install_egg_scripts(pkg_resources_dist)
241
def convert(installers, dest_dir, verbose):
242
require_pkgresources('wheel convert')
244
# Only support wheel convert if pkg_resources is present
245
from ..wininst2wheel import bdist_wininst2wheel
246
from ..egg2wheel import egg2wheel
248
for pat in installers:
249
for installer in iglob(pat):
250
if os.path.splitext(installer)[1] == '.egg':
253
conv = bdist_wininst2wheel
255
sys.stdout.write("{0}... ".format(installer))
257
conv(installer, dest_dir)
259
sys.stdout.write("OK\n")
262
p = argparse.ArgumentParser()
263
s = p.add_subparsers(help="commands")
267
keygen_parser = s.add_parser('keygen', help='Generate signing key')
268
keygen_parser.set_defaults(func=keygen_f)
272
sign_parser = s.add_parser('sign', help='Sign wheel')
273
sign_parser.add_argument('wheelfile', help='Wheel file')
274
sign_parser.set_defaults(func=sign_f)
277
unsign(args.wheelfile)
278
unsign_parser = s.add_parser('unsign', help=unsign.__doc__)
279
unsign_parser.add_argument('wheelfile', help='Wheel file')
280
unsign_parser.set_defaults(func=unsign_f)
283
verify(args.wheelfile)
284
verify_parser = s.add_parser('verify', help=verify.__doc__)
285
verify_parser.add_argument('wheelfile', help='Wheel file')
286
verify_parser.set_defaults(func=verify_f)
289
unpack(args.wheelfile, args.dest)
290
unpack_parser = s.add_parser('unpack', help='Unpack wheel')
291
unpack_parser.add_argument('--dest', '-d', help='Destination directory',
293
unpack_parser.add_argument('wheelfile', help='Wheel file')
294
unpack_parser.set_defaults(func=unpack_f)
297
install(args.requirements, args.requirements_file,
298
args.wheel_dirs, args.force, args.list_files)
299
install_parser = s.add_parser('install', help='Install wheels')
300
install_parser.add_argument('requirements', nargs='*',
301
help='Requirements to install.')
302
install_parser.add_argument('--force', default=False,
304
help='Install incompatible wheel files.')
305
install_parser.add_argument('--wheel-dir', '-d', action='append',
307
help='Directories containing wheels.')
308
install_parser.add_argument('--requirements-file', '-r',
309
help="A file containing requirements to "
311
install_parser.add_argument('--list', '-l', default=False,
314
help="List wheels which would be installed, "
315
"but don't actually install anything.")
316
install_parser.set_defaults(func=install_f)
318
def install_scripts_f(args):
319
install_scripts(args.distributions)
320
install_scripts_parser = s.add_parser('install-scripts', help='Install console_scripts')
321
install_scripts_parser.add_argument('distributions', nargs='*',
322
help='Regenerate console_scripts for these distributions')
323
install_scripts_parser.set_defaults(func=install_scripts_f)
326
convert(args.installers, args.dest_dir, args.verbose)
327
convert_parser = s.add_parser('convert', help='Convert egg or wininst to wheel')
328
convert_parser.add_argument('installers', nargs='*', help='Installers to convert')
329
convert_parser.add_argument('--dest-dir', '-d', default=os.path.curdir,
330
help="Directory to store wheels (default %(default)s)")
331
convert_parser.add_argument('--verbose', '-v', action='store_true')
332
convert_parser.set_defaults(func=convert_f)
335
from .. import __version__
336
sys.stdout.write("wheel %s\n" % __version__)
337
version_parser = s.add_parser('version', help='Print version and exit')
338
version_parser.set_defaults(func=version_f)
342
help_parser = s.add_parser('help', help='Show this help')
343
help_parser.set_defaults(func=help_f)
349
args = p.parse_args()
350
if not hasattr(args, 'func'):
353
# XXX on Python 3.3 we get 'args has no func' rather than short help.
357
except WheelError as e:
358
sys.stderr.write(e.message + "\n")