run_msvc_wine.py 15 KB

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