upload.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #!/usr/bin/env python
  2. import argparse
  3. import errno
  4. import hashlib
  5. import os
  6. import shutil
  7. import subprocess
  8. import sys
  9. import tempfile
  10. from io import StringIO
  11. from lib.config import PLATFORM, get_target_arch, get_chromedriver_version, \
  12. get_env_var, s3_config, get_zip_name
  13. from lib.util import electron_gyp, execute, get_electron_version, \
  14. parse_version, scoped_cwd, s3put
  15. from lib.github import GitHub
  16. ELECTRON_REPO = 'electron/electron'
  17. ELECTRON_VERSION = get_electron_version()
  18. PROJECT_NAME = electron_gyp()['project_name%']
  19. PRODUCT_NAME = electron_gyp()['product_name%']
  20. SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
  21. OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'R')
  22. DIST_DIR = os.path.join(SOURCE_ROOT, 'dist')
  23. DIST_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION)
  24. SYMBOLS_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'symbols')
  25. DSYM_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'dsym')
  26. PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
  27. def main():
  28. args = parse_args()
  29. if not args.publish_release:
  30. if not dist_newer_than_head():
  31. run_python_script('create-dist.py')
  32. build_version = get_electron_build_version()
  33. if not ELECTRON_VERSION.startswith(build_version):
  34. error = 'Tag name ({0}) should match build version ({1})\n'.format(
  35. ELECTRON_VERSION, build_version)
  36. sys.stderr.write(error)
  37. sys.stderr.flush()
  38. return 1
  39. github = GitHub(auth_token())
  40. releases = github.repos(ELECTRON_REPO).releases.get()
  41. tag_exists = False
  42. for release in releases:
  43. if not release['draft'] and release['tag_name'] == args.version:
  44. tag_exists = True
  45. break
  46. release = create_or_get_release_draft(github, releases, args.version,
  47. tag_exists)
  48. if args.publish_release:
  49. # Upload the Node SHASUMS*.txt.
  50. run_python_script('upload-node-checksums.py', '-v', ELECTRON_VERSION)
  51. # Upload the index.json.
  52. run_python_script('upload-index-json.py')
  53. # Create and upload the Electron SHASUMS*.txt
  54. release_electron_checksums(github, release)
  55. # Press the publish button.
  56. publish_release(github, release['id'])
  57. # Do not upload other files when passed "-p".
  58. return
  59. # Upload Electron with GitHub Releases API.
  60. upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME))
  61. upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME))
  62. if PLATFORM == 'darwin':
  63. upload_electron(github, release, os.path.join(DIST_DIR,
  64. 'electron-api.json'))
  65. upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME))
  66. elif PLATFORM == 'win32':
  67. upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME))
  68. # Upload free version of ffmpeg.
  69. ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)
  70. upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg))
  71. # Upload chromedriver and mksnapshot for minor version update.
  72. if parse_version(args.version)[2] == '0':
  73. chromedriver = get_zip_name('chromedriver', get_chromedriver_version())
  74. upload_electron(github, release, os.path.join(DIST_DIR, chromedriver))
  75. mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION)
  76. upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot))
  77. if PLATFORM == 'win32' and not tag_exists:
  78. # Upload PDBs to Windows symbol server.
  79. run_python_script('upload-windows-pdb.py')
  80. # Upload node headers.
  81. run_python_script('upload-node-headers.py', '-v', args.version)
  82. def parse_args():
  83. parser = argparse.ArgumentParser(description='upload distribution file')
  84. parser.add_argument('-v', '--version', help='Specify the version',
  85. default=ELECTRON_VERSION)
  86. parser.add_argument('-p', '--publish-release',
  87. help='Publish the release',
  88. action='store_true')
  89. return parser.parse_args()
  90. def run_python_script(script, *args):
  91. script_path = os.path.join(SOURCE_ROOT, 'script', script)
  92. return execute([sys.executable, script_path] + list(args))
  93. def get_electron_build_version():
  94. if get_target_arch() == 'arm' or os.environ.has_key('CI'):
  95. # In CI we just build as told.
  96. return ELECTRON_VERSION
  97. if PLATFORM == 'darwin':
  98. electron = os.path.join(SOURCE_ROOT, 'out', 'R',
  99. '{0}.app'.format(PRODUCT_NAME), 'Contents',
  100. 'MacOS', PRODUCT_NAME)
  101. elif PLATFORM == 'win32':
  102. electron = os.path.join(SOURCE_ROOT, 'out', 'R',
  103. '{0}.exe'.format(PROJECT_NAME))
  104. else:
  105. electron = os.path.join(SOURCE_ROOT, 'out', 'R', PROJECT_NAME)
  106. return subprocess.check_output([electron, '--version']).strip()
  107. def dist_newer_than_head():
  108. with scoped_cwd(SOURCE_ROOT):
  109. try:
  110. head_time = subprocess.check_output(['git', 'log', '--pretty=format:%at',
  111. '-n', '1']).strip()
  112. dist_time = os.path.getmtime(os.path.join(DIST_DIR, DIST_NAME))
  113. except OSError as e:
  114. if e.errno != errno.ENOENT:
  115. raise
  116. return False
  117. return dist_time > int(head_time)
  118. def get_text_with_editor(name):
  119. editor = os.environ.get('EDITOR', 'nano')
  120. initial_message = '\n# Please enter the body of your release note for %s.' \
  121. % name
  122. t = tempfile.NamedTemporaryFile(suffix='.tmp', delete=False)
  123. t.write(initial_message)
  124. t.close()
  125. subprocess.call([editor, t.name])
  126. text = ''
  127. for line in open(t.name, 'r'):
  128. if len(line) == 0 or line[0] != '#':
  129. text += line
  130. os.unlink(t.name)
  131. return text
  132. def create_or_get_release_draft(github, releases, tag, tag_exists):
  133. # Search for existing draft.
  134. for release in releases:
  135. if release['draft']:
  136. return release
  137. if tag_exists:
  138. tag = 'do-not-publish-me'
  139. return create_release_draft(github, tag)
  140. def create_release_draft(github, tag):
  141. name = '{0} {1}'.format(PROJECT_NAME, tag)
  142. if os.environ.has_key('CI'):
  143. body = '(placeholder)'
  144. else:
  145. body = get_text_with_editor(name)
  146. if body == '':
  147. sys.stderr.write('Quit due to empty release note.\n')
  148. sys.exit(0)
  149. data = dict(tag_name=tag, name=name, body=body, draft=True)
  150. r = github.repos(ELECTRON_REPO).releases.post(data=data)
  151. return r
  152. def release_electron_checksums(github, release):
  153. checksums = run_python_script('merge-electron-checksums.py',
  154. '-v', ELECTRON_VERSION)
  155. upload_io_to_github(github, release, 'SHASUMS256.txt',
  156. StringIO(checksums.decode('utf-8')), 'text/plain')
  157. def upload_electron(github, release, file_path):
  158. # Delete the original file before uploading in CI.
  159. filename = os.path.basename(file_path)
  160. if os.environ.has_key('CI'):
  161. try:
  162. for asset in release['assets']:
  163. if asset['name'] == filename:
  164. github.repos(ELECTRON_REPO).releases.assets(asset['id']).delete()
  165. except Exception:
  166. pass
  167. # Upload the file.
  168. with open(file_path, 'rb') as f:
  169. upload_io_to_github(github, release, filename, f, 'application/zip')
  170. # Upload the checksum file.
  171. upload_sha256_checksum(release['tag_name'], file_path)
  172. # Upload ARM assets without the v7l suffix for backwards compatibility
  173. # TODO Remove for 2.0
  174. if 'armv7l' in filename:
  175. arm_filename = filename.replace('armv7l', 'arm')
  176. arm_file_path = os.path.join(os.path.dirname(file_path), arm_filename)
  177. shutil.copy2(file_path, arm_file_path)
  178. upload_electron(github, release, arm_file_path)
  179. def upload_io_to_github(github, release, name, io, content_type):
  180. params = {'name': name}
  181. headers = {'Content-Type': content_type}
  182. github.repos(ELECTRON_REPO).releases(release['id']).assets.post(
  183. params=params, headers=headers, data=io, verify=False)
  184. def upload_sha256_checksum(version, file_path):
  185. bucket, access_key, secret_key = s3_config()
  186. checksum_path = '{}.sha256sum'.format(file_path)
  187. sha256 = hashlib.sha256()
  188. with open(file_path, 'rb') as f:
  189. sha256.update(f.read())
  190. filename = os.path.basename(file_path)
  191. with open(checksum_path, 'w') as checksum:
  192. checksum.write('{} *{}'.format(sha256.hexdigest(), filename))
  193. s3put(bucket, access_key, secret_key, os.path.dirname(checksum_path),
  194. 'atom-shell/tmp/{0}'.format(version), [checksum_path])
  195. def publish_release(github, release_id):
  196. data = dict(draft=False)
  197. github.repos(ELECTRON_REPO).releases(release_id).patch(data=data)
  198. def auth_token():
  199. token = get_env_var('GITHUB_TOKEN')
  200. message = ('Error: Please set the $ELECTRON_GITHUB_TOKEN '
  201. 'environment variable, which is your personal token')
  202. assert token, message
  203. return token
  204. if __name__ == '__main__':
  205. import sys
  206. sys.exit(main())