run_msvc_wine.py 15 KB

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