create-dist.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. #!/usr/bin/env python
  2. import argparse
  3. import glob
  4. import os
  5. import re
  6. import shutil
  7. import subprocess
  8. import sys
  9. import stat
  10. if sys.platform == "win32":
  11. import _winreg
  12. from lib.config import BASE_URL, PLATFORM, enable_verbose_mode, \
  13. get_target_arch, get_zip_name, build_env
  14. from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \
  15. execute, electron_gyp, electron_features, parse_version
  16. from lib.env_util import get_vs_location
  17. ELECTRON_VERSION = get_electron_version()
  18. SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
  19. DIST_DIR = os.path.join(SOURCE_ROOT, 'dist')
  20. OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'R')
  21. CHROMIUM_DIR = os.path.join(SOURCE_ROOT, 'vendor', 'download',
  22. 'libchromiumcontent', 'static_library')
  23. NATIVE_MKSNAPSHOT_DIR = os.path.join(SOURCE_ROOT, 'vendor', 'native_mksnapshot')
  24. PROJECT_NAME = electron_gyp()['project_name%']
  25. PRODUCT_NAME = electron_gyp()['product_name%']
  26. PDF_VIEWER_ENABLED = electron_features()['enable_pdf_viewer%']
  27. TARGET_BINARIES = {
  28. 'darwin': [
  29. ],
  30. 'win32': [
  31. '{0}.exe'.format(PROJECT_NAME), # 'electron.exe'
  32. 'content_shell.pak',
  33. 'd3dcompiler_47.dll',
  34. 'icudtl.dat',
  35. 'libEGL.dll',
  36. 'libGLESv2.dll',
  37. 'ffmpeg.dll',
  38. 'node.dll',
  39. 'blink_image_resources_200_percent.pak',
  40. 'content_resources_200_percent.pak',
  41. 'ui_resources_200_percent.pak',
  42. 'views_resources_200_percent.pak',
  43. 'natives_blob.bin',
  44. 'v8_context_snapshot.bin',
  45. ],
  46. 'linux': [
  47. PROJECT_NAME, # 'electron'
  48. 'content_shell.pak',
  49. 'icudtl.dat',
  50. 'libffmpeg.so',
  51. 'libnode.so',
  52. 'blink_image_resources_200_percent.pak',
  53. 'content_resources_200_percent.pak',
  54. 'ui_resources_200_percent.pak',
  55. 'views_resources_200_percent.pak',
  56. 'natives_blob.bin',
  57. 'v8_context_snapshot.bin',
  58. ],
  59. }
  60. TARGET_BINARIES_EXT = []
  61. TARGET_DIRECTORIES = {
  62. 'darwin': [
  63. '{0}.app'.format(PRODUCT_NAME),
  64. ],
  65. 'win32': [
  66. 'resources',
  67. 'locales',
  68. ],
  69. 'linux': [
  70. 'resources',
  71. 'locales',
  72. ],
  73. }
  74. V8_SNAPSHOT_BINARIES = {
  75. 'darwin': [
  76. 'libffmpeg.dylib',
  77. 'libicui18n.dylib',
  78. 'libicuuc.dylib',
  79. 'libv8.dylib',
  80. 'v8_context_snapshot_generator',
  81. 'natives_blob.bin',
  82. 'snapshot_blob.bin',
  83. ],
  84. 'linux': [
  85. 'libffmpeg.so',
  86. 'libicui18n.so',
  87. 'libicuuc.so',
  88. 'libv8.so',
  89. 'v8_context_snapshot_generator',
  90. 'natives_blob.bin',
  91. 'snapshot_blob.bin',
  92. ],
  93. 'win32': [
  94. 'ffmpeg.dll',
  95. 'icui18n.dll',
  96. 'icuuc.dll',
  97. 'v8.dll',
  98. 'v8_context_snapshot_generator.exe',
  99. 'natives_blob.bin',
  100. 'snapshot_blob.bin',
  101. ],
  102. }
  103. def main():
  104. args = parse_args()
  105. if args.chromium_dir:
  106. globals().update(CHROMIUM_DIR=args.chromium_dir)
  107. if args.verbose:
  108. enable_verbose_mode()
  109. rm_rf(DIST_DIR)
  110. os.makedirs(DIST_DIR)
  111. force_build()
  112. create_symbols()
  113. copy_binaries()
  114. copy_chrome_binary('chromedriver', CHROMIUM_DIR, DIST_DIR)
  115. copy_chrome_binary('mksnapshot', CHROMIUM_DIR, DIST_DIR)
  116. copy_license()
  117. if PLATFORM == 'win32':
  118. copy_vcruntime_binaries()
  119. copy_ucrt_binaries()
  120. if PLATFORM != 'win32' and not args.no_api_docs:
  121. create_api_json_schema()
  122. create_typescript_definitions()
  123. if PLATFORM == 'linux':
  124. strip_binaries()
  125. create_version()
  126. create_dist_zip()
  127. create_chrome_binary_zip('chromedriver', ELECTRON_VERSION)
  128. create_chrome_binary_zip('mksnapshot', ELECTRON_VERSION)
  129. create_ffmpeg_zip()
  130. create_symbols_zip()
  131. def force_build():
  132. build = os.path.join(SOURCE_ROOT, 'script', 'build.py')
  133. execute([sys.executable, build, '-c', 'Release'])
  134. def copy_binaries():
  135. for binary in TARGET_BINARIES[PLATFORM]:
  136. shutil.copy2(os.path.join(OUT_DIR, binary), DIST_DIR)
  137. if PLATFORM != 'darwin' and PDF_VIEWER_ENABLED:
  138. shutil.copy2(os.path.join(OUT_DIR, 'pdf_viewer_resources.pak'),
  139. DIST_DIR)
  140. for directory in TARGET_DIRECTORIES[PLATFORM]:
  141. shutil.copytree(os.path.join(OUT_DIR, directory),
  142. os.path.join(DIST_DIR, directory),
  143. symlinks=True)
  144. def copy_chrome_binary(binary, src_dir, dest_dir, is_native_mksnapshot = False):
  145. if PLATFORM == 'win32':
  146. binary += '.exe'
  147. src = os.path.join(src_dir, binary)
  148. dest = os.path.join(dest_dir, binary)
  149. # Copy file and keep the executable bit.
  150. shutil.copyfile(src, dest)
  151. os.chmod(dest, os.stat(dest).st_mode | stat.S_IEXEC)
  152. if binary.startswith('mksnapshot') and not is_native_mksnapshot:
  153. snapshot_gen_path = os.path.join(src_dir, 'snapshot_gen', '*')
  154. snapshot_gen_files = glob.glob(snapshot_gen_path)
  155. snapshot_gen_files += [ os.path.join(src_dir, get_ffmpeg_name()) ]
  156. for gen_file in snapshot_gen_files:
  157. shutil.copy2(gen_file, dest_dir)
  158. def copy_vcruntime_binaries():
  159. arch = get_target_arch()
  160. if arch == "ia32":
  161. arch = "x86"
  162. subkey = r"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\\"
  163. with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey + arch, 0,
  164. _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key:
  165. runtime_version = _winreg.QueryValueEx(key, "Version")[0][1:]
  166. version_parts = parse_version(runtime_version)
  167. if len(version_parts) > 3:
  168. runtime_version = '.'.join(version_parts[0:3])
  169. vs_location = get_vs_location('[15.0,16.0)')
  170. crt_dir = os.path.join(vs_location, 'VC', 'Redist', 'MSVC', runtime_version,
  171. arch, 'Microsoft.VC141.CRT')
  172. dlls = ["msvcp140.dll", "vcruntime140.dll"]
  173. # Note: copyfile is used to remove the read-only flag
  174. for dll in dlls:
  175. shutil.copyfile(os.path.join(crt_dir, dll), os.path.join(DIST_DIR, dll))
  176. TARGET_BINARIES_EXT.append(dll)
  177. def copy_ucrt_binaries():
  178. with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,
  179. r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"
  180. ) as key:
  181. ucrt_dir = _winreg.QueryValueEx(key, "KitsRoot10")[0]
  182. arch = get_target_arch()
  183. if arch == "ia32":
  184. arch = "x86"
  185. ucrt_dir += r"Redist\ucrt\DLLs\{0}".format(arch)
  186. dlls = glob.glob(os.path.join(ucrt_dir, '*.dll'))
  187. if len(dlls) == 0:
  188. raise Exception('UCRT files not found')
  189. for dll in dlls:
  190. shutil.copy2(dll, DIST_DIR)
  191. TARGET_BINARIES_EXT.append(os.path.basename(dll))
  192. def copy_license():
  193. shutil.copy2(os.path.join(CHROMIUM_DIR, '..', 'LICENSES.chromium.html'),
  194. DIST_DIR)
  195. shutil.copy2(os.path.join(SOURCE_ROOT, 'LICENSE'), DIST_DIR)
  196. def create_api_json_schema():
  197. node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin')
  198. env = os.environ.copy()
  199. env['PATH'] = os.path.pathsep.join([node_bin_dir, env['PATH']])
  200. outfile = os.path.relpath(os.path.join(DIST_DIR, 'electron-api.json'))
  201. execute(['electron-docs-linter', 'docs', '--outfile={0}'.format(outfile),
  202. '--version={}'.format(ELECTRON_VERSION.replace('v', ''))],
  203. env=env)
  204. def create_typescript_definitions():
  205. node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin')
  206. env = os.environ.copy()
  207. env['PATH'] = os.path.pathsep.join([node_bin_dir, env['PATH']])
  208. infile = os.path.relpath(os.path.join(DIST_DIR, 'electron-api.json'))
  209. outfile = os.path.relpath(os.path.join(DIST_DIR, 'electron.d.ts'))
  210. execute(['electron-typescript-definitions', '--in={0}'.format(infile),
  211. '--out={0}'.format(outfile)], env=env)
  212. def strip_binaries():
  213. for binary in TARGET_BINARIES[PLATFORM]:
  214. if binary.endswith('.so') or '.' not in binary:
  215. strip_binary(os.path.join(DIST_DIR, binary))
  216. def strip_binary(binary_path):
  217. if get_target_arch() == 'arm':
  218. strip = 'arm-linux-gnueabihf-strip'
  219. elif get_target_arch() == 'arm64':
  220. strip = 'aarch64-linux-gnu-strip'
  221. elif get_target_arch() == 'mips64el':
  222. strip = 'mips64el-redhat-linux-strip'
  223. else:
  224. strip = 'strip'
  225. execute([strip, binary_path], env=build_env())
  226. def create_version():
  227. version_path = os.path.join(SOURCE_ROOT, 'dist', 'version')
  228. with open(version_path, 'w') as version_file:
  229. version_file.write(ELECTRON_VERSION)
  230. def create_symbols():
  231. if get_target_arch() == 'mips64el':
  232. return
  233. destination = os.path.join(DIST_DIR, '{0}.breakpad.syms'.format(PROJECT_NAME))
  234. dump_symbols = os.path.join(SOURCE_ROOT, 'script', 'dump-symbols.py')
  235. execute([sys.executable, dump_symbols, destination])
  236. if PLATFORM == 'darwin':
  237. dsyms = glob.glob(os.path.join(OUT_DIR, '*.dSYM'))
  238. for dsym in dsyms:
  239. shutil.copytree(dsym, os.path.join(DIST_DIR, os.path.basename(dsym)))
  240. elif PLATFORM == 'win32':
  241. pdbs = glob.glob(os.path.join(OUT_DIR, '*.pdb'))
  242. for pdb in pdbs:
  243. shutil.copy2(pdb, DIST_DIR)
  244. def create_dist_zip():
  245. dist_name = get_zip_name(PROJECT_NAME, ELECTRON_VERSION)
  246. zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name)
  247. with scoped_cwd(DIST_DIR):
  248. files = TARGET_BINARIES[PLATFORM] + TARGET_BINARIES_EXT + ['LICENSE',
  249. 'LICENSES.chromium.html', 'version']
  250. if PLATFORM != 'darwin' and PDF_VIEWER_ENABLED:
  251. files += ['pdf_viewer_resources.pak']
  252. dirs = TARGET_DIRECTORIES[PLATFORM]
  253. make_zip(zip_file, files, dirs)
  254. def create_chrome_binary_zip(binary, version):
  255. files = ['LICENSE', 'LICENSES.chromium.html']
  256. if PLATFORM == 'win32':
  257. files += [binary + '.exe']
  258. else:
  259. files += [binary]
  260. file_suffix = ''
  261. create_native_mksnapshot = False
  262. if binary == 'mksnapshot':
  263. files += V8_SNAPSHOT_BINARIES[PLATFORM]
  264. arch = get_target_arch()
  265. if arch.startswith('arm'):
  266. # if the arch is arm/arm64 the mksnapshot executable is an x64 binary,
  267. # so name it as such.
  268. file_suffix = 'x64'
  269. create_native_mksnapshot = True
  270. dist_name = get_zip_name(binary, version, file_suffix)
  271. zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name)
  272. with scoped_cwd(DIST_DIR):
  273. make_zip(zip_file, files, [])
  274. if create_native_mksnapshot == True:
  275. # Create a zip with the native version of the mksnapshot binary.
  276. copy_chrome_binary('mksnapshot', NATIVE_MKSNAPSHOT_DIR, DIST_DIR, True)
  277. copy_chrome_binary('v8_context_snapshot_generator', NATIVE_MKSNAPSHOT_DIR, \
  278. DIST_DIR)
  279. dist_name = get_zip_name(binary, version)
  280. zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name)
  281. with scoped_cwd(DIST_DIR):
  282. make_zip(zip_file, files, [])
  283. def create_ffmpeg_zip():
  284. dist_name = get_zip_name('ffmpeg', ELECTRON_VERSION)
  285. zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name)
  286. ffmpeg_name = get_ffmpeg_name()
  287. shutil.copy2(os.path.join(CHROMIUM_DIR, '..', 'ffmpeg', ffmpeg_name),
  288. DIST_DIR)
  289. if PLATFORM == 'linux':
  290. strip_binary(os.path.join(DIST_DIR, ffmpeg_name))
  291. with scoped_cwd(DIST_DIR):
  292. make_zip(zip_file, [ffmpeg_name, 'LICENSE', 'LICENSES.chromium.html'], [])
  293. def get_ffmpeg_name():
  294. if PLATFORM == 'darwin':
  295. ffmpeg_name = 'libffmpeg.dylib'
  296. elif PLATFORM == 'linux':
  297. ffmpeg_name = 'libffmpeg.so'
  298. elif PLATFORM == 'win32':
  299. ffmpeg_name = 'ffmpeg.dll'
  300. return ffmpeg_name
  301. def create_symbols_zip():
  302. if get_target_arch() == 'mips64el':
  303. return
  304. dist_name = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'symbols')
  305. zip_file = os.path.join(DIST_DIR, dist_name)
  306. licenses = ['LICENSE', 'LICENSES.chromium.html', 'version']
  307. with scoped_cwd(DIST_DIR):
  308. dirs = ['{0}.breakpad.syms'.format(PROJECT_NAME)]
  309. make_zip(zip_file, licenses, dirs)
  310. if PLATFORM == 'darwin':
  311. dsym_name = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'dsym')
  312. with scoped_cwd(DIST_DIR):
  313. dsyms = glob.glob('*.dSYM')
  314. make_zip(os.path.join(DIST_DIR, dsym_name), licenses, dsyms)
  315. elif PLATFORM == 'win32':
  316. pdb_name = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
  317. with scoped_cwd(DIST_DIR):
  318. pdbs = glob.glob('*.pdb')
  319. make_zip(os.path.join(DIST_DIR, pdb_name), pdbs + licenses, [])
  320. def parse_args():
  321. parser = argparse.ArgumentParser(description='Create Electron Distribution')
  322. parser.add_argument('--no_api_docs',
  323. action='store_true',
  324. help='Skip generating the Electron API Documentation!')
  325. parser.add_argument('--chromium_dir',
  326. help='Specify a custom libchromiumcontent dist directory '
  327. + 'if manually compiled')
  328. parser.add_argument('-v', '--verbose',
  329. action='store_true',
  330. help='Prints the output of the subprocesses')
  331. return parser.parse_args()
  332. if __name__ == '__main__':
  333. sys.exit(main())