summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Grönholm <alex.gronholm@nextday.fi>2018-07-15 17:42:25 +0300
committerAlex Grönholm <alex.gronholm@nextday.fi>2018-07-17 15:02:50 +0300
commit3328624c70b6328f10050507a63da0438d3882be (patch)
tree324a309058a0570944c971eeef5e9519c721d0ac
parente90cabb29e8e7ed89c6fa4ebfca5183433e134b6 (diff)
downloadwheel-git-3328624c70b6328f10050507a63da0438d3882be.tar.gz
Added the "wheel pack" command
Fixes #157. Fixes #168.
-rw-r--r--docs/news.rst1
-rw-r--r--docs/reference/index.rst1
-rw-r--r--docs/reference/wheel_pack.rst39
-rw-r--r--manpages/wheel.rst3
-rw-r--r--tests/cli/test_pack.py26
-rw-r--r--tests/testdata/test-1.0-py2.py3-none-any.whl (renamed from tests/testdata/test-1.0-py2.py3-none-win32.whl)bin5226 -> 6521 bytes
-rw-r--r--wheel/cli/__init__.py11
-rw-r--r--wheel/cli/pack.py54
-rw-r--r--wheel/wheelfile.py22
9 files changed, 157 insertions, 0 deletions
diff --git a/docs/news.rst b/docs/news.rst
index 27a5624..8fa66cd 100644
--- a/docs/news.rst
+++ b/docs/news.rst
@@ -5,6 +5,7 @@ Release Notes
- Removed wheel signing and verifying features
- Removed the "wheel install" and "wheel installscripts" commands
+- Added the ``wheel pack`` command
**0.31.1**
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index a688c63..1921323 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -6,3 +6,4 @@ Reference Guide
wheel_convert
wheel_unpack
+ wheel_pack
diff --git a/docs/reference/wheel_pack.rst b/docs/reference/wheel_pack.rst
new file mode 100644
index 0000000..7854f97
--- /dev/null
+++ b/docs/reference/wheel_pack.rst
@@ -0,0 +1,39 @@
+wheel repack
+============
+
+Usage
+-----
+
+::
+
+ wheel repack <wheel_directory>
+
+
+Description
+-----------
+
+Repack a previously unpacked wheel file.
+
+This command can be used to repack a wheel file after its contents have been modified.
+This is the equivalent of ``zip -r <wheel_file> <wheel_directory>`` except that it regenerates the
+``RECORD`` file which contains hashes of all included files.
+
+
+Options
+-------
+
+.. option:: -d, --dest-dir <dir>
+
+ Directory to put the new wheel file into.
+
+
+Examples
+--------
+
+* Unpack a wheel, add a dummy module and then repack it::
+
+ $ wheel unpack someproject-1.5.0-py2-py3-none.whl
+ Unpacking to: ./someproject-1.5.0
+ $ touch someproject-1.5.0/somepackage/module.py
+ $ wheel repack someproject-1.5.0
+ Repacking wheel as ./someproject-1.5.0-py2-py3-none.whl...OK
diff --git a/manpages/wheel.rst b/manpages/wheel.rst
index 3e950a2..a2e4873 100644
--- a/manpages/wheel.rst
+++ b/manpages/wheel.rst
@@ -20,6 +20,9 @@ Commands
``unpack``
Unpack wheel
+ ``pack``
+ Repack a previously unpacked wheel
+
``convert``
Convert egg or wininst to wheel
diff --git a/tests/cli/test_pack.py b/tests/cli/test_pack.py
new file mode 100644
index 0000000..5320113
--- /dev/null
+++ b/tests/cli/test_pack.py
@@ -0,0 +1,26 @@
+import os
+from zipfile import ZipFile
+
+from wheel.cli.pack import pack
+
+THISDIR = os.path.dirname(__file__)
+TESTWHEEL_NAME = 'test-1.0-py2.py3-none-any.whl'
+TESTWHEEL_PATH = os.path.join(THISDIR, '..', 'testdata', TESTWHEEL_NAME)
+
+
+def test_pack(tmpdir_factory, tmpdir):
+ unpack_dir = str(tmpdir_factory.mktemp('wheeldir'))
+ with ZipFile(TESTWHEEL_PATH) as zf:
+ old_record = zf.read('test-1.0.dist-info/RECORD')
+ old_record_lines = sorted(line.rstrip() for line in old_record.split(b'\n') if line)
+ zf.extractall(unpack_dir)
+
+ pack(unpack_dir, str(tmpdir))
+ new_wheel_path = tmpdir.join(TESTWHEEL_NAME)
+ assert new_wheel_path.isfile()
+
+ with ZipFile(str(new_wheel_path)) as zf:
+ new_record = zf.read('test-1.0.dist-info/RECORD')
+ new_record_lines = sorted(line.rstrip() for line in new_record.split(b'\n') if line)
+
+ assert new_record_lines == old_record_lines
diff --git a/tests/testdata/test-1.0-py2.py3-none-win32.whl b/tests/testdata/test-1.0-py2.py3-none-any.whl
index dfd3070..cf1436c 100644
--- a/tests/testdata/test-1.0-py2.py3-none-win32.whl
+++ b/tests/testdata/test-1.0-py2.py3-none-any.whl
Binary files differ
diff --git a/wheel/cli/__init__.py b/wheel/cli/__init__.py
index 29a4710..635f40f 100644
--- a/wheel/cli/__init__.py
+++ b/wheel/cli/__init__.py
@@ -25,6 +25,11 @@ def unpack_f(args):
unpack(args.wheelfile, args.dest)
+def pack_f(args):
+ from .pack import pack
+ pack(args.directory, args.dest_dir)
+
+
def convert_f(args):
from .convert import convert
convert(args.files, args.dest_dir, args.verbose)
@@ -45,6 +50,12 @@ def parser():
unpack_parser.add_argument('wheelfile', help='Wheel file')
unpack_parser.set_defaults(func=unpack_f)
+ repack_parser = s.add_parser('pack', help='Repack wheel')
+ repack_parser.add_argument('directory', help='Root directory of the unpacked wheel')
+ repack_parser.add_argument('--dest-dir', '-d', default=os.path.curdir,
+ help="Directory to store the wheel (default %(default)s)")
+ repack_parser.set_defaults(func=pack_f)
+
convert_parser = s.add_parser('convert', help='Convert egg or wininst to wheel')
convert_parser.add_argument('files', nargs='*', help='Files to convert')
convert_parser.add_argument('--dest-dir', '-d', default=os.path.curdir,
diff --git a/wheel/cli/pack.py b/wheel/cli/pack.py
new file mode 100644
index 0000000..45c70f2
--- /dev/null
+++ b/wheel/cli/pack.py
@@ -0,0 +1,54 @@
+from __future__ import print_function
+
+import os.path
+import re
+import sys
+
+from wheel.cli import WheelError
+from wheel.wheelfile import WheelFile
+
+DIST_INFO_RE = re.compile(r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))\.dist-info$")
+
+
+def pack(directory, dest_dir):
+ """Repack a previously unpacked wheel directory into a new wheel file.
+
+ The .dist-info/WHEEL file must contain one or more tags so that the target
+ wheel file name can be determined.
+
+ :param directory: The unpacked wheel directory
+ :param dest_dir: Destination directory (defaults to the current directory)
+ """
+ # Find the .dist-info directory
+ dist_info_dirs = [fn for fn in os.listdir(directory)
+ if os.path.isdir(os.path.join(directory, fn)) and DIST_INFO_RE.match(fn)]
+ if len(dist_info_dirs) > 1:
+ raise WheelError('Multiple .dist-info directories found in {}'.format(directory))
+ elif not dist_info_dirs:
+ raise WheelError('No .dist-info directories found in {}'.format(directory))
+
+ # Determine the target wheel filename
+ dist_info_dir = dist_info_dirs[0]
+ name_version = DIST_INFO_RE.match(dist_info_dir).group('namever')
+
+ # Read the tags from .dist-info/WHEEL
+ with open(os.path.join(directory, dist_info_dir, 'WHEEL')) as f:
+ tags = [line.split(' ')[1].rstrip() for line in f if line.startswith('Tag: ')]
+ if not tags:
+ raise WheelError('No tags present in {}/WHEEL; cannot determine target wheel filename'
+ .format(dist_info_dir))
+
+ # Reassemble the tags for the wheel file
+ impls = sorted({tag.split('-')[0] for tag in tags})
+ abivers = sorted({tag.split('-')[1] for tag in tags})
+ platforms = sorted({tag.split('-')[2] for tag in tags})
+ tagline = '-'.join(['.'.join(impls), '.'.join(abivers), '.'.join(platforms)])
+
+ # Repack the wheel
+ wheel_path = os.path.join(dest_dir, '{}-{}.whl'.format(name_version, tagline))
+ with WheelFile(wheel_path, 'w') as wf:
+ print("Repacking wheel as {}...".format(wheel_path), end='')
+ sys.stdout.flush()
+ wf.write_files(directory)
+
+ print('OK')
diff --git a/wheel/wheelfile.py b/wheel/wheelfile.py
index c37dc60..4633e4f 100644
--- a/wheel/wheelfile.py
+++ b/wheel/wheelfile.py
@@ -100,6 +100,28 @@ class WheelFile(ZipFile):
return ef
+ def write_files(self, base_dir):
+ logger.info("creating '%s' and adding '%s' to it", self.filename, base_dir)
+ deferred = []
+ for root, dirnames, filenames in os.walk(base_dir):
+ # Sort the directory names so that `os.walk` will walk them in a
+ # defined order on the next iteration.
+ dirnames.sort()
+ for name in sorted(filenames):
+ path = os.path.normpath(os.path.join(root, name))
+ if os.path.isfile(path):
+ arcname = os.path.relpath(path, base_dir)
+ if arcname == self.record_path:
+ pass
+ elif root.endswith('.dist-info'):
+ deferred.append((path, arcname))
+ else:
+ self.write(path, arcname)
+
+ deferred.sort()
+ for path, arcname in deferred:
+ self.write(path, arcname)
+
def write(self, filename, arcname=None, compress_type=None):
with open(filename, 'rb') as f:
st = os.fstat(f.fileno())