upload.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #!/usr/bin/env python
  2. import argparse
  3. import datetime
  4. import errno
  5. import hashlib
  6. import os
  7. import subprocess
  8. import sys
  9. import tempfile
  10. from io import StringIO
  11. from lib.config import PLATFORM, get_target_arch, get_env_var, s3_config, \
  12. 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 args.upload_to_s3:
  30. utcnow = datetime.datetime.utcnow()
  31. args.upload_timestamp = utcnow.strftime('%Y-%m-%d_%H:%M:%S')
  32. if not dist_newer_than_head():
  33. run_python_script('create-dist.py')
  34. build_version = get_electron_build_version()
  35. if not ELECTRON_VERSION.startswith(build_version):
  36. error = 'Tag name ({0}) should match build version ({1})\n'.format(
  37. ELECTRON_VERSION, build_version)
  38. sys.stderr.write(error)
  39. sys.stderr.flush()
  40. return 1
  41. github = GitHub(auth_token())
  42. releases = github.repos(ELECTRON_REPO).releases.get()
  43. tag_exists = False
  44. release = None
  45. for r in releases:
  46. if not r['draft'] and r['tag_name'] == args.version:
  47. release = r
  48. tag_exists = True
  49. break
  50. if not args.upload_to_s3:
  51. assert tag_exists == args.overwrite, \
  52. 'You have to pass --overwrite to overwrite a published release'
  53. if not args.overwrite:
  54. release = create_or_get_release_draft(github, releases, args.version,
  55. tag_exists)
  56. elif release is None:
  57. release = dict(tag_name=args.version)
  58. # Upload Electron with GitHub Releases API.
  59. upload_electron(github, release, os.path.join(DIST_DIR, DIST_NAME), args)
  60. if get_target_arch() != 'mips64el':
  61. upload_electron(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME), args)
  62. if PLATFORM == 'darwin':
  63. upload_electron(github, release, os.path.join(DIST_DIR,
  64. 'electron-api.json'), args)
  65. upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts'),
  66. args)
  67. upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME),
  68. args)
  69. elif PLATFORM == 'win32':
  70. upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME),
  71. args)
  72. # Upload free version of ffmpeg.
  73. ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)
  74. upload_electron(github, release, os.path.join(DIST_DIR, ffmpeg),
  75. args)
  76. chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION)
  77. upload_electron(github, release, os.path.join(DIST_DIR, chromedriver),
  78. args)
  79. mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION)
  80. upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot),
  81. args)
  82. if get_target_arch().startswith('arm'):
  83. # Upload the x64 binary for arm/arm64 mksnapshot
  84. mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION, 'x64')
  85. upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot),
  86. args)
  87. if not tag_exists and not args.upload_to_s3:
  88. # Upload symbols to symbol server.
  89. run_python_script('upload-symbols.py')
  90. if PLATFORM == 'win32':
  91. # Upload node headers.
  92. run_python_script('create-node-headers.py', '-v', args.version)
  93. run_python_script('upload-node-headers.py', '-v', args.version)
  94. def parse_args():
  95. parser = argparse.ArgumentParser(description='upload distribution file')
  96. parser.add_argument('-v', '--version', help='Specify the version',
  97. default=ELECTRON_VERSION)
  98. parser.add_argument('-o', '--overwrite',
  99. help='Overwrite a published release',
  100. action='store_true')
  101. parser.add_argument('-p', '--publish-release',
  102. help='Publish the release',
  103. action='store_true')
  104. parser.add_argument('-s', '--upload_to_s3',
  105. help='Upload assets to s3 bucket',
  106. dest='upload_to_s3',
  107. action='store_true',
  108. default=False,
  109. required=False)
  110. return parser.parse_args()
  111. def run_python_script(script, *args):
  112. script_path = os.path.join(SOURCE_ROOT, 'script', script)
  113. return execute([sys.executable, script_path] + list(args))
  114. def get_electron_build_version():
  115. if get_target_arch().startswith('arm') or os.environ.has_key('CI'):
  116. # In CI we just build as told.
  117. return ELECTRON_VERSION
  118. if PLATFORM == 'darwin':
  119. electron = os.path.join(SOURCE_ROOT, 'out', 'R',
  120. '{0}.app'.format(PRODUCT_NAME), 'Contents',
  121. 'MacOS', PRODUCT_NAME)
  122. elif PLATFORM == 'win32':
  123. electron = os.path.join(SOURCE_ROOT, 'out', 'R',
  124. '{0}.exe'.format(PROJECT_NAME))
  125. else:
  126. electron = os.path.join(SOURCE_ROOT, 'out', 'R', PROJECT_NAME)
  127. return subprocess.check_output([electron, '--version']).strip()
  128. def dist_newer_than_head():
  129. with scoped_cwd(SOURCE_ROOT):
  130. try:
  131. head_time = subprocess.check_output(['git', 'log', '--pretty=format:%at',
  132. '-n', '1']).strip()
  133. dist_time = os.path.getmtime(os.path.join(DIST_DIR, DIST_NAME))
  134. except OSError as e:
  135. if e.errno != errno.ENOENT:
  136. raise
  137. return False
  138. return dist_time > int(head_time)
  139. def get_text_with_editor(name):
  140. editor = os.environ.get('EDITOR', 'nano')
  141. initial_message = '\n# Please enter the body of your release note for %s.' \
  142. % name
  143. t = tempfile.NamedTemporaryFile(suffix='.tmp', delete=False)
  144. t.write(initial_message)
  145. t.close()
  146. subprocess.call([editor, t.name])
  147. text = ''
  148. for line in open(t.name, 'r'):
  149. if len(line) == 0 or line[0] != '#':
  150. text += line
  151. os.unlink(t.name)
  152. return text
  153. def create_or_get_release_draft(github, releases, tag, tag_exists):
  154. # Search for existing draft.
  155. for release in releases:
  156. if release['draft'] and release['tag_name'] == tag:
  157. return release
  158. if tag_exists:
  159. tag = 'do-not-publish-me'
  160. return create_release_draft(github, tag)
  161. def create_release_draft(github, tag):
  162. name = '{0} {1} beta'.format(PROJECT_NAME, tag)
  163. if os.environ.has_key('CI'):
  164. body = '(placeholder)'
  165. else:
  166. body = get_text_with_editor(name)
  167. if body == '':
  168. sys.stderr.write('Quit due to empty release note.\n')
  169. sys.exit(0)
  170. data = dict(tag_name=tag, name=name, body=body, draft=True, prerelease=True)
  171. r = github.repos(ELECTRON_REPO).releases.post(data=data)
  172. return r
  173. def upload_electron(github, release, file_path, args):
  174. # if upload_to_s3 is set, skip github upload.
  175. if args.upload_to_s3:
  176. bucket, access_key, secret_key = s3_config()
  177. key_prefix = 'electron-artifacts/{0}_{1}'.format(release['tag_name'],
  178. args.upload_timestamp)
  179. s3put(bucket, access_key, secret_key, os.path.dirname(file_path),
  180. key_prefix, [file_path])
  181. upload_sha256_checksum(release['tag_name'], file_path, key_prefix)
  182. return
  183. # Delete the original file before uploading in CI.
  184. filename = os.path.basename(file_path)
  185. if os.environ.has_key('CI'):
  186. try:
  187. for asset in release['assets']:
  188. if asset['name'] == filename:
  189. github.repos(ELECTRON_REPO).releases.assets(asset['id']).delete()
  190. except Exception:
  191. pass
  192. # Upload the file.
  193. upload_io_to_github(release, filename, file_path)
  194. # Upload the checksum file.
  195. upload_sha256_checksum(release['tag_name'], file_path)
  196. def upload_io_to_github(release, filename, filepath):
  197. print 'Uploading %s to Github' % \
  198. (filename)
  199. script_path = os.path.join(SOURCE_ROOT, 'script', 'upload-to-github.js')
  200. execute(['node', script_path, filepath, filename, str(release['id'])])
  201. def upload_sha256_checksum(version, file_path, key_prefix=None):
  202. bucket, access_key, secret_key = s3_config()
  203. checksum_path = '{}.sha256sum'.format(file_path)
  204. if key_prefix is None:
  205. key_prefix = 'atom-shell/tmp/{0}'.format(version)
  206. sha256 = hashlib.sha256()
  207. with open(file_path, 'rb') as f:
  208. sha256.update(f.read())
  209. filename = os.path.basename(file_path)
  210. with open(checksum_path, 'w') as checksum:
  211. checksum.write('{} *{}'.format(sha256.hexdigest(), filename))
  212. s3put(bucket, access_key, secret_key, os.path.dirname(checksum_path),
  213. key_prefix, [checksum_path])
  214. def auth_token():
  215. token = get_env_var('GITHUB_TOKEN')
  216. message = ('Error: Please set the $ELECTRON_GITHUB_TOKEN '
  217. 'environment variable, which is your personal token')
  218. assert token, message
  219. return token
  220. if __name__ == '__main__':
  221. import sys
  222. sys.exit(main())