cmakelib.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. from __future__ import print_function
  2. import sys, subprocess, json, os, select, shutil, time, socket
  3. termwidth = 150
  4. print_communication = True
  5. def ordered(obj):
  6. if isinstance(obj, dict):
  7. return sorted((k, ordered(v)) for k, v in obj.items())
  8. if isinstance(obj, list):
  9. return sorted(ordered(x) for x in obj)
  10. else:
  11. return obj
  12. def col_print(title, array):
  13. print()
  14. print()
  15. print(title)
  16. indentwidth = 4
  17. indent = " " * indentwidth
  18. if not array:
  19. print(indent + "<None>")
  20. return
  21. padwidth = 2
  22. maxitemwidth = len(max(array, key=len))
  23. numCols = max(1, int((termwidth - indentwidth + padwidth) / (maxitemwidth + padwidth)))
  24. numRows = len(array) // numCols + 1
  25. pad = " " * padwidth
  26. for index in range(numRows):
  27. print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows]))
  28. filterPacket = lambda x: x
  29. STDIN = 0
  30. PIPE = 1
  31. communicationMethods = [STDIN]
  32. if hasattr(socket, 'AF_UNIX'):
  33. communicationMethods.append(PIPE)
  34. def defaultExitWithError(proc):
  35. data = ""
  36. try:
  37. while select.select([proc.outPipe], [], [], 3.)[0]:
  38. data = data + proc.outPipe.read(1)
  39. if len(data):
  40. print("Rest of raw buffer from server:")
  41. printServer(data)
  42. except:
  43. pass
  44. proc.outPipe.close()
  45. proc.inPipe.close()
  46. proc.kill()
  47. sys.exit(1)
  48. exitWithError = lambda proc: defaultExitWithError(proc)
  49. serverTag = "SERVER"
  50. def printServer(*args):
  51. print(serverTag + ">", *args)
  52. print()
  53. sys.stdout.flush()
  54. def printClient(*args):
  55. print("CLIENT>", *args)
  56. print()
  57. sys.stdout.flush()
  58. def waitForRawMessage(cmakeCommand):
  59. stdoutdata = ""
  60. payload = ""
  61. while not cmakeCommand.poll():
  62. stdoutdataLine = cmakeCommand.outPipe.readline()
  63. if stdoutdataLine:
  64. stdoutdata += stdoutdataLine.decode('utf-8')
  65. else:
  66. break
  67. begin = stdoutdata.find('[== "CMake Server" ==[\n')
  68. end = stdoutdata.find(']== "CMake Server" ==]')
  69. if begin != -1 and end != -1:
  70. begin += len('[== "CMake Server" ==[\n')
  71. payload = stdoutdata[begin:end]
  72. jsonPayload = json.loads(payload)
  73. filteredPayload = filterPacket(jsonPayload)
  74. if print_communication and filteredPayload:
  75. printServer(filteredPayload)
  76. if filteredPayload is not None or jsonPayload is None:
  77. return jsonPayload
  78. stdoutdata = stdoutdata[(end+len(']== "CMake Server" ==]')):]
  79. # Python2 has no problem writing the output of encodes directly,
  80. # but Python3 returns only 'int's for encode and so must be turned
  81. # into bytes. We use the existence of 'to_bytes' on an int to
  82. # determine which behavior is appropriate. It might be more clear
  83. # to do this in the code which uses the flag, but introducing
  84. # this lookup cost at every byte sent isn't ideal.
  85. has_to_bytes = "to_bytes" in dir(10)
  86. def writeRawData(cmakeCommand, content):
  87. writeRawData.counter += 1
  88. payload = """
  89. [== "CMake Server" ==[
  90. %s
  91. ]== "CMake Server" ==]
  92. """ % content
  93. rn = ( writeRawData.counter % 2 ) == 0
  94. if rn:
  95. payload = payload.replace('\n', '\r\n')
  96. if print_communication:
  97. printClient(content, "(Use \\r\\n:", rn, ")")
  98. # To stress test how cmake deals with fragmentation in the
  99. # communication channel, we send only one byte at a time.
  100. # Certain communication methods / platforms might still buffer
  101. # it all into one message since its so close together, but in
  102. # general this will catch places where we assume full buffers
  103. # come in all at once.
  104. encoded_payload = payload.encode('utf-8')
  105. # Python version 3+ can't write ints directly; but 'to_bytes'
  106. # for int was only added in python 3.2. If this is a 3+ version
  107. # of python without that conversion function; just write the whole
  108. # thing out at once.
  109. if sys.version_info[0] > 2 and not has_to_bytes:
  110. cmakeCommand.write(encoded_payload)
  111. else:
  112. for c in encoded_payload:
  113. if has_to_bytes:
  114. c = c.to_bytes(1, byteorder='big')
  115. cmakeCommand.write(c)
  116. writeRawData.counter = 0
  117. def writePayload(cmakeCommand, obj):
  118. writeRawData(cmakeCommand, json.dumps(obj))
  119. def getPipeName():
  120. return "/tmp/server-test-socket"
  121. def attachPipe(cmakeCommand, pipeName):
  122. time.sleep(1)
  123. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  124. sock.connect(pipeName)
  125. global serverTag
  126. serverTag = "SERVER(PIPE)"
  127. cmakeCommand.outPipe = sock.makefile()
  128. cmakeCommand.inPipe = sock
  129. cmakeCommand.write = cmakeCommand.inPipe.sendall
  130. def writeAndFlush(pipe, val):
  131. pipe.write(val)
  132. pipe.flush()
  133. def initServerProc(cmakeCommand, comm):
  134. if comm == PIPE:
  135. pipeName = getPipeName()
  136. cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--pipe=" + pipeName])
  137. attachPipe(cmakeCommand, pipeName)
  138. else:
  139. cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"],
  140. stdin=subprocess.PIPE,
  141. stdout=subprocess.PIPE)
  142. cmakeCommand.outPipe = cmakeCommand.stdout
  143. cmakeCommand.inPipe = cmakeCommand.stdin
  144. cmakeCommand.write = lambda val: writeAndFlush(cmakeCommand.inPipe, val)
  145. packet = waitForRawMessage(cmakeCommand)
  146. if packet == None:
  147. print("Not in server mode")
  148. sys.exit(2)
  149. if packet['type'] != 'hello':
  150. print("No hello message")
  151. sys.exit(3)
  152. return cmakeCommand
  153. def exitProc(cmakeCommand):
  154. # Tell the server to exit.
  155. cmakeCommand.stdin.close()
  156. cmakeCommand.stdout.close()
  157. # Wait for the server to exit.
  158. # If this version of python supports it, terminate the server after a timeout.
  159. try:
  160. cmakeCommand.wait(timeout=5)
  161. except TypeError:
  162. cmakeCommand.wait()
  163. except:
  164. cmakeCommand.terminate()
  165. raise
  166. def waitForMessage(cmakeCommand, expected):
  167. data = ordered(expected)
  168. packet = ordered(waitForRawMessage(cmakeCommand))
  169. if packet != data:
  170. print ("Received unexpected message; test failed")
  171. exitWithError(cmakeCommand)
  172. return packet
  173. def waitForReply(cmakeCommand, originalType, cookie, skipProgress):
  174. gotResult = False
  175. while True:
  176. packet = waitForRawMessage(cmakeCommand)
  177. t = packet['type']
  178. if packet['cookie'] != cookie or packet['inReplyTo'] != originalType:
  179. print("cookie or inReplyTo mismatch")
  180. sys.exit(4)
  181. if t == 'message' or t == 'progress':
  182. if skipProgress:
  183. continue
  184. if t == 'reply':
  185. break
  186. print("Unrecognized message", packet)
  187. sys.exit(5)
  188. return packet
  189. def waitForError(cmakeCommand, originalType, cookie, message):
  190. packet = waitForRawMessage(cmakeCommand)
  191. if packet['cookie'] != cookie or packet['type'] != 'error' or packet['inReplyTo'] != originalType or packet['errorMessage'] != message:
  192. sys.exit(6)
  193. def waitForProgress(cmakeCommand, originalType, cookie, current, message):
  194. packet = waitForRawMessage(cmakeCommand)
  195. if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message:
  196. sys.exit(7)
  197. def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerator):
  198. version = { 'major': major }
  199. if minor >= 0:
  200. version['minor'] = minor
  201. writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version,
  202. 'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build,
  203. 'generator': generator, 'extraGenerator': extraGenerator })
  204. waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False)
  205. def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
  206. packet = waitForReply(cmakeCommand, 'globalSettings', '', False)
  207. capabilities = packet['capabilities']
  208. # validate version:
  209. cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--version" ], universal_newlines=True)
  210. cmakeVersion = cmakeoutput.splitlines()[0][14:]
  211. version = capabilities['version']
  212. versionString = version['string']
  213. vs = str(version['major']) + '.' + str(version['minor']) + '.' + str(version['patch'])
  214. if (versionString != vs and not versionString.startswith(vs + '-')):
  215. sys.exit(8)
  216. if (versionString != cmakeVersion):
  217. sys.exit(9)
  218. # validate generators:
  219. generatorObjects = capabilities['generators']
  220. cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--help" ], universal_newlines=True)
  221. index = cmakeoutput.index('\nGenerators\n\n')
  222. cmakeGenerators = []
  223. for line in cmakeoutput[index + 12:].splitlines():
  224. if not line.startswith(' '):
  225. continue
  226. if line.startswith(' '):
  227. continue
  228. equalPos = line.find('=')
  229. tmp = ''
  230. if (equalPos > 0):
  231. tmp = line[2:equalPos].strip()
  232. else:
  233. tmp = line.strip()
  234. if tmp.endswith(" [arch]"):
  235. tmp = tmp[0:len(tmp) - 7]
  236. if (len(tmp) > 0) and (" - " not in tmp):
  237. cmakeGenerators.append(tmp)
  238. generators = []
  239. for genObj in generatorObjects:
  240. generators.append(genObj['name'])
  241. generators.sort()
  242. cmakeGenerators.sort()
  243. for gen in cmakeGenerators:
  244. if (not gen in generators):
  245. sys.exit(10)
  246. gen = packet['generator']
  247. if (gen != '' and not (gen in generators)):
  248. sys.exit(11)
  249. for i in data:
  250. print("Validating", i)
  251. if (packet[i] != data[i]):
  252. sys.exit(12)
  253. def validateCache(cmakeCommand, data):
  254. packet = waitForReply(cmakeCommand, 'cache', '', False)
  255. cache = packet['cache']
  256. if (data['isEmpty']):
  257. if (cache != []):
  258. print('Expected empty cache, but got data.\n')
  259. sys.exit(1)
  260. return;
  261. if (cache == []):
  262. print('Expected cache contents, but got none.\n')
  263. sys.exit(1)
  264. hadHomeDir = False
  265. for value in cache:
  266. if (value['key'] == 'CMAKE_HOME_DIRECTORY'):
  267. hadHomeDir = True
  268. if (not hadHomeDir):
  269. print('No CMAKE_HOME_DIRECTORY found in cache.')
  270. sys.exit(1)
  271. def handleBasicMessage(proc, obj, debug):
  272. if 'sendRaw' in obj:
  273. data = obj['sendRaw']
  274. if debug: print("Sending raw:", data)
  275. writeRawData(proc, data)
  276. return True
  277. elif 'send' in obj:
  278. data = obj['send']
  279. if debug: print("Sending:", json.dumps(data))
  280. writePayload(proc, data)
  281. return True
  282. elif 'recv' in obj:
  283. data = obj['recv']
  284. if debug: print("Waiting for:", json.dumps(data))
  285. waitForMessage(proc, data)
  286. return True
  287. elif 'message' in obj:
  288. print("MESSAGE:", obj["message"])
  289. sys.stdout.flush()
  290. return True
  291. return False
  292. def shutdownProc(proc):
  293. # Tell the server to exit.
  294. proc.inPipe.close()
  295. proc.outPipe.close()
  296. # Wait for the server to exit.
  297. # If this version of python supports it, terminate the server after a timeout.
  298. try:
  299. proc.wait(timeout=5)
  300. except TypeError:
  301. proc.wait()
  302. except:
  303. proc.terminate()
  304. raise
  305. print('cmake-server exited: %d' % proc.returncode)
  306. sys.exit(proc.returncode)