upload.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #!/usr/bin/env python3
  2. from __future__ import print_function
  3. import argparse
  4. import datetime
  5. import hashlib
  6. import json
  7. import mmap
  8. import os
  9. import shutil
  10. import subprocess
  11. from struct import Struct
  12. import sys
  13. sys.path.append(
  14. os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/../.."))
  15. from zipfile import ZipFile
  16. from lib.config import PLATFORM, get_target_arch, \
  17. get_zip_name, enable_verbose_mode, get_platform_key
  18. from lib.util import get_electron_branding, execute, get_electron_version, \
  19. store_artifact, get_electron_exec, get_out_dir, \
  20. SRC_DIR, ELECTRON_DIR, TS_NODE
  21. ELECTRON_VERSION = 'v' + get_electron_version()
  22. PROJECT_NAME = get_electron_branding()['project_name']
  23. PRODUCT_NAME = get_electron_branding()['product_name']
  24. OUT_DIR = get_out_dir()
  25. DIST_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION)
  26. SYMBOLS_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'symbols')
  27. DSYM_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'dsym')
  28. DSYM_SNAPSHOT_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION,
  29. 'dsym-snapshot')
  30. PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
  31. DEBUG_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'debug')
  32. TOOLCHAIN_PROFILE_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION,
  33. 'toolchain-profile')
  34. CXX_OBJECTS_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION,
  35. 'libcxx_objects')
  36. def main():
  37. args = parse_args()
  38. if args.verbose:
  39. enable_verbose_mode()
  40. if args.upload_to_storage:
  41. utcnow = datetime.datetime.utcnow()
  42. args.upload_timestamp = utcnow.strftime('%Y%m%d')
  43. build_version = get_electron_build_version()
  44. if not ELECTRON_VERSION.startswith(build_version):
  45. error = 'Tag name ({0}) should match build version ({1})\n'.format(
  46. ELECTRON_VERSION, build_version)
  47. sys.stderr.write(error)
  48. sys.stderr.flush()
  49. return 1
  50. tag_exists = False
  51. release = get_release(args.version)
  52. if not release['draft']:
  53. tag_exists = True
  54. if not args.upload_to_storage:
  55. assert release['exists'], \
  56. 'Release does not exist; cannot upload to GitHub!'
  57. assert tag_exists == args.overwrite, \
  58. 'You have to pass --overwrite to overwrite a published release'
  59. # Upload Electron files.
  60. # Rename dist.zip to get_zip_name('electron', version, suffix='')
  61. electron_zip = os.path.join(OUT_DIR, DIST_NAME)
  62. shutil.copy2(os.path.join(OUT_DIR, 'dist.zip'), electron_zip)
  63. upload_electron(release, electron_zip, args)
  64. if get_target_arch() != 'mips64el':
  65. symbols_zip = os.path.join(OUT_DIR, SYMBOLS_NAME)
  66. shutil.copy2(os.path.join(OUT_DIR, 'symbols.zip'), symbols_zip)
  67. upload_electron(release, symbols_zip, args)
  68. if PLATFORM == 'darwin':
  69. if get_platform_key() == 'darwin' and get_target_arch() == 'x64':
  70. api_path = os.path.join(ELECTRON_DIR, 'electron-api.json')
  71. upload_electron(release, api_path, args)
  72. ts_defs_path = os.path.join(ELECTRON_DIR, 'electron.d.ts')
  73. upload_electron(release, ts_defs_path, args)
  74. dsym_zip = os.path.join(OUT_DIR, DSYM_NAME)
  75. shutil.copy2(os.path.join(OUT_DIR, 'dsym.zip'), dsym_zip)
  76. upload_electron(release, dsym_zip, args)
  77. dsym_snaphot_zip = os.path.join(OUT_DIR, DSYM_SNAPSHOT_NAME)
  78. shutil.copy2(os.path.join(OUT_DIR, 'dsym-snapshot.zip'), dsym_snaphot_zip)
  79. upload_electron(release, dsym_snaphot_zip, args)
  80. elif PLATFORM == 'win32':
  81. pdb_zip = os.path.join(OUT_DIR, PDB_NAME)
  82. shutil.copy2(os.path.join(OUT_DIR, 'pdb.zip'), pdb_zip)
  83. upload_electron(release, pdb_zip, args)
  84. elif PLATFORM == 'linux':
  85. debug_zip = os.path.join(OUT_DIR, DEBUG_NAME)
  86. shutil.copy2(os.path.join(OUT_DIR, 'debug.zip'), debug_zip)
  87. upload_electron(release, debug_zip, args)
  88. # Upload libcxx_objects.zip for linux only
  89. libcxx_objects = get_zip_name('libcxx-objects', ELECTRON_VERSION)
  90. libcxx_objects_zip = os.path.join(OUT_DIR, libcxx_objects)
  91. shutil.copy2(os.path.join(OUT_DIR, 'libcxx_objects.zip'),
  92. libcxx_objects_zip)
  93. upload_electron(release, libcxx_objects_zip, args)
  94. # Upload headers.zip and abi_headers.zip as non-platform specific
  95. if get_target_arch() == "x64":
  96. cxx_headers_zip = os.path.join(OUT_DIR, 'libcxx_headers.zip')
  97. upload_electron(release, cxx_headers_zip, args)
  98. abi_headers_zip = os.path.join(OUT_DIR, 'libcxxabi_headers.zip')
  99. upload_electron(release, abi_headers_zip, args)
  100. # Upload free version of ffmpeg.
  101. ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)
  102. ffmpeg_zip = os.path.join(OUT_DIR, ffmpeg)
  103. ffmpeg_build_path = os.path.join(SRC_DIR, 'out', 'ffmpeg', 'ffmpeg.zip')
  104. shutil.copy2(ffmpeg_build_path, ffmpeg_zip)
  105. upload_electron(release, ffmpeg_zip, args)
  106. chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION)
  107. chromedriver_zip = os.path.join(OUT_DIR, chromedriver)
  108. shutil.copy2(os.path.join(OUT_DIR, 'chromedriver.zip'), chromedriver_zip)
  109. upload_electron(release, chromedriver_zip, args)
  110. mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION)
  111. mksnapshot_zip = os.path.join(OUT_DIR, mksnapshot)
  112. if get_target_arch().startswith('arm') and PLATFORM != 'darwin':
  113. # Upload the x64 binary for arm/arm64 mksnapshot
  114. mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION, 'x64')
  115. mksnapshot_zip = os.path.join(OUT_DIR, mksnapshot)
  116. shutil.copy2(os.path.join(OUT_DIR, 'mksnapshot.zip'), mksnapshot_zip)
  117. upload_electron(release, mksnapshot_zip, args)
  118. if PLATFORM == 'linux' and get_target_arch() == 'x64':
  119. # Upload the hunspell dictionaries only from the linux x64 build
  120. hunspell_dictionaries_zip = os.path.join(
  121. OUT_DIR, 'hunspell_dictionaries.zip')
  122. upload_electron(release, hunspell_dictionaries_zip, args)
  123. if not tag_exists and not args.upload_to_storage:
  124. # Upload symbols to symbol server.
  125. run_python_upload_script('upload-symbols.py')
  126. if PLATFORM == 'win32':
  127. run_python_upload_script('upload-node-headers.py', '-v', args.version)
  128. if PLATFORM == 'win32':
  129. toolchain_profile_zip = os.path.join(OUT_DIR, TOOLCHAIN_PROFILE_NAME)
  130. with ZipFile(toolchain_profile_zip, 'w') as myzip:
  131. myzip.write(
  132. os.path.join(OUT_DIR, 'windows_toolchain_profile.json'),
  133. 'toolchain_profile.json')
  134. upload_electron(release, toolchain_profile_zip, args)
  135. return 0
  136. def parse_args():
  137. parser = argparse.ArgumentParser(description='upload distribution file')
  138. parser.add_argument('-v', '--version', help='Specify the version',
  139. default=ELECTRON_VERSION)
  140. parser.add_argument('-o', '--overwrite',
  141. help='Overwrite a published release',
  142. action='store_true')
  143. parser.add_argument('-p', '--publish-release',
  144. help='Publish the release',
  145. action='store_true')
  146. parser.add_argument('-s', '--upload_to_storage',
  147. help='Upload assets to azure bucket',
  148. dest='upload_to_storage',
  149. action='store_true',
  150. default=False,
  151. required=False)
  152. parser.add_argument('--verbose',
  153. action='store_true',
  154. help='Mooooorreee logs')
  155. return parser.parse_args()
  156. def run_python_upload_script(script, *args):
  157. script_path = os.path.join(
  158. ELECTRON_DIR, 'script', 'release', 'uploaders', script)
  159. print(execute([sys.executable, script_path] + list(args)))
  160. def get_electron_build_version():
  161. if get_target_arch().startswith('arm') or 'CI' in os.environ:
  162. # In CI we just build as told.
  163. return ELECTRON_VERSION
  164. electron = get_electron_exec()
  165. return subprocess.check_output([electron, '--version']).strip()
  166. class NonZipFileError(ValueError):
  167. """Raised when a given file does not appear to be a zip"""
  168. def zero_zip_date_time(fname):
  169. """ Wrap strip-zip zero_zip_date_time within a file opening operation """
  170. try:
  171. with open(fname, 'r+b') as f:
  172. _zero_zip_date_time(f)
  173. except Exception:
  174. # pylint: disable=W0707
  175. raise NonZipFileError(fname)
  176. def _zero_zip_date_time(zip_):
  177. def purify_extra_data(mm, offset, length, compressed_size=0):
  178. extra_header_struct = Struct("<HH")
  179. # 0. id
  180. # 1. length
  181. STRIPZIP_OPTION_HEADER = 0xFFFF
  182. EXTENDED_TIME_DATA = 0x5455
  183. # Some sort of extended time data, see
  184. # ftp://ftp.info-zip.org/pub/infozip/src/zip30.zip ./proginfo/extrafld.txt
  185. # fallthrough
  186. UNIX_EXTRA_DATA = 0x7875
  187. # Unix extra data; UID / GID stuff, see
  188. # ftp://ftp.info-zip.org/pub/infozip/src/zip30.zip ./proginfo/extrafld.txt
  189. ZIP64_EXTRA_HEADER = 0x0001
  190. zip64_extra_struct = Struct("<HHQQ")
  191. # ZIP64.
  192. # When a ZIP64 extra field is present this 8byte length
  193. # will override the 4byte length defined in canonical zips.
  194. # This is in the form:
  195. # - 0x0001 (header_id)
  196. # - 0x0010 [16] (header_length)
  197. # - ... (8byte uncompressed_length)
  198. # - ... (8byte compressed_length)
  199. mlen = offset + length
  200. while offset < mlen:
  201. values = list(extra_header_struct.unpack_from(mm, offset))
  202. _, header_length = values
  203. extra_struct = Struct("<HH" + "B" * header_length)
  204. values = list(extra_struct.unpack_from(mm, offset))
  205. header_id, header_length = values[:2]
  206. if header_id in (EXTENDED_TIME_DATA, UNIX_EXTRA_DATA):
  207. values[0] = STRIPZIP_OPTION_HEADER
  208. for i in range(2, len(values)):
  209. values[i] = 0xff
  210. extra_struct.pack_into(mm, offset, *values)
  211. if header_id == ZIP64_EXTRA_HEADER:
  212. assert header_length == 16
  213. values = list(zip64_extra_struct.unpack_from(mm, offset))
  214. header_id, header_length, _, compressed_size = values
  215. offset += extra_header_struct.size + header_length
  216. return compressed_size
  217. FILE_HEADER_SIGNATURE = 0x04034b50
  218. CENDIR_HEADER_SIGNATURE = 0x02014b50
  219. archive_size = os.fstat(zip_.fileno()).st_size
  220. signature_struct = Struct("<L")
  221. local_file_header_struct = Struct("<LHHHHHLLLHH")
  222. # 0. L signature
  223. # 1. H version_needed
  224. # 2. H gp_bits
  225. # 3. H compression_method
  226. # 4. H last_mod_time
  227. # 5. H last_mod_date
  228. # 6. L crc32
  229. # 7. L compressed_size
  230. # 8. L uncompressed_size
  231. # 9. H name_length
  232. # 10. H extra_field_length
  233. central_directory_header_struct = Struct("<LHHHHHHLLLHHHHHLL")
  234. # 0. L signature
  235. # 1. H version_made_by
  236. # 2. H version_needed
  237. # 3. H gp_bits
  238. # 4. H compression_method
  239. # 5. H last_mod_time
  240. # 6. H last_mod_date
  241. # 7. L crc32
  242. # 8. L compressed_size
  243. # 9. L uncompressed_size
  244. # 10. H file_name_length
  245. # 11. H extra_field_length
  246. # 12. H file_comment_length
  247. # 13. H disk_number_start
  248. # 14. H internal_attr
  249. # 15. L external_attr
  250. # 16. L rel_offset_local_header
  251. offset = 0
  252. mm = mmap.mmap(zip_.fileno(), 0)
  253. while offset < archive_size:
  254. if signature_struct.unpack_from(mm, offset) != (FILE_HEADER_SIGNATURE,):
  255. break
  256. values = list(local_file_header_struct.unpack_from(mm, offset))
  257. compressed_size, _, name_length, extra_field_length = values[7:11]
  258. # reset last_mod_time
  259. values[4] = 0
  260. # reset last_mod_date
  261. values[5] = 0x21
  262. local_file_header_struct.pack_into(mm, offset, *values)
  263. offset += local_file_header_struct.size + name_length
  264. if extra_field_length != 0:
  265. compressed_size = purify_extra_data(mm, offset, extra_field_length,
  266. compressed_size)
  267. offset += compressed_size + extra_field_length
  268. while offset < archive_size:
  269. if signature_struct.unpack_from(mm, offset) != (CENDIR_HEADER_SIGNATURE,):
  270. break
  271. values = list(central_directory_header_struct.unpack_from(mm, offset))
  272. file_name_length, extra_field_length, file_comment_length = values[10:13]
  273. # reset last_mod_time
  274. values[5] = 0
  275. # reset last_mod_date
  276. values[6] = 0x21
  277. central_directory_header_struct.pack_into(mm, offset, *values)
  278. offset += central_directory_header_struct.size
  279. offset += file_name_length + extra_field_length + file_comment_length
  280. if extra_field_length != 0:
  281. purify_extra_data(mm, offset - extra_field_length, extra_field_length)
  282. if offset == 0:
  283. raise NonZipFileError(zip_.name)
  284. def upload_electron(release, file_path, args):
  285. filename = os.path.basename(file_path)
  286. # Strip zip non determinism before upload, in-place operation
  287. try:
  288. zero_zip_date_time(file_path)
  289. except NonZipFileError:
  290. pass
  291. # if upload_to_storage is set, skip github upload.
  292. # todo (vertedinde): migrate this variable to upload_to_storage
  293. if args.upload_to_storage:
  294. key_prefix = 'release-builds/{0}_{1}'.format(args.version,
  295. args.upload_timestamp)
  296. store_artifact(os.path.dirname(file_path), key_prefix, [file_path])
  297. upload_sha256_checksum(args.version, file_path, key_prefix)
  298. return
  299. # Upload the file.
  300. upload_io_to_github(release, filename, file_path, args.version)
  301. # Upload the checksum file.
  302. upload_sha256_checksum(args.version, file_path)
  303. def upload_io_to_github(release, filename, filepath, version):
  304. print('Uploading %s to Github' % \
  305. (filename))
  306. script_path = os.path.join(
  307. ELECTRON_DIR, 'script', 'release', 'uploaders', 'upload-to-github.ts')
  308. execute([TS_NODE, script_path, filepath, filename, str(release['id']),
  309. version])
  310. def upload_sha256_checksum(version, file_path, key_prefix=None):
  311. checksum_path = '{}.sha256sum'.format(file_path)
  312. if key_prefix is None:
  313. key_prefix = 'checksums-scratchpad/{0}'.format(version)
  314. sha256 = hashlib.sha256()
  315. with open(file_path, 'rb') as f:
  316. sha256.update(f.read())
  317. filename = os.path.basename(file_path)
  318. with open(checksum_path, 'w') as checksum:
  319. checksum.write('{} *{}'.format(sha256.hexdigest(), filename))
  320. store_artifact(os.path.dirname(checksum_path), key_prefix, [checksum_path])
  321. def get_release(version):
  322. script_path = os.path.join(
  323. ELECTRON_DIR, 'script', 'release', 'find-github-release.js')
  324. release_info = execute(['node', script_path, version])
  325. release = json.loads(release_info)
  326. return release
  327. if __name__ == '__main__':
  328. sys.exit(main())