#!/usr/bin/env python # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. import argparse import errno import logging import os import requests import re import shutil import subprocess import sys import tempfile from lib.github import GitHub from lib.helpers import * from lib.config import (PLATFORM, get_env_var, product_name, project_name, SOURCE_ROOT, dist_dir, output_dir, get_brave_version, get_platform_key, get_raw_version) from lib.util import execute, parse_version, scoped_cwd, s3put if os.environ.get('DEBUG_HTTP_HEADERS') == 'true': try: from http.client import HTTPConnection # python3 except ImportError: from httplib import HTTPConnection # python2 def get_zip_name(name, version, target_arch, suffix=''): arch = 'ia32' if (target_arch == 'x86') else target_arch if arch == 'arm': arch += 'v7l' zip_name = '{0}-{1}-{2}-{3}'.format(name, version, get_platform_key(), arch) if suffix: zip_name += '-' + suffix return zip_name + '.zip' def main(): args = parse_args() print('[INFO] Running upload...') # Enable urllib3 debugging output if os.environ.get('DEBUG_HTTP_HEADERS') == 'true': logging.basicConfig(level=logging.DEBUG) logging.getLogger("urllib3").setLevel(logging.DEBUG) logging.debug("DEBUG_HTTP_HEADERS env var is enabled, logging HTTP headers") debug_requests_on() # BRAVE_REPO is defined in lib/helpers.py for now repo = GitHub(get_env_var('GITHUB_TOKEN')).repos(BRAVE_REPO) tag = get_brave_version() release = get_release(repo, tag, allow_published_release_updates=False) if not release: print("[INFO] No existing release found, creating new " "release for this upload") release = create_release_draft(repo, tag) d_dir = dist_dir(args.target_os, args.target_arch) o_dir = output_dir(args.target_os, args.target_arch) DIST_NAME = get_zip_name(project_name(), get_brave_version(), args.target_arch) SYMBOLS_NAME = get_zip_name(project_name(), get_brave_version(), args.target_arch, 'symbols') DSYM_NAME = get_zip_name(project_name(), get_brave_version(), args.target_arch, 'dsym') PDB_NAME = get_zip_name(project_name(), get_brave_version(), args.target_arch, 'pdb') print('[INFO] Uploading release {}'.format(release['tag_name'])) # Upload Brave with GitHub Releases API. if args.target_os != 'android': upload_brave(repo, release, os.path.join(d_dir, DIST_NAME), force=args.force) upload_brave(repo, release, os.path.join(d_dir, SYMBOLS_NAME), force=args.force) else: o_dir = o_dir + '/apks' # if PLATFORM == 'darwin': # upload_brave(repo, release, os.path.join(d_dir, DSYM_NAME)) # elif PLATFORM == 'win32': # upload_brave(repo, release, os.path.join(d_dir, PDB_NAME)) pkgs = get_brave_packages(o_dir, release_channel(), get_raw_version(), args.target_os, args.target_arch, args.target_apk_base) for pkg in pkgs: upload_brave(repo, release, os.path.join(o_dir, pkg), force=args.force) # mksnapshot = get_zip_name('mksnapshot', get_brave_version(), args.target_arch) # upload_brave(repo, release, os.path.join(d_dir, mksnapshot)) # if PLATFORM == 'win32' and not tag_exists: # # Upload PDBs to Windows symbol server. # run_python_script('upload-windows-pdb.py') if os.environ.get('DEBUG_HTTP_HEADERS') == 'true': debug_requests_off() print('[INFO] Finished upload') def debug_requests_on(): '''Switches on logging of the requests module.''' HTTPConnection.debuglevel = 1 logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True def debug_requests_off(): '''Switches off logging of the requests module, might be some side-effects''' HTTPConnection.debuglevel = 0 root_logger = logging.getLogger() root_logger.setLevel(logging.WARNING) root_logger.handlers = [] requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.WARNING) def get_brave_packages(dir, channel, version, target_os, target_arch='x64', target_apk_base=''): pkgs = [] def filecopy(file_path, file_desired): file_desired_path = os.path.join(dir, file_desired) if os.path.isfile(file_path): print('[INFO] Copying file ' + file_path + ' to ' + file_desired_path) shutil.copy(file_path, file_desired_path) return file_desired_path channel_capitalized = '' if (channel == 'release') else channel.capitalize() for file in os.listdir(dir): if os.path.isfile(os.path.join(dir, file)): file_path = os.path.join(dir, file) if PLATFORM == 'darwin': channel_capitalized_dashed = '' if (channel == 'release') else ('-' + channel_capitalized) channel_capitalized_spaced = '' if (channel == 'release') else (' ' + channel_capitalized) file_dmg = 'Brave-Browser' + channel_capitalized_dashed + '.dmg' file_pkg = 'Brave-Browser' + channel_capitalized_dashed + '.pkg' if re.match(r'Brave Browser' + channel_capitalized_spaced + r'.*\.dmg$', file): filecopy(file_path, file_dmg) pkgs.append(file_dmg) elif file == file_dmg: pkgs.append(file_dmg) elif re.match(r'Brave Browser' + channel_capitalized_spaced + r'.*\.pkg$', file): filecopy(file_path, file_pkg) pkgs.append(file_pkg) elif file == file_pkg: pkgs.append(file_pkg) elif PLATFORM == 'linux': channel_dashed = '' if (channel == 'release') else ('-' + channel) if re.match(r'brave-browser' + channel_dashed + '_' + version + r'.*\.deb$', file): pkgs.append(file) elif re.match(r'brave-browser' + channel_dashed + '-' + version + r'.*\.rpm$', file): pkgs.append(file) elif target_os == 'android': if target_arch == 'arm': if not target_apk_base: if re.match(r'Brave.*arm.apk$', file): pkgs.append(file) else: if target_apk_base == 'classic': if file == 'Bravearm.apk': pkgs.append(file) elif target_apk_base == 'modern': if file == 'BraveModernarm.apk': pkgs.append(file) elif target_apk_base == 'mono': if file == 'BraveMonoarm.apk': pkgs.append(file) elif target_arch == 'arm64': if file == 'BraveMonoarm64.apk': pkgs.append(file) elif target_arch == 'x86': if not target_apk_base: if re.match(r'Brave.*x86.apk$', file): pkgs.append(file) else: if target_apk_base == 'classic': if file == 'Bravex86.apk': pkgs.append(file) elif target_apk_base == 'modern': if file == 'BraveModernx86.apk': pkgs.append(file) elif target_apk_base == 'mono': if file == 'BraveMonox86.apk': pkgs.append(file) elif target_arch == 'x64': if file == 'BraveMonox64.apk': pkgs.append(file) else: if re.match(r'Brave.*.apk$', file): pkgs.append(file) elif PLATFORM == 'win32': target_arch = 'ia32' if (target_arch == 'x86') else target_arch arch = '32' if (target_arch == 'ia32') else '' channel_arch_extension = channel_capitalized + 'Setup' + arch + '.exe' file_stub = 'BraveBrowser' + channel_arch_extension file_stub_silent = 'BraveBrowserSilent' + channel_arch_extension file_stub_untagged = 'BraveBrowserUntagged' + channel_arch_extension file_stn = 'BraveBrowserStandalone' + channel_arch_extension file_stn_silent = 'BraveBrowserStandaloneSilent' + channel_arch_extension file_stn_untagged = 'BraveBrowserStandaloneUntagged' + channel_arch_extension file_installer = 'brave_installer-' + target_arch + '.exe' if re.match(r'BraveBrowser' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stub) pkgs.append(file_stub) elif re.match(r'BraveBrowserSilent' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stub_silent) pkgs.append(file_stub_silent) elif re.match(r'BraveBrowserUntagged' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stub_untagged) pkgs.append(file_stub_untagged) elif re.match(r'BraveBrowserStandalone' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stn) pkgs.append(file_stn) elif re.match(r'BraveBrowserStandaloneSilent' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stn_silent) pkgs.append(file_stn_silent) elif re.match(r'BraveBrowserStandaloneUntagged' + channel_capitalized + r'Setup' + arch + r'_.*\.exe', file): filecopy(file_path, file_stn_untagged) pkgs.append(file_stn_untagged) elif re.match(r'brave_installer.exe', file): filecopy(file_path, file_installer) pkgs.append(file_installer) return sorted(list(set(pkgs))) def parse_args(): parser = argparse.ArgumentParser(description='upload distribution file') parser.add_argument('--force', action='store_true', help='Overwrite files in ' 'destination draft on upload.') parser.add_argument('-v', '--version', help='Specify the version', default=get_brave_version()) parser.add_argument('--target_os', help='Specify the target OS', default='') parser.add_argument('--target_arch', help='Specify the target arch', default='') parser.add_argument('--target_apk_base', help='Specify the target APK base', default='') return parser.parse_args() def run_python_script(script, *args): script_path = os.path.join(SOURCE_ROOT, 'script', script) return execute([sys.executable, script_path] + list(args)) def get_text_with_editor(name): editor = os.environ.get('EDITOR', 'nano') initial_message = ( '\n# Please enter the body of your release note for %s.' % name) t = tempfile.NamedTemporaryFile(suffix='.tmp', delete=False) t.write(initial_message) t.close() subprocess.call([editor, t.name]) text = '' for line in open(t.name, 'r'): if len(line) == 0 or line[0] != '#': text += line os.unlink(t.name) return text def create_release_draft(repo, tag): name = '{0} {1}'.format(release_name(), tag) channel = release_channel() channel_capitalized_dashed = '' if (channel == 'release') else ('-' + channel.capitalize()) # TODO: Parse release notes from CHANGELOG.md nightly_winstallers = ( '`BraveBrowserNightlySetup.exe` and `BraveBrowserNightlySetup32.exe`') dev_winstallers = ( '`BraveBrowserDevSetup.exe` and `BraveBrowserDevSetup32.exe`') beta_winstallers = ( '`BraveBrowserBetaSetup.exe` and `BraveBrowserBetaSetup32.exe`') release_winstallers = ( '`BraveBrowserSetup.exe` and `BraveBrowserSetup32.exe`') nightly_dev_beta_warning = '''*This is not the released version of Brave. **Be careful** - things are unstable and might even be broken.* These builds are an unpolished and unfinished early preview for the new \ version of Brave on the desktop. These builds show our work in progress and \ they aren't for the faint-of-heart. Features may be missing or broken in new \ and exciting ways; familiar functionality may have unfamiliar side-effects. \ These builds showcase the newest advances that we're bringing to your browser, \ but this is still a prototype, not a reliable daily driver. Try it out only if \ you're looking for a little extra spice and adventure in your browsing.''' if channel == 'nightly': winstallers = nightly_winstallers warning = nightly_dev_beta_warning elif channel == 'dev': winstallers = dev_winstallers warning = nightly_dev_beta_warning elif channel == 'beta': winstallers = beta_winstallers warning = nightly_dev_beta_warning else: winstallers = release_winstallers warning = "" body = '''{warning} # Mac installation Install Brave-Browser{channel_capitalized_dashed}.dmg on your system. # Linux install instructions https://brave.com/linux # Windows {win} will fetch and install the latest available version from our \ update server.'''.format(warning=warning, channel_capitalized_dashed=channel_capitalized_dashed, win=winstallers) data = dict(tag_name=tag, name=name, body=body, draft=True) release = retry_func(lambda run: repo.releases.post(data=data), catch=requests.exceptions.ConnectionError, retries=3) return release def upload_brave(github, release, file_path, filename=None, force=False): # Delete the original file before uploading. if filename is None: filename = os.path.basename(file_path) try: for asset in release['assets']: if asset['name'] == filename: print('[INFO] Asset "' + filename + '" exists; deleting...') github.repos(BRAVE_REPO).releases.assets(asset['id']).delete() print('[INFO] Asset "' + filename + '" deleted') except Exception: pass # Upload the file. print('[INFO] Uploading: ' + filename) with open(file_path, 'rb') as f: if force: print('[INFO] force deleting "' + filename + '" before upload...') delete_file(github, release, filename) print('[INFO] force deleted "' + filename + '".') retry_func( lambda ran: upload_io_to_github(github, release, filename, f, 'application/zip'), catch_func=lambda ran: delete_file(github, release, filename), catch=requests.exceptions.ConnectionError, retries=3 ) # Upload ARM assets without the v7l suffix for backwards compatibility # TODO Remove for 2.0 if 'armv7l' in filename: arm_filename = filename.replace('armv7l', 'arm') arm_file_path = os.path.join(os.path.dirname(file_path), arm_filename) shutil.copy2(file_path, arm_file_path) upload_brave(github, release, arm_file_path) def upload_io_to_github(github, release, name, io, content_type): io.seek(0) github.releases(release['id']).assets.post( params={'name': name}, headers={'Content-Type': content_type}, data=io, verify=False ) def auth_token(): token = get_env_var('GITHUB_TOKEN') message = ('Error: Please set the $BRAVE_GITHUB_TOKEN ' 'environment variable, which is your personal token') assert token, message return token def delete_file(github, release, name, retries=3): release = retry_func( lambda run: github.releases(release['id']).get(), catch=requests.exceptions.ConnectionError, retries=3 ) for asset in release['assets']: if asset['name'] == name: print("[INFO] Deleting file name '{}' with asset id {}".format(name, asset['id'])) retry_func(lambda run: github.releases.assets(asset['id']).delete(), catch=requests.exceptions.ConnectionError, retries=3) if __name__ == '__main__': import sys sys.exit(main())