diff options
| author | Alex Grönholm <alex.gronholm@nextday.fi> | 2018-07-15 17:42:25 +0300 |
|---|---|---|
| committer | Alex Grönholm <alex.gronholm@nextday.fi> | 2018-07-17 15:02:50 +0300 |
| commit | 3328624c70b6328f10050507a63da0438d3882be (patch) | |
| tree | 324a309058a0570944c971eeef5e9519c721d0ac | |
| parent | e90cabb29e8e7ed89c6fa4ebfca5183433e134b6 (diff) | |
| download | wheel-git-3328624c70b6328f10050507a63da0438d3882be.tar.gz | |
Added the "wheel pack" command
Fixes #157. Fixes #168.
| -rw-r--r-- | docs/news.rst | 1 | ||||
| -rw-r--r-- | docs/reference/index.rst | 1 | ||||
| -rw-r--r-- | docs/reference/wheel_pack.rst | 39 | ||||
| -rw-r--r-- | manpages/wheel.rst | 3 | ||||
| -rw-r--r-- | tests/cli/test_pack.py | 26 | ||||
| -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) | bin | 5226 -> 6521 bytes | |||
| -rw-r--r-- | wheel/cli/__init__.py | 11 | ||||
| -rw-r--r-- | wheel/cli/pack.py | 54 | ||||
| -rw-r--r-- | wheel/wheelfile.py | 22 |
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 Binary files differindex 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 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()) |
