Ejemplo n.º 1
0
def main():
    args = parse_args()

    if args.name is None:
        utils.print_error('Please supply parameter name.')
        exit(1)

    if (args.action == 'list'):
        params = list_params(args.name, args.region)
        if args.verbose:
            print_params_verbose(params)
        else:
            print_params_simple(params)
    elif (args.action == 'get'):
        print(
            get_param(
                args.name, args.region,
                decrypt=(not args.plaintext)).get('Parameter').get('Value'))
    elif (args.action == 'put'):
        if args.value is None:
            utils.print_error('Please supply parameter value.')
            sys.exit(1)
        put_param(args.name,
                  args.value,
                  args.region,
                  args.kms_key_alias,
                  overwrite=args.force,
                  plaintext=args.plaintext)
    elif (args.action == 'delete'):
        delete_param(args.name, args.region)
Ejemplo n.º 2
0
    def call(self, src: pathlib.Path, dst: pathlib.Path, *args):
        """Call asciidoc for the given files.

        Args:
            src: The source .asciidoc file.
            dst: The destination .html file, or None to auto-guess.
            *args: Additional arguments passed to asciidoc.
        """
        print("Calling asciidoc for {}...".format(src.name))
        assert self._cmd is not None  # for mypy
        cmdline = self._cmd[:]
        if dst is not None:
            cmdline += ['--out-file', str(dst)]
        cmdline += args
        cmdline.append(str(src))
        try:
            env = os.environ.copy()
            env['HOME'] = str(self._homedir)
            subprocess.run(cmdline, check=True, env=env)
        except (subprocess.CalledProcessError, OSError) as e:
            self._failed = True
            utils.print_error(str(e))
            print("Keeping modified sources in {}.".format(self._homedir),
                  file=sys.stderr)
            sys.exit(1)
Ejemplo n.º 3
0
def main():
    args = parse_args()
    try:
        run(args)
    except Error as e:
        utils.print_error(str(e))
        sys.exit(e.code)
Ejemplo n.º 4
0
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',
                        action='store_true',
                        required=False,
                        help="Toggle to upload the release to GitHub")
    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
        read_github_token()

    if not misc_checks.check_git():
        utils.print_error("Refusing to do a release with a dirty git tree")
        sys.exit(1)

    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:
        upgrade_sdist_dependencies()
        test_makefile()
        artifacts = build_sdist()
        upload_to_pypi = True

    if args.upload:
        version_tag = "v" + qutebrowser.__version__
        utils.print_title("Press enter to release {}...".format(version_tag))
        input()

        github_upload(artifacts, version_tag)
        if upload_to_pypi:
            pypi_upload(artifacts)
    else:
        print()
        utils.print_title("Artifacts")
        for artifact in artifacts:
            print(artifact)
Ejemplo n.º 5
0
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

    libs = _find_libs()
    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)
Ejemplo n.º 6
0
def delete_param(name, region):
    """Remove SSM parameter."""
    ssm = boto3.client('ssm', region)
    try:
        utils.print_info(json.dumps(ssm.delete_parameter(Name=name)))
    except botocore.exceptions.ClientError as e:
        if (e.response['Error']['Code'] == 'ParameterNotFound'):
            utils.print_error(f'Cannot find {name}')
            sys.exit(1)
            raise e
Ejemplo n.º 7
0
def get_param(name, region, decrypt=True):
    """Retrieve parameter."""
    ssm = boto3.client('ssm', region)
    try:
        return ssm.get_parameter(Name=name, WithDecryption=decrypt)
    except botocore.exceptions.ClientError as e:
        if (e.response['Error']['Code'] == 'ParameterNotFound'):
            utils.print_error(f'Cannot find {name}')
            sys.exit(1)
        raise e
Ejemplo n.º 8
0
def github_upload(artifacts: List[Artifact], tag: str, gh_token: str) -> None:
    """Upload the given artifacts to GitHub.

    Args:
        artifacts: A list of Artifacts to upload.
        tag: The name of the release tag
        gh_token: The GitHub token to use
    """
    import github3
    import github3.exceptions
    utils.print_title("Uploading to github...")

    gh = github3.login(token=gh_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(f"No release found for {tag!r}!")

    for artifact in artifacts:
        while True:
            print(f"Uploading {artifact.path}")

            assets = [asset for asset in release.assets()
                      if asset.name == artifact.path.name]
            if assets:
                print(f"Assets already exist: {assets}")
                print("Press enter to continue anyways or Ctrl-C to abort.")
                input()

            try:
                with artifact.path.open('rb') as f:
                    release.upload_asset(
                        artifact.mimetype,
                        artifact.path.name,
                        f,
                        artifact.description,
                    )
            except github3.exceptions.ConnectionError as e:
                utils.print_error(f'Failed to upload: {e}')
                print("Press Enter to retry...", file=sys.stderr)
                input()
                print("Retrying!")

                assets = [asset for asset in release.assets()
                          if asset.name == artifact.path.name]
                if assets:
                    stray_asset = assets[0]
                    print(f"Deleting stray asset {stray_asset.name}")
                    stray_asset.delete()
            else:
                break
Ejemplo n.º 9
0
def run_venv(venv_dir: pathlib.Path, executable, *args: str) -> None:
    """Run the given command inside the virtualenv."""
    subdir = 'Scripts' if os.name == 'nt' else 'bin'

    try:
        subprocess.run([str(venv_dir / subdir / executable)] +
                       [str(arg) for arg in args],
                       check=True)
    except subprocess.CalledProcessError as e:
        utils.print_error("Subprocess failed, exiting")
        sys.exit(e.returncode)
Ejemplo n.º 10
0
def github_upload(artifacts, tag, gh_token):
    """Upload the given artifacts to GitHub.

    Args:
        artifacts: A list of (filename, mimetype, description) tuples
        tag: The name of the release tag
        gh_token: The GitHub token to use
    """
    import github3
    import github3.exceptions
    utils.print_title("Uploading to github...")

    gh = github3.login(token=gh_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:
        while True:
            print("Uploading {}".format(filename))

            basename = os.path.basename(filename)
            assets = [
                asset for asset in release.assets() if asset.name == basename
            ]
            if assets:
                print("Assets already exist: {}".format(assets))
                print("Press enter to continue anyways or Ctrl-C to abort.")
                input()

            try:
                with open(filename, 'rb') as f:
                    release.upload_asset(mimetype, basename, f, description)
            except github3.exceptions.ConnectionError as e:
                utils.print_error('Failed to upload: {}'.format(e))
                print("Press Enter to retry...", file=sys.stderr)
                input()
                print("Retrying!")

                assets = [
                    asset for asset in release.assets()
                    if asset.name == basename
                ]
                if assets:
                    stray_asset = assets[0]
                    print("Deleting stray asset {}".format(stray_asset.name))
                    stray_asset.delete()
            else:
                break
Ejemplo n.º 11
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('qt_location', help='Qt compiler directory')
    parser.add_argument('--wheels-dir',
                        help='Directory to use for wheels',
                        default='wheels')
    args = parser.parse_args()

    old_cwd = pathlib.Path.cwd()

    try:
        pyqt_bundle = find_pyqt_bundle()
    except FileNotFoundError as e:
        utils.print_error(str(e))
        sys.exit(1)

    qt_dir = pathlib.Path(args.qt_location)
    bin_dir = qt_dir / 'bin'
    if not bin_dir.exists():
        utils.print_error("Can't find {}".format(bin_dir))
        sys.exit(1)

    wheels_dir = pathlib.Path(args.wheels_dir).resolve()
    wheels_dir.mkdir(exist_ok=True)

    if list(wheels_dir.glob('*')):
        utils.print_col(
            "Wheels directory is not empty, "
            "unexpected behavior might occur!", 'yellow')

    os.chdir(wheels_dir)

    utils.print_title("Downloading wheels")
    subprocess.run([
        sys.executable, '-m', 'pip', 'download', '--no-deps', '--only-binary',
        'PyQt5,PyQtWebEngine', 'PyQt5', 'PyQtWebEngine'
    ],
                   check=True)

    utils.print_title("Patching wheels")
    input_files = wheels_dir.glob('*.whl')
    for wheel in input_files:
        utils.print_subtitle(wheel.stem.split('-')[0])
        subprocess.run([
            str(pyqt_bundle), '--qt-dir', args.qt_location, '--ignore-missing',
            str(wheel)
        ],
                       check=True)
        wheel.unlink()

    print("Done, output files:")
    for wheel in wheels_dir.glob('*.whl'):
        print(wheel.relative_to(old_cwd))
Ejemplo n.º 12
0
def create_venv(venv_dir: pathlib.Path, use_virtualenv: bool = False) -> None:
    """Create a new virtualenv."""
    if use_virtualenv:
        utils.print_col('$ python3 -m virtualenv {}'.format(venv_dir), 'blue')
        try:
            subprocess.run([sys.executable, '-m', 'virtualenv', venv_dir],
                           check=True)
        except subprocess.CalledProcessError as e:
            utils.print_error("virtualenv failed, exiting")
            sys.exit(e.returncode)
    else:
        utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue')
        venv.create(str(venv_dir), with_pip=True)
Ejemplo n.º 13
0
def install(languages):
    """Install languages."""
    for lang in languages:
        try:
            print('Installing {}: {}'.format(lang.code, lang.name))
            install_lang(lang)
        except PermissionError as e:
            msg = ("\n{}\n\nWith Qt < 5.10, you will need to run this script "
                   "as root, as dictionaries need to be installed "
                   "system-wide. If your qutebrowser uses a newer Qt version "
                   "via a virtualenv, make sure you start this script with "
                   "the virtualenv's Python.".format(e))
            scriptutils.print_error(msg)
            sys.exit(1)
Ejemplo n.º 14
0
def show_tox_error(pyqt_type: str) -> None:
    """DIsplay an error when invoked from tox."""
    if pyqt_type == 'link':
        env = 'mkvenv'
        args = ' --pyqt-type link'
    elif pyqt_type == 'binary':
        env = 'mkvenv-pypi'
        args = ''
    else:
        raise AssertionError

    print()
    utils.print_error(
        'tox -e {} is deprecated. '
        'Please use "python3 scripts/mkvenv.py{}" instead.'.format(env, args))
    print()
Ejemplo n.º 15
0
def main() -> None:
    """Install qutebrowser in a virtualenv.."""
    args = parse_args()
    venv_dir = pathlib.Path(args.venv_dir)
    wheels_dir = pathlib.Path(args.pyqt_wheels_dir)
    utils.change_cwd()

    if args.tox_error:
        show_tox_error(args.pyqt_type)
        sys.exit(1)
    elif (args.pyqt_version != 'auto'
          and args.pyqt_type not in ['binary', 'source']):
        utils.print_error('The --pyqt-version option is only available when '
                          'installing PyQt from binary or source')
        sys.exit(1)
    elif args.pyqt_wheels_dir != 'wheels' and args.pyqt_type != 'wheels':
        utils.print_error('The --pyqt-wheels-dir option is only available '
                          'when installing PyQt from wheels')
        sys.exit(1)

    if not args.keep:
        utils.print_title("Creating virtual environment")
        delete_old_venv(venv_dir)
        create_venv(venv_dir, use_virtualenv=args.virtualenv)

    upgrade_seed_pkgs(venv_dir)

    if args.pyqt_type == 'binary':
        install_pyqt_binary(venv_dir, args.pyqt_version)
    elif args.pyqt_type == 'source':
        install_pyqt_source(venv_dir, args.pyqt_version)
    elif args.pyqt_type == 'link':
        install_pyqt_link(venv_dir)
    elif args.pyqt_type == 'wheels':
        install_pyqt_wheels(venv_dir, wheels_dir)
    elif args.pyqt_type == 'skip':
        pass
    else:
        raise AssertionError

    install_requirements(venv_dir)
    install_qutebrowser(venv_dir)
    if args.dev:
        install_dev_requirements(venv_dir)

    if not args.skip_docs:
        regenerate_docs(venv_dir, args.asciidoc)
Ejemplo n.º 16
0
def run(**kwargs) -> None:
    """Regenerate documentation."""
    DOC_DIR.mkdir(exist_ok=True)

    asciidoc = AsciiDoc(**kwargs)
    try:
        asciidoc.prepare()
    except FileNotFoundError:
        utils.print_error("Could not find asciidoc! Please install it, or use "
                          "the --asciidoc argument to point this script to "
                          "the correct python/asciidoc.py location!")
        sys.exit(1)

    try:
        asciidoc.build()
    finally:
        asciidoc.cleanup()
Ejemplo n.º 17
0
def delete_old_venv(venv_dir: pathlib.Path) -> None:
    """Remove an existing virtualenv directory."""
    if not venv_dir.exists():
        return

    markers = [
        venv_dir / '.tox-config1',  # tox
        venv_dir / 'pyvenv.cfg',  # venv
        venv_dir / 'Scripts',  # Windows
        venv_dir / 'bin',  # Linux
    ]

    if not any(m.exists() for m in markers):
        utils.print_error('{} does not look like a virtualenv, '
                          'cowardly refusing to remove it.'.format(venv_dir))
        sys.exit(1)

    utils.print_col('$ rm -r {}'.format(venv_dir), 'blue')
    shutil.rmtree(str(venv_dir))
Ejemplo n.º 18
0
def run(**kwargs):
    """Regenerate documentation."""
    try:
        os.mkdir('qutebrowser/html/doc')
    except FileExistsError:
        pass

    asciidoc = AsciiDoc(**kwargs)
    try:
        asciidoc.prepare()
    except FileNotFoundError:
        utils.print_error("Could not find asciidoc! Please install it, or use "
                          "the --asciidoc argument to point this script to "
                          "the correct python/asciidoc.py location!")
        sys.exit(1)

    try:
        asciidoc.build()
    finally:
        asciidoc.cleanup()
Ejemplo n.º 19
0
def install_pyqt_binary(venv_dir: pathlib.Path, version: str) -> None:
    """Install PyQt from a binary wheel."""
    utils.print_title("Installing PyQt from binary")
    utils.print_col(
        "No proprietary codec support will be available in "
        "qutebrowser.", 'bold')

    supported_archs = {
        'linux': {'x86_64'},
        'win32': {'x86', 'AMD64'},
        'darwin': {'x86_64'},
    }
    if sys.platform not in supported_archs:
        utils.print_error(
            f"{sys.platform} is not a supported platform by PyQt5 binary "
            "packages, this will most likely fail.")
    elif platform.machine() not in supported_archs[sys.platform]:
        utils.print_error(
            f"{platform.machine()} is not a supported architecture for PyQt5 binaries "
            f"on {sys.platform}, this will most likely fail.")
    elif sys.platform == 'linux' and platform.libc_ver()[0] != 'glibc':
        utils.print_error(
            "Non-glibc Linux is not a supported platform for PyQt5 "
            "binaries, this will most likely fail.")

    pip_install(venv_dir, '-r', pyqt_requirements_file(version),
                '--only-binary', 'PyQt5,PyQtWebEngine')
Ejemplo n.º 20
0
def put_param(name,
              value,
              region,
              kms_key_alias=None,
              overwrite=False,
              plaintext=True):
    """Store the name and value"""
    ssm = boto3.client('ssm', region)

    try:
        if kms_key_alias:
            kms_key = kms.get_kms_key_id(kms_key_alias, region)
            if not kms_key:
                raise ParamException(
                    f'No key found for alias {kms_key_alias} {region}')
            result = ssm.put_parameter(Name=name,
                                       Description=name,
                                       Value=value,
                                       Type='SecureString',
                                       KeyId=kms_key,
                                       Overwrite=overwrite)
        else:
            utils.print_warning('Creating without encryption')
            result = ssm.put_parameter(Name=name,
                                       Description=name,
                                       Value=value,
                                       Type='String',
                                       Overwrite=overwrite)

        utils.print_info(json.dumps(result))
    except botocore.exceptions.ClientError as e:
        if (e.response['Error']['Code'] == 'ParameterAlreadyExists'):
            utils.print_error(
                f'setting "{name}" already exists, use -f to overwrite.')
            sys.exit(1)
        raise e
Ejemplo n.º 21
0
def main():
    args = parse_args()
    if args.data:
        if len(args.data) < 8:
            utils.print_error('Secrets should be 8 characters or greater')
            sys.exit(1)
    if (args.action == 'encrypt'):
        if not args.alias:
            utils.print_error('You must provide --alias to encrypt')
            sys.exit(1)
        print(
            encrypt(args.data, args.alias, json.loads(args.context),
                    args.region))
    elif (args.action == 'decrypt'):
        print(decrypt(args.data, json.loads(args.context), args.region))
    else:
        utils.print_error('Commands are encrypt/decrypt')
        sys.exit(1)
Ejemplo n.º 22
0
def rolling_replace_instances(ecs, ec2, cluster_name, batches, ami_id, force, drain_timeout_s):

    replace_start_time = time.time()
    services = get_services(ecs, cluster_name)
    if not services:
        raise RollingException('No services found in cluster. exiting.')
    utils.print_info(
        f'Checking cluster {cluster_name}, services {str(services)} are stable'
    )
    ecs_utils.poll_cluster_state(
        ecs, cluster_name, services, polling_timeout=120
    )
    instances = get_container_instance_arns(ecs, cluster_name)
    # batches determines the number of instances you want to replace at once.
    # Choose conservatively, as this process temporarily reduces your capacity.
    # But note each batch can be time consuming (up to 10m per batch)

    batch_count = math.ceil(len(instances) / batches)
    utils.print_info(f'You have {len(instances)} instances.')
    utils.print_info(f'Terminating in batches of {batch_count}')
    if len(instances) <= batch_count:
        utils.print_warning(
            f'Terminating {batch_count} instances will cause downtime.'
        )
        if not force:
            raise RollingException('Quitting, use --force to over-ride.')
    instance_batches = batch_instances(instances, batch_count)
    for to_drain in instance_batches:
        if len(to_drain) > 100:
            utils.print_error('Batch size exceeded 100, try using more batches.')
            raise RollingException(
                f'Quitting, batch size exceeded 100: {batch_count}.'
            )
        response = ecs.describe_container_instances(
            cluster=cluster_name, containerInstances=to_drain)

        if not response.get('containerInstances'):
            raise RollingException('No containerInstances found.')

        # don't drain or teriminate any instances that are already up to date
        # (if the user provided the --ami-id flag)
        done_instances = get_already_updated_instances(response, ami_id)
        if len(done_instances) == len(to_drain):
            # move on if the whole batch is already up to date
            continue

        # drain instances in this batch
        ecs.update_container_instances_state(cluster=cluster_name,
                                             status='DRAINING',
                                             containerInstances=to_drain)
        utils.print_info(f'Wait for drain to complete with {drain_timeout_s}s timeout...')
        start_time = time.time()
        while len(done_instances) < len(to_drain):
            if (time.time() - start_time) > drain_timeout_s:
                raise RollingTimeoutException('Waiting for instance to complete draining. Giving up.')
            time.sleep(SLEEP_TIME_S)
            response = ecs.describe_container_instances(
                cluster=cluster_name, containerInstances=to_drain)
            for container_instance in response.get('containerInstances'):
                instance_id = container_instance.get('ec2InstanceId')
                running_tasks = container_instance.get('runningTasksCount')
                if running_tasks > 0:
                    PRINT_PROGRESS()
                    continue
                if instance_id not in done_instances:
                    utils.print_info(f'{instance_id} is drained, terminate!')
                    ec2.terminate_instances(InstanceIds=[instance_id])
                    done_instances.append(instance_id)
        # new instance will take as much as 10m to go into service
        # then we wait for ECS to resume a steady state before moving on
        ecs_utils.poll_cluster_state(ecs, cluster_name,
                                     services, polling_timeout=drain_timeout_s)
    utils.print_success(f'EC2 instance replacement process complete! {int(time.time() - replace_start_time)}s elapsed')
Ejemplo n.º 23
0
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:
        test_makefile()
        artifacts = build_sdist()
        twine_check(artifacts)
        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)