run_msvc_wine.py 15 KB

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