diff -r 42dd69985778 -r b83de9150c1c contrib/packaging/hgpackaging/util.py --- a/contrib/packaging/hgpackaging/util.py Wed Mar 13 10:51:40 2019 -0700 +++ b/contrib/packaging/hgpackaging/util.py Thu Mar 14 18:14:33 2019 -0700 @@ -1,157 +1,157 @@ -# util.py - Common packaging utility code. -# -# Copyright 2019 Gregory Szorc -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -# no-check-code because Python 3 native. - -import distutils.version -import getpass -import os -import pathlib -import subprocess -import tarfile -import zipfile - - -def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): - with tarfile.open(source, 'r') as tf: - tf.extractall(dest) - - -def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path): - with zipfile.ZipFile(source, 'r') as zf: - zf.extractall(dest) - - -def find_vc_runtime_files(x64=False): - """Finds Visual C++ Runtime DLLs to include in distribution.""" - winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' - - prefix = 'amd64' if x64 else 'x86' - - candidates = sorted(p for p in os.listdir(winsxs) - if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)) - - for p in candidates: - print('found candidate VC runtime: %s' % p) - - # Take the newest version. - version = candidates[-1] - - d = winsxs / version - - return [ - d / 'msvcm90.dll', - d / 'msvcp90.dll', - d / 'msvcr90.dll', - winsxs / 'Manifests' / ('%s.manifest' % version), - ] - - -def windows_10_sdk_info(): - """Resolves information about the Windows 10 SDK.""" - - base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10' - - if not base.is_dir(): - raise Exception('unable to find Windows 10 SDK at %s' % base) - - # Find the latest version. - bin_base = base / 'bin' - - versions = [v for v in os.listdir(bin_base) if v.startswith('10.')] - version = sorted(versions, reverse=True)[0] - - bin_version = bin_base / version - - return { - 'root': base, - 'version': version, - 'bin_root': bin_version, - 'bin_x86': bin_version / 'x86', - 'bin_x64': bin_version / 'x64' - } - - -def find_signtool(): - """Find signtool.exe from the Windows SDK.""" - sdk = windows_10_sdk_info() - - for key in ('bin_x64', 'bin_x86'): - p = sdk[key] / 'signtool.exe' - - if p.exists(): - return p - - raise Exception('could not find signtool.exe in Windows 10 SDK') - - -def sign_with_signtool(file_path, description, subject_name=None, - cert_path=None, cert_password=None, - timestamp_url=None): - """Digitally sign a file with signtool.exe. - - ``file_path`` is file to sign. - ``description`` is text that goes in the signature. - - The signing certificate can be specified by ``cert_path`` or - ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments - to signtool.exe, respectively. - - The certificate password can be specified via ``cert_password``. If - not provided, you will be prompted for the password. - - ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr`` - argument to signtool.exe). - """ - if cert_path and subject_name: - raise ValueError('cannot specify both cert_path and subject_name') - - while cert_path and not cert_password: - cert_password = getpass.getpass('password for %s: ' % cert_path) - - args = [ - str(find_signtool()), 'sign', - '/v', - '/fd', 'sha256', - '/d', description, - ] - - if cert_path: - args.extend(['/f', str(cert_path), '/p', cert_password]) - elif subject_name: - args.extend(['/n', subject_name]) - - if timestamp_url: - args.extend(['/tr', timestamp_url, '/td', 'sha256']) - - args.append(str(file_path)) - - print('signing %s' % file_path) - subprocess.run(args, check=True) - - -PRINT_PYTHON_INFO = ''' -import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version())) -'''.strip() - - -def python_exe_info(python_exe: pathlib.Path): - """Obtain information about a Python executable.""" - - res = subprocess.run( - [str(python_exe), '-c', PRINT_PYTHON_INFO], - capture_output=True, check=True) - - arch, version = res.stdout.decode('utf-8').split(':') - - version = distutils.version.LooseVersion(version) - - return { - 'arch': arch, - 'version': version, - 'py3': version >= distutils.version.LooseVersion('3'), - } +# util.py - Common packaging utility code. +# +# Copyright 2019 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# no-check-code because Python 3 native. + +import distutils.version +import getpass +import os +import pathlib +import subprocess +import tarfile +import zipfile + + +def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): + with tarfile.open(source, 'r') as tf: + tf.extractall(dest) + + +def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path): + with zipfile.ZipFile(source, 'r') as zf: + zf.extractall(dest) + + +def find_vc_runtime_files(x64=False): + """Finds Visual C++ Runtime DLLs to include in distribution.""" + winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' + + prefix = 'amd64' if x64 else 'x86' + + candidates = sorted(p for p in os.listdir(winsxs) + if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)) + + for p in candidates: + print('found candidate VC runtime: %s' % p) + + # Take the newest version. + version = candidates[-1] + + d = winsxs / version + + return [ + d / 'msvcm90.dll', + d / 'msvcp90.dll', + d / 'msvcr90.dll', + winsxs / 'Manifests' / ('%s.manifest' % version), + ] + + +def windows_10_sdk_info(): + """Resolves information about the Windows 10 SDK.""" + + base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10' + + if not base.is_dir(): + raise Exception('unable to find Windows 10 SDK at %s' % base) + + # Find the latest version. + bin_base = base / 'bin' + + versions = [v for v in os.listdir(bin_base) if v.startswith('10.')] + version = sorted(versions, reverse=True)[0] + + bin_version = bin_base / version + + return { + 'root': base, + 'version': version, + 'bin_root': bin_version, + 'bin_x86': bin_version / 'x86', + 'bin_x64': bin_version / 'x64' + } + + +def find_signtool(): + """Find signtool.exe from the Windows SDK.""" + sdk = windows_10_sdk_info() + + for key in ('bin_x64', 'bin_x86'): + p = sdk[key] / 'signtool.exe' + + if p.exists(): + return p + + raise Exception('could not find signtool.exe in Windows 10 SDK') + + +def sign_with_signtool(file_path, description, subject_name=None, + cert_path=None, cert_password=None, + timestamp_url=None): + """Digitally sign a file with signtool.exe. + + ``file_path`` is file to sign. + ``description`` is text that goes in the signature. + + The signing certificate can be specified by ``cert_path`` or + ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments + to signtool.exe, respectively. + + The certificate password can be specified via ``cert_password``. If + not provided, you will be prompted for the password. + + ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr`` + argument to signtool.exe). + """ + if cert_path and subject_name: + raise ValueError('cannot specify both cert_path and subject_name') + + while cert_path and not cert_password: + cert_password = getpass.getpass('password for %s: ' % cert_path) + + args = [ + str(find_signtool()), 'sign', + '/v', + '/fd', 'sha256', + '/d', description, + ] + + if cert_path: + args.extend(['/f', str(cert_path), '/p', cert_password]) + elif subject_name: + args.extend(['/n', subject_name]) + + if timestamp_url: + args.extend(['/tr', timestamp_url, '/td', 'sha256']) + + args.append(str(file_path)) + + print('signing %s' % file_path) + subprocess.run(args, check=True) + + +PRINT_PYTHON_INFO = ''' +import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version())) +'''.strip() + + +def python_exe_info(python_exe: pathlib.Path): + """Obtain information about a Python executable.""" + + res = subprocess.run( + [str(python_exe), '-c', PRINT_PYTHON_INFO], + capture_output=True, check=True) + + arch, version = res.stdout.decode('utf-8').split(':') + + version = distutils.version.LooseVersion(version) + + return { + 'arch': arch, + 'version': version, + 'py3': version >= distutils.version.LooseVersion('3'), + }