def build_osx(): """Build OS X .dmg/.app.""" utils.print_title("Updating 3rdparty content") update_3rdparty.update_pdfjs() utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller', '-r') utils.print_title("Building .dmg") subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: os.remove(f) for d in ['dist', 'build']: shutil.rmtree(d) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) os.rename('qutebrowser.dmg', dmg_name) utils.print_title("Running smoke test") with tempfile.TemporaryDirectory() as tmpdir: subprocess.check_call(['hdiutil', 'attach', dmg_name, '-mountpoint', tmpdir]) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary) finally: subprocess.check_call(['hdiutil', 'detach', tmpdir]) return [(dmg_name, 'application/x-apple-diskimage', 'OS X .dmg')]
def main(): """Re-compile the given (or all) requirement files.""" names = sys.argv[1:] if len(sys.argv) > 1 else sorted(get_all_names()) for name in names: utils.print_title(name) filename = os.path.join(REQ_DIR, 'requirements-{}.txt-raw'.format(name)) if name == 'qutebrowser': outfile = os.path.join(REPO_DIR, 'requirements.txt') else: outfile = os.path.join(REQ_DIR, 'requirements-{}.txt'.format(name)) with tempfile.TemporaryDirectory() as tmpdir: pip_bin = os.path.join(tmpdir, 'bin', 'pip') subprocess.run(['virtualenv', tmpdir], check=True) subprocess.run([pip_bin, 'install', '-r', filename], check=True) reqs = subprocess.run([pip_bin, 'freeze'], check=True, stdout=subprocess.PIPE ).stdout.decode('utf-8') with open(filename, 'r', encoding='utf-8') as f: comments = read_comments(f) with open(outfile, 'w', encoding='utf-8') as f: f.write("# This file is automatically generated by " "scripts/dev/recompile_requirements.py\n\n") for line in reqs.splitlines(): f.write(convert_line(line, comments) + '\n')
def main_check(): """Check coverage after a test run.""" try: with open('coverage.xml', encoding='utf-8') as f: messages = check(f, PERFECT_FILES) except Skipped as e: print(e) messages = [] if messages: print() print() utils.print_title("Coverage check failed") for msg in messages: print(msg.text) print() filters = ','.join(msg.filename for msg in messages) subprocess.check_call([sys.executable, '-m', 'coverage', 'report', '--show-missing', '--include', filters]) print() print("To debug this, run 'tox -e py35-cov' (or py34-cov) locally and " "check htmlcov/index.html") print("or check https://codecov.io/github/The-Compiler/qutebrowser") print() if 'CI' in os.environ: print("Keeping coverage.xml on CI.") else: os.remove('coverage.xml') return 1 if messages else 0
def main_check(): """Check coverage after a test run.""" try: with open('coverage.xml', encoding='utf-8') as f: messages = check(f, PERFECT_FILES) except Skipped as e: print(e) messages = [] if messages: print() print() utils.print_title("Coverage check failed") for msg in messages: print(msg.text) print() print("You can run 'tox -e py35-cov' (or py34-cov) locally and check " "htmlcov/index.html to debug this.") print() if 'CI' in os.environ: print("Keeping coverage.xml on CI.") else: os.remove('coverage.xml') return 1 if messages else 0
def github_upload(artifacts, tag): """Upload the given artifacts to GitHub. Args: artifacts: A list of (filename, mimetype, description) tuples tag: The name of the release tag """ import github3 utils.print_title("Uploading to github...") token = read_github_token() gh = github3.login(token=token) repo = gh.repository('qutebrowser', 'qutebrowser') release = None # to satisfy pylint for release in repo.releases(): if release.tag_name == tag: break else: raise Exception("No release found for {!r}!".format(tag)) for filename, mimetype, description in artifacts: with open(filename, 'rb') as f: basename = os.path.basename(filename) asset = release.upload_asset(mimetype, basename, f) asset.edit(basename, description)
def link_pyqt(): """Symlink the systemwide PyQt/sip into the virtualenv.""" if os.name == 'nt': return utils.print_title("Softlinking PyQt5") sys_path = distutils.sysconfig.get_python_lib() venv_path = venv_python( '-c', 'from distutils.sysconfig import get_python_lib\n' 'print(get_python_lib())', output=True).rstrip() globbed_sip = (glob.glob(os.path.join(sys_path, 'sip*.so')) + glob.glob(os.path.join(sys_path, 'sip*.pyd'))) if not globbed_sip: print("Did not find sip in {}!".format(sys_path), file=sys.stderr) sys.exit(1) files = [ 'PyQt5', ] files += [os.path.basename(e) for e in globbed_sip] for fn in files: source = os.path.join(sys_path, fn) link_name = os.path.join(venv_path, fn) if not os.path.exists(source): raise FileNotFoundError(source) print('{} -> {}'.format(source, link_name)) os.symlink(source, link_name)
def build_sdist(): """Build an sdist and list the contents.""" utils.print_title("Building sdist") _maybe_remove('dist') subprocess.check_call([sys.executable, 'setup.py', 'sdist']) dist_files = os.listdir(os.path.abspath('dist')) assert len(dist_files) == 1 dist_file = os.path.join('dist', dist_files[0]) subprocess.check_call(['gpg', '--detach-sign', '-a', dist_file]) tar = tarfile.open(dist_file) by_ext = collections.defaultdict(list) for tarinfo in tar.getmembers(): if not tarinfo.isfile(): continue name = os.sep.join(tarinfo.name.split(os.sep)[1:]) _base, ext = os.path.splitext(name) by_ext[ext].append(name) assert '.pyc' not in by_ext utils.print_title("sdist contents") for ext, files in sorted(by_ext.items()): utils.print_subtitle(ext) print('\n'.join(files))
def run_asciidoc2html(args): """Common buildsteps used for all OS'.""" utils.print_title("Running asciidoc2html.py") if args.asciidoc is not None: a2h_args = ['--asciidoc'] + args.asciidoc else: a2h_args = [] call_script('asciidoc2html.py', *a2h_args)
def create_venv(): """Create a new virtualenv.""" utils.print_title("Creating virtualenv") if os.name == 'nt': sys_site = ['--system-site-packages'] else: sys_site = [] subprocess.check_call(['virtualenv'] + sys_site + ['-p', sys.executable, g_path])
def test_toolchain(): """Test if imports work properly.""" utils.print_title("Checking toolchain") packages = ['sip', 'PyQt5.QtCore', 'PyQt5.QtWebKit', 'qutebrowser.app'] if g_args.dev: packages += get_dev_packages(short=True) for pkg in packages: if pkg == 'beautifulsoup4': pkg = 'bs4' print("Importing {}".format(pkg)) venv_python('-c', 'import {}'.format(pkg))
def dump_infos(parsed): """Dump all possible infos for the given crash.""" if not parsed.present: info = get_info(parsed.pid) print("{}: Signal {} with no coredump: {}".format( parsed.time, info.get('Signal', None), info.get('Command Line', None))) else: print('\n\n\n') utils.print_title('{} - {}'.format(parsed.time, parsed.pid)) sys.stdout.flush() dump_infos_gdb(parsed)
def main(): """Main entry point.""" utils.change_cwd() read_files = config.read('.run_checks') if not read_files: raise OSError("Could not read config!") exit_status = collections.OrderedDict() exit_status_bool = {} args = _parse_args() checkers = _get_checkers() groups = ['global'] groups += config.get('DEFAULT', 'targets').split(',') groups.append('setup') for group in groups: print() utils.print_title(group) for name, func in checkers[group].items(): if _checker_enabled(args, group, name): utils.print_subtitle(name) status = func() key = '{}_{}'.format(group, name) exit_status[key] = status if name == 'flake8': # pyflakes uses True for errors and False for ok. exit_status_bool[key] = not status elif isinstance(status, bool): exit_status_bool[key] = status else: # sys.exit(0) means no problems -> True, anything != 0 # means problems. exit_status_bool[key] = (status == 0) elif not args.quiet: utils.print_subtitle(name) utils.print_col("Checker disabled.", 'blue') print() utils.print_col("Exit status values:", 'yellow') for (k, v) in exit_status.items(): ok = exit_status_bool[k] color = 'green' if ok else 'red' utils.print_col( ' {} - {} ({})'.format(k, 'ok' if ok else 'FAIL', v), color) if all(exit_status_bool.values()): return 0 else: return 1
def main(): parser = argparse.ArgumentParser() parser.add_argument('--asciidoc', help="Full path to python and " "asciidoc.py. If not given, it's searched in PATH.", nargs=2, required=False, metavar=('PYTHON', 'ASCIIDOC')) parser.add_argument('--upload', help="Tag to upload the release for", nargs=1, required=False, metavar='TAG') args = parser.parse_args() utils.change_cwd() upload_to_pypi = False if args.upload is not None: # Fail early when trying to upload without github3 installed # or without API token import github3 # pylint: disable=unused-variable read_github_token() run_asciidoc2html(args) if os.name == 'nt': if sys.maxsize > 2**32: # WORKAROUND print("Due to a python/Windows bug, this script needs to be run ") print("with a 32bit Python.") print() print("See http://bugs.python.org/issue24493 and ") print("https://github.com/pypa/virtualenv/issues/774") sys.exit(1) artifacts = build_windows() elif sys.platform == 'darwin': artifacts = build_mac() else: test_makefile() artifacts = build_sdist() upload_to_pypi = True if args.upload is not None: utils.print_title("Press enter to release...") input() github_upload(artifacts, args.upload[0]) if upload_to_pypi: pypi_upload(artifacts) else: print() utils.print_title("Artifacts") for artifact in artifacts: print(artifact)
def main(): parser = argparse.ArgumentParser() parser.add_argument('--no-asciidoc', action='store_true', help="Don't generate docs") parser.add_argument('--asciidoc', help="Full path to python and " "asciidoc.py. If not given, it's searched in PATH.", nargs=2, required=False, metavar=('PYTHON', 'ASCIIDOC')) parser.add_argument('--upload', help="Tag to upload the release for", nargs=1, required=False, metavar='TAG') args = parser.parse_args() utils.change_cwd() upload_to_pypi = False if args.upload is not None: # Fail early when trying to upload without github3 installed # or without API token import github3 # pylint: disable=unused-variable read_github_token() if args.no_asciidoc: os.makedirs(os.path.join('qutebrowser', 'html', 'doc'), exist_ok=True) else: run_asciidoc2html(args) if os.name == 'nt': artifacts = build_windows() elif sys.platform == 'darwin': artifacts = build_mac() else: test_makefile() artifacts = build_sdist() upload_to_pypi = True if args.upload is not None: utils.print_title("Press enter to release...") input() github_upload(artifacts, args.upload[0]) if upload_to_pypi: pypi_upload(artifacts) else: print() utils.print_title("Artifacts") for artifact in artifacts: print(artifact)
def main(): """Main entry point.""" global g_path, g_args g_args = parse_args() if not g_args.path: print("Refusing to run with empty path!", file=sys.stderr) sys.exit(1) g_path = os.path.abspath(g_args.path) check_exists() create_venv() utils.print_title("Calling setup.py") venv_python('setup.py', 'develop') if g_args.dev: utils.print_title("Installing developer packages") install_dev_packages() link_pyqt() test_toolchain()
def build_osx(): """Build OS X .dmg/.app.""" utils.print_title("Updating 3rdparty content") update_3rdparty.update_pdfjs() utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller') utils.print_title("Building .dmg") subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: os.remove(f) for d in ['dist', 'build']: shutil.rmtree(d)
def build_sdist(): """Build an sdist and list the contents.""" utils.print_title("Building sdist") _maybe_remove('dist') subprocess.run([sys.executable, 'setup.py', 'sdist'], check=True) dist_files = os.listdir(os.path.abspath('dist')) assert len(dist_files) == 1 dist_file = os.path.join('dist', dist_files[0]) subprocess.run(['gpg', '--detach-sign', '-a', dist_file], check=True) tar = tarfile.open(dist_file) by_ext = collections.defaultdict(list) for tarinfo in tar.getmembers(): if not tarinfo.isfile(): continue name = os.sep.join(tarinfo.name.split(os.sep)[1:]) _base, ext = os.path.splitext(name) by_ext[ext].append(name) assert '.pyc' not in by_ext utils.print_title("sdist contents") for ext, files in sorted(by_ext.items()): utils.print_subtitle(ext) print('\n'.join(files)) filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__) artifacts = [ (os.path.join('dist', filename), 'application/gzip', 'Source release'), (os.path.join('dist', filename + '.asc'), 'application/pgp-signature', 'Source release - PGP signature'), ] return artifacts
def main(): """Re-compile the given (or all) requirement files.""" names = sys.argv[1:] if len(sys.argv) > 1 else sorted(get_all_names()) utils.print_title('pip') pip_requirements = get_requirements(None) for name in names: utils.print_title(name) if name == 'qutebrowser': outfile = os.path.join(REPO_DIR, 'requirements.txt') else: outfile = os.path.join(REQ_DIR, 'requirements-{}.txt'.format(name)) if name == 'pip': requirements = [req for req in pip_requirements if not req.startswith('pip==')] comments = { 'filter': {}, 'comment': {}, 'ignore': [], 'replace': {}, } else: filename = os.path.join(REQ_DIR, 'requirements-{}.txt-raw'.format(name)) requirements = get_requirements(filename, exclude=pip_requirements) with open(filename, 'r', encoding='utf-8') as f: comments = read_comments(f) with open(outfile, 'w', encoding='utf-8') as f: f.write("# This file is automatically generated by " "scripts/dev/recompile_requirements.py\n\n") for line in requirements: converted = convert_line(line, comments) f.write(converted + '\n')
def apply_xcb_util_workaround( venv_dir: pathlib.Path, pyqt_type: str, pyqt_version: str, ) -> None: """If needed (Debian Stable), symlink libxcb-util.so.0 -> .1. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-88688 """ utils.print_title("Running xcb-util workaround") if not sys.platform.startswith('linux'): print("Workaround not needed: Not on Linux.") return if pyqt_type != 'binary': print("Workaround not needed: Not installing from PyQt binaries.") return if pyqt_version not in ['auto', '5.15']: print("Workaround not needed: Not installing Qt 5.15.") return try: libs = _find_libs() except subprocess.CalledProcessError as e: utils.print_error( f'Workaround failed: ldconfig exited with status {e.returncode}') return abi_type = 'libc6,x86-64' # the only one PyQt wheels are available for if ('libxcb-util.so.1', abi_type) in libs: print("Workaround not needed: libxcb-util.so.1 found.") return try: libxcb_util_libs = libs['libxcb-util.so.0', abi_type] except KeyError: utils.print_error('Workaround failed: libxcb-util.so.0 not found.') return if len(libxcb_util_libs) > 1: utils.print_error( f'Workaround failed: Multiple matching libxcb-util found: ' f'{libxcb_util_libs}') return libxcb_util_path = pathlib.Path(libxcb_util_libs[0]) code = [ 'from PyQt5.QtCore import QLibraryInfo', 'print(QLibraryInfo.location(QLibraryInfo.LibrariesPath))', ] proc = run_venv(venv_dir, 'python', '-c', '; '.join(code), capture_output=True) venv_lib_path = pathlib.Path(proc.stdout.strip()) link_path = venv_lib_path / libxcb_util_path.with_suffix('.1').name # This gives us a nicer path to print, and also conveniently makes sure we # didn't accidentally end up with a path outside the venv. rel_link_path = venv_dir / link_path.relative_to(venv_dir.resolve()) print_command('ln -s', libxcb_util_path, rel_link_path, venv=False) link_path.symlink_to(libxcb_util_path)
def build_windows(): """Build windows executables/setups.""" parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) dotver = '.'.join(parts) python_x86 = r'C:\Python{}_x32\python.exe'.format(ver) python_x64 = r'C:\Python{}\python.exe'.format(ver) utils.print_title("Running 32bit freeze.py build_exe") call_script('freeze.py', 'build_exe', python=python_x86) utils.print_title("Running 64bit freeze.py build_exe") call_script('freeze.py', 'build_exe', python=python_x64) utils.print_title("Running 32bit freeze.py bdist_msi") call_script('freeze.py', 'bdist_msi', python=python_x86) utils.print_title("Running 64bit freeze.py bdist_msi") call_script('freeze.py', 'bdist_msi', python=python_x64) destdir = os.path.join('dist', 'zip') _maybe_remove(destdir) os.makedirs(destdir) basedirname = 'qutebrowser-{}'.format(qutebrowser.__version__) builddir = os.path.join('build', basedirname) _maybe_remove(builddir) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win32-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), 'zip', 'build', basedirname) shutil.rmtree(builddir) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win-amd64-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), 'zip', 'build', basedirname) shutil.rmtree(builddir) utils.print_title("Creating final zip...") shutil.move( os.path.join( 'dist', 'qutebrowser-{}-amd64.msi'.format(qutebrowser.__version__)), os.path.join('dist', 'zip')) shutil.move( os.path.join( 'dist', 'qutebrowser-{}-win32.msi'.format(qutebrowser.__version__)), os.path.join('dist', 'zip')) shutil.make_archive( 'qutebrowser-{}-windows'.format(qutebrowser.__version__), 'zip', destdir)
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") update_3rdparty.update_pdfjs() utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) dotver = '.'.join(parts) python_x86 = r'C:\Python{}_x32'.format(ver) python_x64 = r'C:\Python{}'.format(ver) artifacts = [] utils.print_title("Rebuilding tox environment") call_tox('cxfreeze-windows', '-r', '--notest') utils.print_title("Running 32bit freeze.py build_exe") call_tox('cxfreeze-windows', 'build_exe', python=python_x86) utils.print_title("Running 32bit freeze.py bdist_msi") call_tox('cxfreeze-windows', 'bdist_msi', python=python_x86) utils.print_title("Running 64bit freeze.py build_exe") call_tox('cxfreeze-windows', 'build_exe', python=python_x64) utils.print_title("Running 64bit freeze.py bdist_msi") call_tox('cxfreeze-windows', 'bdist_msi', python=python_x64) name_32 = 'qutebrowser-{}-win32.msi'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.msi'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_32), 'application/x-msi', 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/x-msi', 'Windows 64bit installer'), ] utils.print_title("Running 32bit smoke test") smoke_test('build/exe.win32-{}/qutebrowser.exe'.format(dotver)) utils.print_title("Running 64bit smoke test") smoke_test('build/exe.win-amd64-{}/qutebrowser.exe'.format(dotver)) basedirname = 'qutebrowser-{}'.format(qutebrowser.__version__) builddir = os.path.join('build', basedirname) _maybe_remove(builddir) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win32-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(name, 'zip', 'build', basedirname) shutil.rmtree(builddir) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 32bit standalone')) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win-amd64-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(name, 'zip', 'build', basedirname) shutil.rmtree(builddir) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def test_makefile(): """Make sure the Makefile works correctly.""" utils.print_title("Testing makefile") with tempfile.TemporaryDirectory() as tmpdir: subprocess.run(['make', '-f', 'misc/Makefile', 'DESTDIR={}'.format(tmpdir), 'install'], check=True)
def pypi_upload(artifacts): """Upload the given artifacts to PyPI using twine.""" utils.print_title("Uploading to PyPI...") run_twine('upload', artifacts)
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) dot_ver = '.'.join(parts) # Get python path from registry if possible try: reg64_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore' r'\{}\InstallPath'.format(dot_ver)) python_x64 = winreg.QueryValueEx(reg64_key, 'ExecutablePath')[0] except FileNotFoundError: python_x64 = r'C:\Python{}\python.exe'.format(ver) out_pyinstaller = os.path.join('dist', 'qutebrowser') out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] from scripts.dev import gen_versioninfo utils.print_title("Updating VersionInfo file") gen_versioninfo.main() utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) shutil.move(out_pyinstaller, out_64) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) utils.print_title("Building installers") subprocess.run(['makensis.exe', '/DX64', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi'], check=True) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_64)) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def upgrade_pip(venv_dir: pathlib.Path) -> None: """Upgrade pip inside a virtualenv.""" utils.print_title("Upgrading pip") pip_install(venv_dir, '-U', 'pip')
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") # Currently disabled because QtWebEngine has no pdfjs support # update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) python_x86 = r'C:\Python{}-32\python.exe'.format(ver) python_x64 = r'C:\Python{}\python.exe'.format(ver) out_pyinstaller = os.path.join('dist', 'qutebrowser') out_32 = os.path.join('dist', 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] utils.print_title("Running pyinstaller 32bit") _maybe_remove(out_32) call_tox('pyinstaller', '-r', python=python_x86) shutil.move(out_pyinstaller, out_32) patch_windows(out_32) utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) shutil.move(out_pyinstaller, out_64) patch_windows(out_64) utils.print_title("Building installers") subprocess.check_call([ 'makensis.exe', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi' ]) subprocess.check_call([ 'makensis.exe', '/DX64', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi' ]) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_32), 'application/vnd.microsoft.portable-executable', 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] utils.print_title("Running 32bit smoke test") smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) artifacts.append( ('{}.zip'.format(name), 'application/zip', 'Windows 32bit standalone')) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_64)) artifacts.append( ('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") update_3rdparty.main() utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = "".join(parts) dotver = ".".join(parts) python_x86 = r"C:\Python{}_x32".format(ver) python_x64 = r"C:\Python{}".format(ver) utils.print_title("Running 32bit freeze.py build_exe") call_freeze("build_exe", python=python_x86) utils.print_title("Running 32bit freeze.py bdist_msi") call_freeze("bdist_msi", python=python_x86) utils.print_title("Running 64bit freeze.py build_exe") call_freeze("build_exe", python=python_x64) utils.print_title("Running 64bit freeze.py bdist_msi") call_freeze("bdist_msi", python=python_x64) utils.print_title("Running 32bit smoke test") smoke_test("build/exe.win32-{}/qutebrowser.exe".format(dotver)) utils.print_title("Running 64bit smoke test") smoke_test("build/exe.win-amd64-{}/qutebrowser.exe".format(dotver)) destdir = os.path.join("dist", "zip") _maybe_remove(destdir) os.makedirs(destdir) basedirname = "qutebrowser-{}".format(qutebrowser.__version__) builddir = os.path.join("build", basedirname) _maybe_remove(builddir) utils.print_title("Zipping 32bit standalone...") name = "qutebrowser-{}-windows-standalone-win32".format(qutebrowser.__version__) origin = os.path.join("build", "exe.win32-{}".format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), "zip", "build", basedirname) shutil.rmtree(builddir) utils.print_title("Zipping 64bit standalone...") name = "qutebrowser-{}-windows-standalone-amd64".format(qutebrowser.__version__) origin = os.path.join("build", "exe.win-amd64-{}".format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), "zip", "build", basedirname) shutil.rmtree(builddir) utils.print_title("Creating final zip...") shutil.move( os.path.join("dist", "qutebrowser-{}-amd64.msi".format(qutebrowser.__version__)), os.path.join("dist", "zip") ) shutil.move( os.path.join("dist", "qutebrowser-{}-win32.msi".format(qutebrowser.__version__)), os.path.join("dist", "zip") ) shutil.make_archive("qutebrowser-{}-windows".format(qutebrowser.__version__), "zip", destdir)
def install_qutebrowser(venv_dir: pathlib.Path) -> None: """Install qutebrowser itself as an editable install.""" utils.print_title("Installing qutebrowser") pip_install(venv_dir, '-e', str(REPO_ROOT))
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") update_3rdparty.run(nsis=True, ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building Windows binaries") python_x64 = _get_windows_python_path(x64=True) python_x86 = _get_windows_python_path(x64=False) out_pyinstaller = os.path.join('dist', 'qutebrowser') out_32 = os.path.join('dist', 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] from scripts.dev import gen_versioninfo utils.print_title("Updating VersionInfo file") gen_versioninfo.main() utils.print_title("Running pyinstaller 32bit") _maybe_remove(out_32) call_tox('pyinstaller', '-r', python=python_x86) shutil.move(out_pyinstaller, out_32) patch_windows(out_32, x64=False) utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) shutil.move(out_pyinstaller, out_64) patch_windows(out_64, x64=True) utils.print_title("Running 32bit smoke test") smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) utils.print_title("Building installers") subprocess.run(['makensis.exe', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/nsis/qutebrowser.nsi'], check=True) subprocess.run(['makensis.exe', '/DX86', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/nsis/qutebrowser.nsi'], check=True) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_32), 'application/vnd.microsoft.portable-executable', 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 32bit standalone')) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_64)) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def build_mac(): """Build macOS .dmg/.app.""" utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: try: os.remove(f) except FileNotFoundError: pass for d in ['dist', 'build']: shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller', '-r') utils.print_title("Patching .app") patch_mac_app() utils.print_title("Building .dmg") subprocess.run(['make', '-f', 'scripts/dev/Makefile-dmg'], check=True) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) os.rename('qutebrowser.dmg', dmg_name) utils.print_title("Running smoke test") try: with tempfile.TemporaryDirectory() as tmpdir: subprocess.run(['hdiutil', 'attach', dmg_name, '-mountpoint', tmpdir], check=True) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary) finally: time.sleep(5) subprocess.run(['hdiutil', 'detach', tmpdir], check=False) except PermissionError as e: print("Failed to remove tempdir: {}".format(e)) return [(dmg_name, 'application/x-apple-diskimage', 'macOS .dmg')]
def pypi_upload(artifacts): """Upload the given artifacts to PyPI using twine.""" utils.print_title("Uploading to PyPI...") filenames = [a[0] for a in artifacts] subprocess.run([sys.executable, '-m', 'twine', 'upload'] + filenames, check=True)
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") # Currently disabled because QtWebEngine has no pdfjs support # update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) dot_ver = '.'.join(parts) # Get python path from registry if possible try: reg64_key = winreg.OpenKeyEx( winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore' r'\{}\InstallPath'.format(dot_ver)) python_x64 = winreg.QueryValueEx(reg64_key, 'ExecutablePath')[0] except FileNotFoundError: python_x64 = r'C:\Python{}\python.exe'.format(ver) try: reg32_key = winreg.OpenKeyEx( winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\WOW6432Node\Python\PythonCore' r'\{}-32\InstallPath'.format(dot_ver)) python_x86 = winreg.QueryValueEx(reg32_key, 'ExecutablePath')[0] except FileNotFoundError: python_x86 = r'C:\Python{}-32\python.exe'.format(ver) out_pyinstaller = os.path.join('dist', 'qutebrowser') out_32 = os.path.join('dist', 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] utils.print_title("Updating VersionInfo file") gen_versioninfo.main() utils.print_title("Running pyinstaller 32bit") _maybe_remove(out_32) call_tox('pyinstaller', '-r', python=python_x86) shutil.move(out_pyinstaller, out_32) patch_windows(out_32) utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) shutil.move(out_pyinstaller, out_64) patch_windows(out_64) utils.print_title("Building installers") subprocess.run([ 'makensis.exe', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi' ], check=True) subprocess.run([ 'makensis.exe', '/DX64', '/DVERSION={}'.format( qutebrowser.__version__), 'misc/qutebrowser.nsi' ], check=True) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_32), 'application/vnd.microsoft.portable-executable', 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] utils.print_title("Running 32bit smoke test") smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) artifacts.append( ('{}.zip'.format(name), 'application/zip', 'Windows 32bit standalone')) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_64)) artifacts.append( ('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def main(): parser = argparse.ArgumentParser() parser.add_argument('--skip-docs', action='store_true', help="Don't generate docs") parser.add_argument('--asciidoc', help="Full path to asciidoc.py. " "If not given, it's searched in PATH.", nargs='?') parser.add_argument('--asciidoc-python', help="Python to use for asciidoc." "If not given, the current Python interpreter is used.", nargs='?') parser.add_argument('--gh-token', help="GitHub token to use.", nargs='?') parser.add_argument('--upload', action='store_true', required=False, help="Toggle to upload the release to GitHub.") parser.add_argument('--no-confirm', action='store_true', required=False, help="Skip confirmation before uploading.") parser.add_argument('--skip-packaging', action='store_true', required=False, help="Skip Windows installer/zip generation.") parser.add_argument('--32bit', action='store_true', required=False, help="Skip Windows 64 bit build.", dest='only_32bit') parser.add_argument('--64bit', action='store_true', required=False, help="Skip Windows 32 bit build.", dest='only_64bit') parser.add_argument('--debug', action='store_true', required=False, help="Build a debug build.") args = parser.parse_args() utils.change_cwd() upload_to_pypi = False if args.upload: # Fail early when trying to upload without github3 installed # or without API token import github3 # pylint: disable=unused-import gh_token = read_github_token(args.gh_token) else: gh_token = read_github_token(args.gh_token, optional=True) if not misc_checks.check_git(): utils.print_error("Refusing to do a release with a dirty git tree") sys.exit(1) if args.skip_docs: os.makedirs(os.path.join('qutebrowser', 'html', 'doc'), exist_ok=True) else: run_asciidoc2html(args) if os.name == 'nt': artifacts = build_windows( gh_token=gh_token, skip_packaging=args.skip_packaging, only_32bit=args.only_32bit, only_64bit=args.only_64bit, debug=args.debug, ) elif sys.platform == 'darwin': artifacts = build_mac(gh_token=gh_token, debug=args.debug) else: upgrade_sdist_dependencies() test_makefile() artifacts = build_sdist() upload_to_pypi = True if args.upload: version_tag = "v" + qutebrowser.__version__ if not args.no_confirm: utils.print_title("Press enter to release {}...".format(version_tag)) input() github_upload(artifacts, version_tag, gh_token=gh_token) if upload_to_pypi: pypi_upload(artifacts) else: print() utils.print_title("Artifacts") for artifact in artifacts: print(artifact)
def build_windows(): """Build windows executables/setups.""" parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) dotver = '.'.join(parts) python_x86 = r'C:\Python{}_x32'.format(ver) python_x64 = r'C:\Python{}'.format(ver) utils.print_title("Running 32bit freeze.py build_exe") call_freeze('build_exe', python=python_x86) utils.print_title("Running 32bit freeze.py bdist_msi") call_freeze('bdist_msi', python=python_x86) utils.print_title("Running 64bit freeze.py build_exe") call_freeze('build_exe', python=python_x64) utils.print_title("Running 64bit freeze.py bdist_msi") call_freeze('bdist_msi', python=python_x64) utils.print_title("Running 32bit smoke test") smoke_test('build/exe.win32-{}/qutebrowser.exe'.format(dotver)) utils.print_title("Running 64bit smoke test") smoke_test('build/exe.win-amd64-{}/qutebrowser.exe'.format(dotver)) destdir = os.path.join('dist', 'zip') _maybe_remove(destdir) os.makedirs(destdir) basedirname = 'qutebrowser-{}'.format(qutebrowser.__version__) builddir = os.path.join('build', basedirname) _maybe_remove(builddir) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win32-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), 'zip', 'build', basedirname) shutil.rmtree(builddir) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) origin = os.path.join('build', 'exe.win-amd64-{}'.format(dotver)) os.rename(origin, builddir) shutil.make_archive(os.path.join(destdir, name), 'zip', 'build', basedirname) shutil.rmtree(builddir) utils.print_title("Creating final zip...") shutil.move(os.path.join('dist', 'qutebrowser-{}-amd64.msi'.format( qutebrowser.__version__)), os.path.join('dist', 'zip')) shutil.move(os.path.join('dist', 'qutebrowser-{}-win32.msi'.format( qutebrowser.__version__)), os.path.join('dist', 'zip')) shutil.make_archive('qutebrowser-{}-windows'.format( qutebrowser.__version__), 'zip', destdir)
def install_requirements(venv_dir: pathlib.Path) -> None: """Install qutebrowser's requirement.txt.""" utils.print_title("Installing other qutebrowser dependencies") requirements_file = REPO_ROOT / 'requirements.txt' pip_install(venv_dir, '-r', str(requirements_file))
def twine_check(artifacts): """Check packages using 'twine check'.""" utils.print_title("Running twine check...") run_twine('check', artifacts, '--strict')
def build_osx(): """Build OS X .dmg/.app.""" utils.print_title("Updating 3rdparty content") # update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller', '-r') utils.print_title("Patching .app") patch_osx_app() utils.print_title("Building .dmg") subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: os.remove(f) for d in ['dist', 'build']: shutil.rmtree(d) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) os.rename('qutebrowser.dmg', dmg_name) utils.print_title("Running smoke test") with tempfile.TemporaryDirectory() as tmpdir: subprocess.check_call( ['hdiutil', 'attach', dmg_name, '-mountpoint', tmpdir]) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary) finally: subprocess.check_call(['hdiutil', 'detach', tmpdir]) return [(dmg_name, 'application/x-apple-diskimage', 'OS X .dmg')]
def build_mac(): """Build macOS .dmg/.app.""" utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: try: os.remove(f) except FileNotFoundError: pass for d in ['dist', 'build']: shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller', '-r') utils.print_title("Patching .app") patch_mac_app() utils.print_title("Building .dmg") subprocess.run(['make', '-f', 'scripts/dev/Makefile-dmg'], check=True) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) os.rename('qutebrowser.dmg', dmg_name) utils.print_title("Running smoke test") try: with tempfile.TemporaryDirectory() as tmpdir: subprocess.run(['hdiutil', 'attach', dmg_name, '-mountpoint', tmpdir], check=True) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary) finally: time.sleep(5) subprocess.run(['hdiutil', 'detach', tmpdir]) except PermissionError as e: print("Failed to remove tempdir: {}".format(e)) return [(dmg_name, 'application/x-apple-diskimage', 'macOS .dmg')]
def install_pyqt_link(venv_dir: pathlib.Path) -> None: """Install PyQt by linking a system-wide install.""" utils.print_title("Linking system-wide PyQt") lib_path = link_pyqt.get_venv_lib_path(str(venv_dir)) link_pyqt.link_pyqt(sys.executable, lib_path)
def install_pyqt_source(venv_dir: pathlib.Path, version: str) -> None: """Install PyQt from the source tarball.""" utils.print_title("Installing PyQt from sources") pip_install(venv_dir, '-r', pyqt_requirements_file(version), '--verbose', '--no-binary', 'PyQt5,PyQtWebEngine')
def build_windows(): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") # Currently disabled because QtWebEngine has no pdfjs support # update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False) utils.print_title("Building Windows binaries") parts = str(sys.version_info.major), str(sys.version_info.minor) ver = ''.join(parts) python_x86 = r'C:\Python{}-32\python.exe'.format(ver) python_x64 = r'C:\Python{}\python.exe'.format(ver) out_pyinstaller = os.path.join('dist', 'qutebrowser') out_32 = os.path.join('dist', 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] utils.print_title("Running pyinstaller 32bit") _maybe_remove(out_32) call_tox('pyinstaller', '-r', python=python_x86) shutil.move(out_pyinstaller, out_32) patch_windows(out_32) utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) shutil.move(out_pyinstaller, out_64) patch_windows(out_64) utils.print_title("Building installers") subprocess.check_call(['makensis.exe', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi']) subprocess.check_call(['makensis.exe', '/DX64', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi']) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ (os.path.join('dist', name_32), 'application/vnd.microsoft.portable-executable', 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] utils.print_title("Running 32bit smoke test") smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) utils.print_title("Zipping 32bit standalone...") name = 'qutebrowser-{}-windows-standalone-win32'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 32bit standalone')) utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_64)) artifacts.append(('{}.zip'.format(name), 'application/zip', 'Windows 64bit standalone')) return artifacts
def install_pyqt_wheels(venv_dir: pathlib.Path, wheels_dir: pathlib.Path) -> None: """Install PyQt from the wheels/ directory.""" utils.print_title("Installing PyQt wheels") wheels = [str(wheel) for wheel in wheels_dir.glob('*.whl')] pip_install(venv_dir, *wheels)
def build_mac(*, gh_token, debug): """Build macOS .dmg/.app.""" utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: try: os.remove(f) except FileNotFoundError: pass for d in ['dist', 'build']: shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building .app via pyinstaller") # call_tox('pyinstaller-64', '-r', debug=debug) env = os.environ.copy() subprocess.run( "pyinstaller --noconfirm misc/qutebrowser.spec".split(" "), env=env, check=True) utils.print_title("Patching .app") patch_mac_app() utils.print_title("Building .dmg") subprocess.run(['make', '-f', 'scripts/dev/Makefile-dmg'], check=True) suffix = "-debug" if debug else "" dmg_path = f'dist/qutebrowser-{qutebrowser.__version__}{suffix}.dmg' os.rename('qutebrowser.dmg', dmg_path) utils.print_title("Running smoke test") try: with tempfile.TemporaryDirectory() as tmpdir: subprocess.run(['hdiutil', 'attach', dmg_path, '-mountpoint', tmpdir], check=True) try: binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', 'MacOS', 'qutebrowser') smoke_test(binary, debug=debug) finally: print("Waiting 10s for dmg to be detachable...") time.sleep(10) subprocess.run(['hdiutil', 'detach', tmpdir], check=False) except PermissionError as e: print("Failed to remove tempdir: {}".format(e)) return [(dmg_path, 'application/x-apple-diskimage', 'macOS .dmg')]
def install_dev_requirements(venv_dir: pathlib.Path) -> None: """Install development dependencies.""" utils.print_title("Installing dev dependencies") pip_install(venv_dir, '-r', str(requirements_file('dev')), '-r', requirements_file('tests'))