| from __future__ import print_function |
| import sys, subprocess, json, os, select, shutil, time, socket |
| |
| termwidth = 150 |
| |
| print_communication = True |
| |
| def ordered(obj): |
| if isinstance(obj, dict): |
| return sorted((k, ordered(v)) for k, v in obj.items()) |
| if isinstance(obj, list): |
| return sorted(ordered(x) for x in obj) |
| else: |
| return obj |
| |
| def col_print(title, array): |
| print() |
| print() |
| print(title) |
| |
| indentwidth = 4 |
| indent = " " * indentwidth |
| |
| if not array: |
| print(indent + "<None>") |
| return |
| |
| padwidth = 2 |
| |
| maxitemwidth = len(max(array, key=len)) |
| |
| numCols = max(1, int((termwidth - indentwidth + padwidth) / (maxitemwidth + padwidth))) |
| |
| numRows = len(array) // numCols + 1 |
| |
| pad = " " * padwidth |
| |
| for index in range(numRows): |
| print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows])) |
| |
| filterPacket = lambda x: x |
| |
| STDIN = 0 |
| PIPE = 1 |
| |
| communicationMethods = [STDIN] |
| |
| if hasattr(socket, 'AF_UNIX'): |
| communicationMethods.append(PIPE) |
| |
| def defaultExitWithError(proc): |
| data = "" |
| try: |
| while select.select([proc.outPipe], [], [], 3.)[0]: |
| data = data + proc.outPipe.read(1) |
| if len(data): |
| print("Rest of raw buffer from server:") |
| printServer(data) |
| except: |
| pass |
| proc.outPipe.close() |
| proc.inPipe.close() |
| proc.kill() |
| sys.exit(1) |
| |
| exitWithError = lambda proc: defaultExitWithError(proc) |
| |
| serverTag = "SERVER" |
| |
| def printServer(*args): |
| print(serverTag + ">", *args) |
| print() |
| sys.stdout.flush() |
| |
| def printClient(*args): |
| print("CLIENT>", *args) |
| print() |
| sys.stdout.flush() |
| |
| def waitForRawMessage(cmakeCommand): |
| stdoutdata = "" |
| payload = "" |
| while not cmakeCommand.poll(): |
| stdoutdataLine = cmakeCommand.outPipe.readline() |
| if stdoutdataLine: |
| stdoutdata += stdoutdataLine.decode('utf-8') |
| else: |
| break |
| begin = stdoutdata.find('[== "CMake Server" ==[\n') |
| end = stdoutdata.find(']== "CMake Server" ==]') |
| |
| if begin != -1 and end != -1: |
| begin += len('[== "CMake Server" ==[\n') |
| payload = stdoutdata[begin:end] |
| jsonPayload = json.loads(payload) |
| filteredPayload = filterPacket(jsonPayload) |
| if print_communication and filteredPayload: |
| printServer(filteredPayload) |
| if filteredPayload is not None or jsonPayload is None: |
| return jsonPayload |
| stdoutdata = stdoutdata[(end+len(']== "CMake Server" ==]')):] |
| |
| # Python2 has no problem writing the output of encodes directly, |
| # but Python3 returns only 'int's for encode and so must be turned |
| # into bytes. We use the existence of 'to_bytes' on an int to |
| # determine which behavior is appropriate. It might be more clear |
| # to do this in the code which uses the flag, but introducing |
| # this lookup cost at every byte sent isn't ideal. |
| has_to_bytes = "to_bytes" in dir(10) |
| |
| def writeRawData(cmakeCommand, content): |
| writeRawData.counter += 1 |
| payload = """ |
| [== "CMake Server" ==[ |
| %s |
| ]== "CMake Server" ==] |
| """ % content |
| |
| rn = ( writeRawData.counter % 2 ) == 0 |
| |
| if rn: |
| payload = payload.replace('\n', '\r\n') |
| |
| if print_communication: |
| printClient(content, "(Use \\r\\n:", rn, ")") |
| |
| # To stress test how cmake deals with fragmentation in the |
| # communication channel, we send only one byte at a time. |
| # Certain communication methods / platforms might still buffer |
| # it all into one message since its so close together, but in |
| # general this will catch places where we assume full buffers |
| # come in all at once. |
| encoded_payload = payload.encode('utf-8') |
| |
| # Python version 3+ can't write ints directly; but 'to_bytes' |
| # for int was only added in python 3.2. If this is a 3+ version |
| # of python without that conversion function; just write the whole |
| # thing out at once. |
| if sys.version_info[0] > 2 and not has_to_bytes: |
| cmakeCommand.write(encoded_payload) |
| else: |
| for c in encoded_payload: |
| if has_to_bytes: |
| c = c.to_bytes(1, byteorder='big') |
| cmakeCommand.write(c) |
| |
| writeRawData.counter = 0 |
| |
| def writePayload(cmakeCommand, obj): |
| writeRawData(cmakeCommand, json.dumps(obj)) |
| |
| def getPipeName(): |
| return "/tmp/server-test-socket" |
| |
| def attachPipe(cmakeCommand, pipeName): |
| time.sleep(1) |
| sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
| sock.connect(pipeName) |
| global serverTag |
| serverTag = "SERVER(PIPE)" |
| cmakeCommand.outPipe = sock.makefile() |
| cmakeCommand.inPipe = sock |
| cmakeCommand.write = cmakeCommand.inPipe.sendall |
| |
| def writeAndFlush(pipe, val): |
| pipe.write(val) |
| pipe.flush() |
| |
| def initServerProc(cmakeCommand, comm): |
| if comm == PIPE: |
| pipeName = getPipeName() |
| cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--pipe=" + pipeName]) |
| attachPipe(cmakeCommand, pipeName) |
| else: |
| cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"], |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE) |
| cmakeCommand.outPipe = cmakeCommand.stdout |
| cmakeCommand.inPipe = cmakeCommand.stdin |
| cmakeCommand.write = lambda val: writeAndFlush(cmakeCommand.inPipe, val) |
| |
| packet = waitForRawMessage(cmakeCommand) |
| if packet == None: |
| print("Not in server mode") |
| sys.exit(2) |
| |
| if packet['type'] != 'hello': |
| print("No hello message") |
| sys.exit(3) |
| |
| return cmakeCommand |
| |
| def exitProc(cmakeCommand): |
| # Tell the server to exit. |
| cmakeCommand.stdin.close() |
| cmakeCommand.stdout.close() |
| |
| # Wait for the server to exit. |
| # If this version of python supports it, terminate the server after a timeout. |
| try: |
| cmakeCommand.wait(timeout=5) |
| except TypeError: |
| cmakeCommand.wait() |
| except: |
| cmakeCommand.terminate() |
| raise |
| |
| def waitForMessage(cmakeCommand, expected): |
| data = ordered(expected) |
| packet = ordered(waitForRawMessage(cmakeCommand)) |
| |
| if packet != data: |
| print ("Received unexpected message; test failed") |
| exitWithError(cmakeCommand) |
| return packet |
| |
| def waitForReply(cmakeCommand, originalType, cookie, skipProgress): |
| gotResult = False |
| while True: |
| packet = waitForRawMessage(cmakeCommand) |
| t = packet['type'] |
| if packet['cookie'] != cookie or packet['inReplyTo'] != originalType: |
| print("cookie or inReplyTo mismatch") |
| sys.exit(4) |
| if t == 'message' or t == 'progress': |
| if skipProgress: |
| continue |
| if t == 'reply': |
| break |
| print("Unrecognized message", packet) |
| sys.exit(5) |
| |
| return packet |
| |
| def waitForError(cmakeCommand, originalType, cookie, message): |
| packet = waitForRawMessage(cmakeCommand) |
| if packet['cookie'] != cookie or packet['type'] != 'error' or packet['inReplyTo'] != originalType or packet['errorMessage'] != message: |
| sys.exit(6) |
| |
| def waitForProgress(cmakeCommand, originalType, cookie, current, message): |
| packet = waitForRawMessage(cmakeCommand) |
| if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message: |
| sys.exit(7) |
| |
| def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerator): |
| version = { 'major': major } |
| if minor >= 0: |
| version['minor'] = minor |
| |
| writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, |
| 'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build, |
| 'generator': generator, 'extraGenerator': extraGenerator }) |
| waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False) |
| |
| def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data): |
| packet = waitForReply(cmakeCommand, 'globalSettings', '', False) |
| |
| capabilities = packet['capabilities'] |
| |
| # validate version: |
| cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--version" ], universal_newlines=True) |
| cmakeVersion = cmakeoutput.splitlines()[0][14:] |
| |
| version = capabilities['version'] |
| versionString = version['string'] |
| vs = str(version['major']) + '.' + str(version['minor']) + '.' + str(version['patch']) |
| if (versionString != vs and not versionString.startswith(vs + '-')): |
| sys.exit(8) |
| if (versionString != cmakeVersion): |
| sys.exit(9) |
| |
| # validate generators: |
| generatorObjects = capabilities['generators'] |
| |
| cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--help" ], universal_newlines=True) |
| index = cmakeoutput.index('\nGenerators\n\n') |
| cmakeGenerators = [] |
| for line in cmakeoutput[index + 12:].splitlines(): |
| if not line: |
| continue |
| if line[0] == '*': # default generator marker |
| line = ' ' + line[1:] |
| if not line.startswith(' '): |
| continue |
| if line.startswith(' '): |
| continue |
| equalPos = line.find('=') |
| tmp = '' |
| if (equalPos > 0): |
| tmp = line[2:equalPos].strip() |
| else: |
| tmp = line.strip() |
| if tmp.endswith(" [arch]"): |
| tmp = tmp[0:len(tmp) - 7] |
| if (len(tmp) > 0) and (" - " not in tmp): |
| cmakeGenerators.append(tmp) |
| |
| generators = [] |
| for genObj in generatorObjects: |
| generators.append(genObj['name']) |
| |
| generators.sort() |
| cmakeGenerators.sort() |
| |
| for gen in cmakeGenerators: |
| if (not gen in generators): |
| sys.exit(10) |
| |
| gen = packet['generator'] |
| if (gen != '' and not (gen in generators)): |
| sys.exit(11) |
| |
| for i in data: |
| print("Validating", i) |
| if (packet[i] != data[i]): |
| sys.exit(12) |
| |
| def validateCache(cmakeCommand, data): |
| packet = waitForReply(cmakeCommand, 'cache', '', False) |
| |
| cache = packet['cache'] |
| |
| if (data['isEmpty']): |
| if (cache != []): |
| print('Expected empty cache, but got data.\n') |
| sys.exit(1) |
| return; |
| |
| if (cache == []): |
| print('Expected cache contents, but got none.\n') |
| sys.exit(1) |
| |
| hadHomeDir = False |
| for value in cache: |
| if (value['key'] == 'CMAKE_HOME_DIRECTORY'): |
| hadHomeDir = True |
| |
| if (not hadHomeDir): |
| print('No CMAKE_HOME_DIRECTORY found in cache.') |
| sys.exit(1) |
| |
| def handleBasicMessage(proc, obj, debug): |
| if 'sendRaw' in obj: |
| data = obj['sendRaw'] |
| if debug: print("Sending raw:", data) |
| writeRawData(proc, data) |
| return True |
| elif 'send' in obj: |
| data = obj['send'] |
| if debug: print("Sending:", json.dumps(data)) |
| writePayload(proc, data) |
| return True |
| elif 'recv' in obj: |
| data = obj['recv'] |
| if debug: print("Waiting for:", json.dumps(data)) |
| waitForMessage(proc, data) |
| return True |
| elif 'message' in obj: |
| print("MESSAGE:", obj["message"]) |
| sys.stdout.flush() |
| return True |
| return False |
| |
| def shutdownProc(proc): |
| # Tell the server to exit. |
| proc.inPipe.close() |
| proc.outPipe.close() |
| |
| # Wait for the server to exit. |
| # If this version of python supports it, terminate the server after a timeout. |
| try: |
| proc.wait(timeout=5) |
| except TypeError: |
| proc.wait() |
| except: |
| proc.terminate() |
| raise |
| |
| print('cmake-server exited: %d' % proc.returncode) |
| sys.exit(proc.returncode) |