contrib/packaging/hgpackaging/util.py
author Gregory Szorc <gregory.szorc@gmail.com>
Thu, 07 Mar 2019 10:10:04 -0800
changeset 41907 9da97f49d4f4
parent 41853 contrib/packaging/packagingutil.py@d7dc4ac1ff84
child 41908 c2237fe1359e
permissions -rw-r--r--
packaging: establish hgpackaging package Previously, contrib/packaging behaved as a root to a package directory and we had a "packagingutil" module. As I work more on packaging code, we'll want to have more code shared between different packaging tools. I think it makes sense to have a single package containing multiple modules than multiple top-level modules. This commit establishes an "hgpackaging" package by moving the existing packagingutil code to it. Differential Revision: https://phab.mercurial-scm.org/D6083
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
41907
9da97f49d4f4 packaging: establish hgpackaging package
Gregory Szorc <gregory.szorc@gmail.com>
parents: 41853
diff changeset
     1
# util.py - Common packaging utility code.
41853
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     2
#
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     3
# Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     4
#
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     5
# This software may be used and distributed according to the terms of the
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     6
# GNU General Public License version 2 or any later version.
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     7
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     8
# no-check-code because Python 3 native.
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
     9
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    10
import gzip
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    11
import hashlib
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    12
import pathlib
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    13
import tarfile
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    14
import urllib.request
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    15
import zipfile
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    16
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    17
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    18
def hash_path(p: pathlib.Path):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    19
    h = hashlib.sha256()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    20
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    21
    with p.open('rb') as fh:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    22
        while True:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    23
            chunk = fh.read(65536)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    24
            if not chunk:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    25
                break
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    26
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    27
            h.update(chunk)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    28
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    29
    return h.hexdigest()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    30
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    31
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    32
class IntegrityError(Exception):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    33
    """Represents an integrity error when downloading a URL."""
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    34
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    35
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    36
def secure_download_stream(url, size, sha256):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    37
    """Securely download a URL to a stream of chunks.
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    38
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    39
    If the integrity of the download fails, an IntegrityError is
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    40
    raised.
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    41
    """
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    42
    h = hashlib.sha256()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    43
    length = 0
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    44
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    45
    with urllib.request.urlopen(url) as fh:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    46
        if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip':
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    47
            fh = gzip.GzipFile(fileobj=fh)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    48
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    49
        while True:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    50
            chunk = fh.read(65536)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    51
            if not chunk:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    52
                break
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    53
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    54
            h.update(chunk)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    55
            length += len(chunk)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    56
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    57
            yield chunk
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    58
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    59
    digest = h.hexdigest()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    60
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    61
    if length != size:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    62
        raise IntegrityError('size mismatch on %s: wanted %d; got %d' % (
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    63
            url, size, length))
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    64
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    65
    if digest != sha256:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    66
        raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % (
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    67
            url, sha256, digest))
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    68
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    69
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    70
def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    71
    """Download a URL to a filesystem path, possibly with verification."""
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    72
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    73
    # We download to a temporary file and rename at the end so there's
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    74
    # no chance of the final file being partially written or containing
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    75
    # bad data.
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    76
    print('downloading %s to %s' % (url, path))
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    77
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    78
    if path.exists():
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    79
        good = True
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    80
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    81
        if path.stat().st_size != size:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    82
            print('existing file size is wrong; removing')
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    83
            good = False
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    84
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    85
        if good:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    86
            if hash_path(path) != sha256:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    87
                print('existing file hash is wrong; removing')
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    88
                good = False
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    89
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    90
        if good:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    91
            print('%s exists and passes integrity checks' % path)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    92
            return
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    93
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    94
        path.unlink()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    95
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    96
    tmp = path.with_name('%s.tmp' % path.name)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    97
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    98
    try:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
    99
        with tmp.open('wb') as fh:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   100
            for chunk in secure_download_stream(url, size, sha256):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   101
                fh.write(chunk)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   102
    except IntegrityError:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   103
        tmp.unlink()
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   104
        raise
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   105
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   106
    tmp.rename(path)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   107
    print('successfully downloaded %s' % url)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   108
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   109
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   110
def download_entry(entry: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   111
    url = entry['url']
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   112
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   113
    local_name = local_name or url[url.rindex('/') + 1:]
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   114
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   115
    local_path = dest_path / local_name
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   116
    download_to_path(url, local_path, entry['size'], entry['sha256'])
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   117
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   118
    return local_path
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   119
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   120
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   121
def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   122
    with tarfile.open(source, 'r') as tf:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   123
        tf.extractall(dest)
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   124
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   125
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   126
def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   127
    with zipfile.ZipFile(source, 'r') as zf:
d7dc4ac1ff84 inno: script to automate building Inno installer
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
   128
        zf.extractall(dest)