1dc98923aSAlex Orlov#!/usr/bin/env python 2dc98923aSAlex Orlov# ===----------------------------------------------------------------------===## 3dc98923aSAlex Orlov# 4dc98923aSAlex Orlov# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5dc98923aSAlex Orlov# See https://llvm.org/LICENSE.txt for license information. 6dc98923aSAlex Orlov# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7dc98923aSAlex Orlov# 8dc98923aSAlex Orlov# ===----------------------------------------------------------------------===## 9dc98923aSAlex Orlov 10dc98923aSAlex Orlov""" 11dc98923aSAlex OrlovRuns an executable on a remote host. 12dc98923aSAlex Orlov 13dc98923aSAlex OrlovThis is meant to be used as an executor when running the LLVM and the Libraries 14dc98923aSAlex Orlovtests on a target. 15dc98923aSAlex Orlov""" 16dc98923aSAlex Orlov 17dc98923aSAlex Orlovimport argparse 18dc98923aSAlex Orlovimport os 19dc98923aSAlex Orlovimport posixpath 20dc98923aSAlex Orlovimport shlex 21dc98923aSAlex Orlovimport subprocess 22dc98923aSAlex Orlovimport sys 23dc98923aSAlex Orlovimport tarfile 24dc98923aSAlex Orlovimport tempfile 25dc98923aSAlex Orlovimport re 26dc98923aSAlex Orlov 27*b71edfaaSTobias Hieta 28dc98923aSAlex Orlovdef ssh(args, command): 29*b71edfaaSTobias Hieta cmd = ["ssh", "-oBatchMode=yes"] 30dc98923aSAlex Orlov if args.extra_ssh_args is not None: 31dc98923aSAlex Orlov cmd.extend(shlex.split(args.extra_ssh_args)) 32dc98923aSAlex Orlov return cmd + [args.host, command] 33dc98923aSAlex Orlov 34dc98923aSAlex Orlov 35dc98923aSAlex Orlovdef scp(args, src, dst): 36*b71edfaaSTobias Hieta cmd = ["scp", "-q", "-oBatchMode=yes"] 37dc98923aSAlex Orlov if args.extra_scp_args is not None: 38dc98923aSAlex Orlov cmd.extend(shlex.split(args.extra_scp_args)) 39*b71edfaaSTobias Hieta return cmd + [src, "{}:{}".format(args.host, dst)] 40dc98923aSAlex Orlov 41dc98923aSAlex Orlov 42dc98923aSAlex Orlovdef main(): 43dc98923aSAlex Orlov parser = argparse.ArgumentParser() 44*b71edfaaSTobias Hieta parser.add_argument("--host", type=str, required=True) 45*b71edfaaSTobias Hieta parser.add_argument("--execdir", type=str, required=False) 46*b71edfaaSTobias Hieta parser.add_argument("--extra-ssh-args", type=str, required=False) 47*b71edfaaSTobias Hieta parser.add_argument("--extra-scp-args", type=str, required=False) 48*b71edfaaSTobias Hieta parser.add_argument("--codesign_identity", type=str, required=False, default=None) 49*b71edfaaSTobias Hieta parser.add_argument("--env", type=str, nargs="*", required=False, default=dict()) 50dc98923aSAlex Orlov 51dc98923aSAlex Orlov # Note: The default value is for the backward compatibility with a hack in 52dc98923aSAlex Orlov # libcxx test suite. 53dc98923aSAlex Orlov # If an argument is a file that ends in `.tmp.exe`, assume it is the name 54dc98923aSAlex Orlov # of an executable generated by a test file. We call these test-executables 55dc98923aSAlex Orlov # below. This allows us to do custom processing like codesigning test-executables 56dc98923aSAlex Orlov # and changing their path when running on the remote host. It's also possible 57dc98923aSAlex Orlov # for there to be no such executable, for example in the case of a .sh.cpp 58dc98923aSAlex Orlov # test. 59*b71edfaaSTobias Hieta parser.add_argument( 60*b71edfaaSTobias Hieta "--exec-pattern", 61*b71edfaaSTobias Hieta type=str, 62*b71edfaaSTobias Hieta required=False, 63*b71edfaaSTobias Hieta default=".*", 64*b71edfaaSTobias Hieta help="The name regex pattern of the executables generated by \ 65dc98923aSAlex Orlov a test file. Specifying it allows us to do custom \ 66dc98923aSAlex Orlov processing like codesigning test-executables \ 67dc98923aSAlex Orlov and changing their path when running on \ 68*b71edfaaSTobias Hieta the remote host. It's also possible for there \ 69dc98923aSAlex Orlov to be no such executable, for example in \ 70*b71edfaaSTobias Hieta the case of a .sh.cpp test.", 71*b71edfaaSTobias Hieta ) 72dc98923aSAlex Orlov 73dc98923aSAlex Orlov parser.add_argument("command", nargs=argparse.ONE_OR_MORE) 74dc98923aSAlex Orlov args = parser.parse_args() 75dc98923aSAlex Orlov commandLine = args.command 76dc98923aSAlex Orlov 7720e1efcfSVladimir Vereschaka execdir = args.execdir 78*b71edfaaSTobias Hieta if execdir == ".": 7920e1efcfSVladimir Vereschaka # Retrieve the exec directory from the command line. 8020e1efcfSVladimir Vereschaka execdir, _ = os.path.split(commandLine[0]) 81*b71edfaaSTobias Hieta if execdir == "": 8220e1efcfSVladimir Vereschaka # Get the current directory in that case. 8320e1efcfSVladimir Vereschaka execdir = os.getcwd() 8420e1efcfSVladimir Vereschaka arcname = os.path.basename(execdir) if execdir else None 8520e1efcfSVladimir Vereschaka 86dc98923aSAlex Orlov # Create a temporary directory where the test will be run. 87dc98923aSAlex Orlov # That is effectively the value of %T on the remote host. 88dc98923aSAlex Orlov tmp = subprocess.check_output( 89*b71edfaaSTobias Hieta ssh(args, "mktemp -d /tmp/llvm.XXXXXXXXXX"), universal_newlines=True 90dc98923aSAlex Orlov ).strip() 91dc98923aSAlex Orlov 92dc98923aSAlex Orlov isExecutable = lambda exe: re.match(args.exec_pattern, exe) and os.path.exists(exe) 93dc98923aSAlex Orlov pathOnRemote = lambda file: posixpath.join(tmp, os.path.basename(file)) 94dc98923aSAlex Orlov 9520e1efcfSVladimir Vereschaka remoteCommands = [] 9620e1efcfSVladimir Vereschaka 97dc98923aSAlex Orlov try: 98dc98923aSAlex Orlov # Do any necessary codesigning of test-executables found in the command line. 99dc98923aSAlex Orlov if args.codesign_identity: 100dc98923aSAlex Orlov for exe in filter(isExecutable, commandLine): 101dc98923aSAlex Orlov subprocess.check_call( 102*b71edfaaSTobias Hieta ["xcrun", "codesign", "-f", "-s", args.codesign_identity, exe], 103*b71edfaaSTobias Hieta env={}, 104*b71edfaaSTobias Hieta ) 105dc98923aSAlex Orlov 106dc98923aSAlex Orlov # tar up the execution directory (which contains everything that's needed 107dc98923aSAlex Orlov # to run the test), and copy the tarball over to the remote host. 10820e1efcfSVladimir Vereschaka if execdir: 109dc98923aSAlex Orlov try: 110*b71edfaaSTobias Hieta tmpTar = tempfile.NamedTemporaryFile(suffix=".tar", delete=False) 111*b71edfaaSTobias Hieta with tarfile.open(fileobj=tmpTar, mode="w") as tarball: 11220e1efcfSVladimir Vereschaka tarball.add(execdir, arcname=arcname) 113dc98923aSAlex Orlov 114dc98923aSAlex Orlov # Make sure we close the file before we scp it, because accessing 115dc98923aSAlex Orlov # the temporary file while still open doesn't work on Windows. 116dc98923aSAlex Orlov tmpTar.close() 117dc98923aSAlex Orlov remoteTarball = pathOnRemote(tmpTar.name) 118dc98923aSAlex Orlov subprocess.check_call(scp(args, tmpTar.name, remoteTarball)) 119dc98923aSAlex Orlov finally: 120dc98923aSAlex Orlov # Make sure we close the file in case an exception happens before 121dc98923aSAlex Orlov # we've closed it above -- otherwise close() is idempotent. 122dc98923aSAlex Orlov tmpTar.close() 123dc98923aSAlex Orlov os.remove(tmpTar.name) 124dc98923aSAlex Orlov 125dc98923aSAlex Orlov # Untar the dependencies in the temporary directory and remove the tarball. 126*b71edfaaSTobias Hieta remoteCommands.extend( 127*b71edfaaSTobias Hieta [ 128*b71edfaaSTobias Hieta "tar -xf {} -C {} --strip-components 1".format(remoteTarball, tmp), 129*b71edfaaSTobias Hieta "rm {}".format(remoteTarball), 130*b71edfaaSTobias Hieta ] 131*b71edfaaSTobias Hieta ) 13220e1efcfSVladimir Vereschaka else: 13320e1efcfSVladimir Vereschaka # Copy only the files, which are specified in the command line. 13420e1efcfSVladimir Vereschaka # Copy them to remote host one by one. 13520e1efcfSVladimir Vereschaka for x in commandLine: 13620e1efcfSVladimir Vereschaka _, f = os.path.split(x) 13720e1efcfSVladimir Vereschaka subprocess.check_call(scp(args, x, pathOnRemote(f))) 138dc98923aSAlex Orlov 139dc98923aSAlex Orlov # Make sure all executables in the remote command line have 'execute' 140dc98923aSAlex Orlov # permissions on the remote host. The host that compiled the test-executable 141dc98923aSAlex Orlov # might not have a notion of 'executable' permissions. 142dc98923aSAlex Orlov for exe in filter(isExecutable, commandLine): 143*b71edfaaSTobias Hieta remoteCommands.append("chmod +x {}".format(pathOnRemote(exe))) 144dc98923aSAlex Orlov 145dc98923aSAlex Orlov # Execute the command through SSH in the temporary directory, with the 146dc98923aSAlex Orlov # correct environment. We tweak the command line to run it on the remote 147dc98923aSAlex Orlov # host by transforming the path of test-executables to their path in the 148dc98923aSAlex Orlov # temporary directory on the remote host. 149dc98923aSAlex Orlov for i, x in enumerate(commandLine): 150dc98923aSAlex Orlov if isExecutable(x): 151dc98923aSAlex Orlov commandLine[i] = pathOnRemote(x) 152*b71edfaaSTobias Hieta remoteCommands.append("cd {}".format(tmp)) 153dc98923aSAlex Orlov if args.env: 154*b71edfaaSTobias Hieta remoteCommands.append("export {}".format(" ".join(args.env))) 155dc98923aSAlex Orlov remoteCommands.append(subprocess.list2cmdline(commandLine)) 156dc98923aSAlex Orlov 157dc98923aSAlex Orlov # Finally, SSH to the remote host and execute all the commands. 158*b71edfaaSTobias Hieta rc = subprocess.call(ssh(args, " && ".join(remoteCommands))) 159dc98923aSAlex Orlov return rc 160dc98923aSAlex Orlov 161dc98923aSAlex Orlov finally: 162dc98923aSAlex Orlov # Make sure the temporary directory is removed when we're done. 163*b71edfaaSTobias Hieta subprocess.check_call(ssh(args, "rm -r {}".format(tmp))) 164dc98923aSAlex Orlov 165dc98923aSAlex Orlov 166*b71edfaaSTobias Hietaif __name__ == "__main__": 167dc98923aSAlex Orlov exit(main()) 168