run_msvc_wine.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. from __future__ import print_function
  2. import sys
  3. import os
  4. import re
  5. import subprocess
  6. import signal
  7. import time
  8. import json
  9. import argparse
  10. import errno
  11. import process_command_files as pcf
  12. import process_whole_archive_option as pwa
  13. procs = []
  14. build_kekeke = 45
  15. def stringize(s):
  16. return s.encode('utf-8') if isinstance(s, unicode) else s
  17. def run_subprocess(*args, **kwargs):
  18. if 'env' in kwargs:
  19. kwargs['env'] = {stringize(k): stringize(v) for k, v in kwargs['env'].iteritems()}
  20. p = subprocess.Popen(*args, **kwargs)
  21. procs.append(p)
  22. return p
  23. def run_subprocess_with_timeout(timeout, args):
  24. attempts_remaining = 5
  25. delay = 1
  26. p = None
  27. while True:
  28. try:
  29. p = run_subprocess(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  30. stdout, stderr = p.communicate(timeout=timeout)
  31. return p, stdout, stderr
  32. except subprocess.TimeoutExpired as e:
  33. print('timeout running {0}, error {1}, delay {2} seconds'.format(args, str(e), delay), file=sys.stderr)
  34. if p is not None:
  35. try:
  36. p.kill()
  37. p.wait(timeout=1)
  38. except Exception:
  39. pass
  40. attempts_remaining -= 1
  41. if attempts_remaining == 0:
  42. raise
  43. time.sleep(delay)
  44. delay = min(2 * delay, 4)
  45. def terminate_slaves():
  46. for p in procs:
  47. try:
  48. p.terminate()
  49. except Exception:
  50. pass
  51. def sig_term(sig, fr):
  52. terminate_slaves()
  53. sys.exit(sig)
  54. def subst_path(l):
  55. if len(l) > 3:
  56. if l[:3].lower() in ('z:\\', 'z:/'):
  57. return l[2:].replace('\\', '/')
  58. return l
  59. def call_wine_cmd_once(wine, cmd, env, mode):
  60. p = run_subprocess(
  61. wine + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, close_fds=True, shell=False
  62. )
  63. output = find_cmd_out(cmd)
  64. error = None
  65. if output is not None and os.path.exists(output):
  66. try:
  67. os.remove(output)
  68. except OSError as e:
  69. if e.errno != errno.ENOENT:
  70. error = e
  71. except Exception as e:
  72. error = e
  73. if error is not None:
  74. print('Output {} already exists and we have failed to remove it: {}'.format(output, error), file=sys.stderr)
  75. # print >>sys.stderr, cmd, env, wine
  76. stdout_and_stderr, _ = p.communicate()
  77. return_code = p.returncode
  78. if not stdout_and_stderr:
  79. if return_code != 0:
  80. raise Exception('wine did something strange')
  81. return return_code
  82. elif ' : fatal error ' in stdout_and_stderr:
  83. return_code = 1
  84. elif ' : error ' in stdout_and_stderr:
  85. return_code = 2
  86. lines = [x.strip() for x in stdout_and_stderr.split('\n')]
  87. prefixes = [
  88. 'Microsoft (R)',
  89. 'Copyright (C)',
  90. 'Application tried to create a window',
  91. 'The graphics driver is missing',
  92. 'Could not load wine-gecko',
  93. 'wine: configuration in',
  94. 'wine: created the configuration directory',
  95. 'libpng warning:',
  96. ]
  97. suffixes = [
  98. '.c',
  99. '.cxx',
  100. '.cc',
  101. '.cpp',
  102. '.masm',
  103. ]
  104. substrs = [
  105. 'Creating library Z:',
  106. 'err:heap',
  107. 'err:menubuilder:',
  108. 'err:msvcrt',
  109. 'err:ole:',
  110. 'err:wincodecs:',
  111. 'err:winediag:',
  112. ]
  113. def good_line(l):
  114. for x in prefixes:
  115. if l.startswith(x):
  116. return False
  117. for x in suffixes:
  118. if l.endswith(x):
  119. return False
  120. for x in substrs:
  121. if x in l:
  122. return False
  123. return True
  124. def filter_lines():
  125. for l in lines:
  126. if good_line(l):
  127. yield subst_path(l.strip())
  128. stdout_and_stderr = '\n'.join(filter_lines()).strip()
  129. if stdout_and_stderr:
  130. print(stdout_and_stderr, file=sys.stderr)
  131. return return_code
  132. def prepare_vc(fr, to):
  133. for p in os.listdir(fr):
  134. fr_p = os.path.join(fr, p)
  135. to_p = os.path.join(to, p)
  136. if not os.path.exists(to_p):
  137. print('install %s -> %s' % (fr_p, to_p), file=sys.stderr)
  138. os.link(fr_p, to_p)
  139. def run_slave():
  140. args = json.loads(sys.argv[3])
  141. wine = sys.argv[1]
  142. signal.signal(signal.SIGTERM, sig_term)
  143. if args.get('tout', None):
  144. signal.signal(signal.SIGALRM, sig_term)
  145. signal.alarm(args['tout'])
  146. tout = 0.1
  147. while True:
  148. try:
  149. return call_wine_cmd_once([wine], args['cmd'], args['env'], args['mode'])
  150. except Exception as e:
  151. print('%s, will retry in %s' % (str(e), tout), file=sys.stderr)
  152. time.sleep(tout)
  153. tout = min(2 * tout, 4)
  154. def find_cmd_out(args):
  155. for arg in args:
  156. if arg.startswith('/Fo'):
  157. return arg[3:]
  158. if arg.startswith('/OUT:'):
  159. return arg[5:]
  160. def calc_zero_cnt(data):
  161. zero_cnt = 0
  162. for ch in data:
  163. if ch == chr(0):
  164. zero_cnt += 1
  165. return zero_cnt
  166. def is_good_file(p):
  167. if not os.path.isfile(p):
  168. return False
  169. if os.path.getsize(p) < 300:
  170. return False
  171. asm_pattern = re.compile(r'asm(\.\w+)?\.obj$')
  172. if asm_pattern.search(p):
  173. pass
  174. elif p.endswith('.obj'):
  175. with open(p, 'rb') as f:
  176. prefix = f.read(200)
  177. if ord(prefix[0]) != 0:
  178. return False
  179. if ord(prefix[1]) != 0:
  180. return False
  181. if ord(prefix[2]) != 0xFF:
  182. return False
  183. if ord(prefix[3]) != 0xFF:
  184. return False
  185. if calc_zero_cnt(prefix) > 195:
  186. return False
  187. f.seek(-100, os.SEEK_END)
  188. last = f.read(100)
  189. if calc_zero_cnt(last) > 95:
  190. return False
  191. if last[-1] != chr(0):
  192. return False
  193. elif p.endswith('.lib'):
  194. with open(p, 'rb') as f:
  195. if f.read(7) != '!<arch>':
  196. return False
  197. return True
  198. RED = '\x1b[31;1m'
  199. GRAY = '\x1b[30;1m'
  200. RST = '\x1b[0m'
  201. MGT = '\x1b[35m'
  202. YEL = '\x1b[33m'
  203. GRN = '\x1b[32m'
  204. CYA = '\x1b[36m'
  205. def colorize_strings(l):
  206. p = l.find("'")
  207. if p >= 0:
  208. yield l[:p]
  209. l = l[p + 1 :]
  210. p = l.find("'")
  211. if p >= 0:
  212. yield CYA + "'" + subst_path(l[:p]) + "'" + RST
  213. for x in colorize_strings(l[p + 1 :]):
  214. yield x
  215. else:
  216. yield "'" + l
  217. else:
  218. yield l
  219. def colorize_line(l):
  220. lll = l
  221. try:
  222. parts = []
  223. if l.startswith('(compiler file'):
  224. return ''.join(colorize_strings(l))
  225. if l.startswith('/'):
  226. p = l.find('(')
  227. parts.append(GRAY + l[:p] + RST)
  228. l = l[p:]
  229. if l and l.startswith('('):
  230. p = l.find(')')
  231. parts.append(':' + MGT + l[1:p] + RST)
  232. l = l[p + 1 :]
  233. if l:
  234. if l.startswith(' : '):
  235. l = l[1:]
  236. if l.startswith(': error'):
  237. parts.append(': ' + RED + 'error' + RST)
  238. l = l[7:]
  239. elif l.startswith(': warning'):
  240. parts.append(': ' + YEL + 'warning' + RST)
  241. l = l[9:]
  242. elif l.startswith(': note'):
  243. parts.append(': ' + GRN + 'note' + RST)
  244. l = l[6:]
  245. elif l.startswith('fatal error'):
  246. parts.append(RED + 'fatal error' + RST)
  247. l = l[11:]
  248. if l:
  249. parts.extend(colorize_strings(l))
  250. return ''.join(parts)
  251. except Exception:
  252. return lll
  253. def colorize(out):
  254. return '\n'.join(colorize_line(l) for l in out.split('\n'))
  255. def trim_path(path, winepath):
  256. p1, p1_stdout, p1_stderr = run_subprocess_with_timeout(60, [winepath, '-w', path])
  257. win_path = p1_stdout.strip()
  258. if p1.returncode != 0 or not win_path:
  259. # Fall back to only winepath -s
  260. win_path = path
  261. p2, p2_stdout, p2_stderr = run_subprocess_with_timeout(60, [winepath, '-s', win_path])
  262. short_path = p2_stdout.strip()
  263. check_path = short_path
  264. if check_path.startswith(('Z:', 'z:')):
  265. check_path = check_path[2:]
  266. if not check_path[1:].startswith((path[1:4], path[1:4].upper())):
  267. raise Exception(
  268. 'Cannot trim path {}; 1st winepath exit code: {}, stdout:\n{}\n stderr:\n{}\n 2nd winepath exit code: {}, stdout:\n{}\n stderr:\n{}'.format(
  269. path, p1.returncode, p1_stdout, p1_stderr, p2.returncode, p2_stdout, p2_stderr
  270. )
  271. )
  272. return short_path
  273. def downsize_path(path, short_names):
  274. flag = ''
  275. if path.startswith('/Fo'):
  276. flag = '/Fo'
  277. path = path[3:]
  278. for full_name, short_name in short_names.items():
  279. if path.startswith(full_name):
  280. path = path.replace(full_name, short_name)
  281. return flag + path
  282. def make_full_path_arg(arg, bld_root, short_root):
  283. if arg[0] != '/' and len(os.path.join(bld_root, arg)) > 250:
  284. return os.path.join(short_root, arg)
  285. return arg
  286. def fix_path(p):
  287. topdirs = ['/%s/' % d for d in os.listdir('/')]
  288. def abs_path_start(path, pos):
  289. if pos < 0:
  290. return False
  291. return pos == 0 or path[pos - 1] == ':'
  292. pp = None
  293. for pr in topdirs:
  294. pp2 = p.find(pr)
  295. if abs_path_start(p, pp2) and (pp is None or pp > pp2):
  296. pp = pp2
  297. if pp is not None:
  298. return p[:pp] + 'Z:' + p[pp:].replace('/', '\\')
  299. if p.startswith('/Fo'):
  300. return '/Fo' + p[3:].replace('/', '\\')
  301. return p
  302. def process_free_args(args, wine, bld_root, mode):
  303. whole_archive_prefix = '/WHOLEARCHIVE:'
  304. short_names = {}
  305. winepath = os.path.join(os.path.dirname(wine), 'winepath')
  306. short_names[bld_root] = trim_path(bld_root, winepath)
  307. # Slow for no benefit.
  308. # arc_root = args.arcadia_root
  309. # short_names[arc_root] = trim_path(arc_root, winepath)
  310. free_args, wa_peers, wa_libs = pwa.get_whole_archive_peers_and_libs(pcf.skip_markers(args))
  311. process_link = lambda x: make_full_path_arg(x, bld_root, short_names[bld_root]) if mode in ('link', 'lib') else x
  312. def process_arg(arg):
  313. with_wa_prefix = arg.startswith(whole_archive_prefix)
  314. prefix = whole_archive_prefix if with_wa_prefix else ''
  315. without_prefix_arg = arg[len(prefix) :]
  316. return prefix + fix_path(process_link(downsize_path(without_prefix_arg, short_names)))
  317. result = []
  318. for arg in free_args:
  319. if pcf.is_cmdfile_arg(arg):
  320. cmd_file_path = pcf.cmdfile_path(arg)
  321. cf_args = pcf.read_from_command_file(cmd_file_path)
  322. with open(cmd_file_path, 'w') as afile:
  323. for cf_arg in cf_args:
  324. afile.write(process_arg(cf_arg) + "\n")
  325. result.append(arg)
  326. else:
  327. result.append(process_arg(arg))
  328. return pwa.ProcessWholeArchiveOption('WINDOWS', wa_peers, wa_libs).construct_cmd(result)
  329. def run_main():
  330. parser = argparse.ArgumentParser()
  331. parser.add_argument('wine', action='store')
  332. parser.add_argument('-v', action='store', dest='version', default='120')
  333. parser.add_argument('-I', action='append', dest='incl_paths')
  334. parser.add_argument('mode', action='store')
  335. parser.add_argument('arcadia_root', action='store')
  336. parser.add_argument('arcadia_build_root', action='store')
  337. parser.add_argument('binary', action='store')
  338. parser.add_argument('free_args', nargs=argparse.REMAINDER)
  339. # By now just unpack. Ideally we should fix path and pack arguments back into command file
  340. args = parser.parse_args()
  341. wine = args.wine
  342. mode = args.mode
  343. binary = args.binary
  344. version = args.version
  345. incl_paths = args.incl_paths
  346. bld_root = args.arcadia_build_root
  347. free_args = args.free_args
  348. wine_dir = os.path.dirname(os.path.dirname(wine))
  349. bin_dir = os.path.dirname(binary)
  350. tc_dir = os.path.dirname(os.path.dirname(os.path.dirname(bin_dir)))
  351. if not incl_paths:
  352. incl_paths = [tc_dir + '/VC/include', tc_dir + '/include']
  353. cmd_out = find_cmd_out(free_args)
  354. env = os.environ.copy()
  355. env.pop('DISPLAY', None)
  356. env['WINEDLLOVERRIDES'] = 'msvcr{}=n'.format(version)
  357. env['WINEDEBUG'] = 'fixme-all'
  358. env['INCLUDE'] = ';'.join(fix_path(p) for p in incl_paths)
  359. env['VSINSTALLDIR'] = fix_path(tc_dir)
  360. env['VCINSTALLDIR'] = fix_path(tc_dir + '/VC')
  361. env['WindowsSdkDir'] = fix_path(tc_dir)
  362. env['LIBPATH'] = fix_path(tc_dir + '/VC/lib/amd64')
  363. env['LIB'] = fix_path(tc_dir + '/VC/lib/amd64')
  364. env['LD_LIBRARY_PATH'] = ':'.join(wine_dir + d for d in ['/lib', '/lib64', '/lib64/wine'])
  365. cmd = [binary] + process_free_args(free_args, wine, bld_root, mode)
  366. for x in ('/NOLOGO', '/nologo', '/FD'):
  367. try:
  368. cmd.remove(x)
  369. except ValueError:
  370. pass
  371. def run_process(sleep, tout):
  372. if sleep:
  373. time.sleep(sleep)
  374. args = {'cmd': cmd, 'env': env, 'mode': mode, 'tout': tout}
  375. slave_cmd = [sys.executable, sys.argv[0], wine, 'slave', json.dumps(args)]
  376. p = run_subprocess(slave_cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=False)
  377. out, _ = p.communicate()
  378. return p.wait(), out
  379. def print_err_log(log):
  380. if not log:
  381. return
  382. if mode == 'cxx':
  383. log = colorize(log)
  384. print(log, file=sys.stderr)
  385. tout = 200
  386. while True:
  387. rc, out = run_process(0, tout)
  388. if rc in (-signal.SIGALRM, signal.SIGALRM):
  389. print_err_log(out)
  390. print('##append_tag##time out', file=sys.stderr)
  391. elif out and ' stack overflow ' in out:
  392. print('##append_tag##stack overflow', file=sys.stderr)
  393. elif out and 'recvmsg: Connection reset by peer' in out:
  394. print('##append_tag##wine gone', file=sys.stderr)
  395. elif out and 'D8037' in out:
  396. print('##append_tag##repair wine', file=sys.stderr)
  397. try:
  398. os.unlink(os.path.join(os.environ['WINEPREFIX'], '.update-timestamp'))
  399. except Exception as e:
  400. print(e, file=sys.stderr)
  401. else:
  402. print_err_log(out)
  403. # non-zero return code - bad, return it immediately
  404. if rc:
  405. print('##win_cmd##' + ' '.join(cmd), file=sys.stderr)
  406. print('##args##' + ' '.join(free_args), file=sys.stderr)
  407. return rc
  408. # check for output existence(if we expect it!) and real length
  409. if cmd_out:
  410. if is_good_file(cmd_out):
  411. return 0
  412. else:
  413. # retry!
  414. print('##append_tag##no output', file=sys.stderr)
  415. else:
  416. return 0
  417. tout *= 3
  418. def main():
  419. prefix_suffix = os.environ.pop('WINEPREFIX_SUFFIX', None)
  420. if prefix_suffix is not None:
  421. prefix = os.environ.pop('WINEPREFIX', None)
  422. if prefix is not None:
  423. os.environ['WINEPREFIX'] = os.path.join(prefix, prefix_suffix)
  424. # just in case
  425. signal.alarm(2000)
  426. if sys.argv[2] == 'slave':
  427. func = run_slave
  428. else:
  429. func = run_main
  430. try:
  431. try:
  432. sys.exit(func())
  433. finally:
  434. terminate_slaves()
  435. except KeyboardInterrupt:
  436. sys.exit(4)
  437. except Exception as e:
  438. print(str(e), file=sys.stderr)
  439. sys.exit(3)
  440. if __name__ == '__main__':
  441. main()