to reflect the new license. These used slightly different spellings that defeated my regular expressions. We understand that people may be surprised that we're moving the header entirely to discuss the new license. We checked this carefully with the Foundation's lawyer and we believe this is the correct approach. Essentially, all code in the project is now made available by the LLVM project under our new license, so you will see that the license headers include that license only. Some of our contributors have contributed code under our old license, and accordingly, we have retained a copy of our old license notice in the top-level files in each project and repository. llvm-svn: 351648
198 lines
6.9 KiB
Python
198 lines
6.9 KiB
Python
#===----------------------------------------------------------------------===##
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
#
|
|
#===----------------------------------------------------------------------===##
|
|
|
|
import platform
|
|
import os
|
|
|
|
from libcxx.test import tracing
|
|
from libcxx.util import executeCommand
|
|
|
|
|
|
class Executor(object):
|
|
def run(self, exe_path, cmd, local_cwd, file_deps=None, env=None):
|
|
"""Execute a command.
|
|
Be very careful not to change shared state in this function.
|
|
Executor objects are shared between python processes in `lit -jN`.
|
|
Args:
|
|
exe_path: str: Local path to the executable to be run
|
|
cmd: [str]: subprocess.call style command
|
|
local_cwd: str: Local path to the working directory
|
|
file_deps: [str]: Files required by the test
|
|
env: {str: str}: Environment variables to execute under
|
|
Returns:
|
|
cmd, out, err, exitCode
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
class LocalExecutor(Executor):
|
|
def __init__(self):
|
|
super(LocalExecutor, self).__init__()
|
|
self.is_windows = platform.system() == 'Windows'
|
|
|
|
def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
if work_dir == '.':
|
|
work_dir = os.getcwd()
|
|
out, err, rc = executeCommand(cmd, cwd=work_dir, env=env)
|
|
return (cmd, out, err, rc)
|
|
|
|
|
|
class PrefixExecutor(Executor):
|
|
"""Prefix an executor with some other command wrapper.
|
|
|
|
Most useful for setting ulimits on commands, or running an emulator like
|
|
qemu and valgrind.
|
|
"""
|
|
def __init__(self, commandPrefix, chain):
|
|
super(PrefixExecutor, self).__init__()
|
|
|
|
self.commandPrefix = commandPrefix
|
|
self.chain = chain
|
|
|
|
def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
return self.chain.run(exe_path, self.commandPrefix + cmd, work_dir,
|
|
file_deps, env=env)
|
|
|
|
|
|
class PostfixExecutor(Executor):
|
|
"""Postfix an executor with some args."""
|
|
def __init__(self, commandPostfix, chain):
|
|
super(PostfixExecutor, self).__init__()
|
|
|
|
self.commandPostfix = commandPostfix
|
|
self.chain = chain
|
|
|
|
def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
return self.chain.run(cmd + self.commandPostfix, work_dir, file_deps,
|
|
env=env)
|
|
|
|
|
|
|
|
class TimeoutExecutor(PrefixExecutor):
|
|
"""Execute another action under a timeout.
|
|
|
|
Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT.
|
|
"""
|
|
def __init__(self, duration, chain):
|
|
super(TimeoutExecutor, self).__init__(
|
|
['timeout', duration], chain)
|
|
|
|
|
|
class RemoteExecutor(Executor):
|
|
def __init__(self):
|
|
self.local_run = executeCommand
|
|
|
|
def remote_temp_dir(self):
|
|
return self._remote_temp(True)
|
|
|
|
def remote_temp_file(self):
|
|
return self._remote_temp(False)
|
|
|
|
def _remote_temp(self, is_dir):
|
|
raise NotImplementedError()
|
|
|
|
def copy_in(self, local_srcs, remote_dsts):
|
|
# This could be wrapped up in a tar->scp->untar for performance
|
|
# if there are lots of files to be copied/moved
|
|
for src, dst in zip(local_srcs, remote_dsts):
|
|
self._copy_in_file(src, dst)
|
|
|
|
def _copy_in_file(self, src, dst):
|
|
raise NotImplementedError()
|
|
|
|
def delete_remote(self, remote):
|
|
try:
|
|
self._execute_command_remote(['rm', '-rf', remote])
|
|
except OSError:
|
|
# TODO: Log failure to delete?
|
|
pass
|
|
|
|
def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
|
|
target_exe_path = None
|
|
target_cwd = None
|
|
try:
|
|
target_cwd = self.remote_temp_dir()
|
|
target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe')
|
|
if cmd:
|
|
# Replace exe_path with target_exe_path.
|
|
cmd = [c if c != exe_path else target_exe_path for c in cmd]
|
|
else:
|
|
cmd = [target_exe_path]
|
|
|
|
srcs = [exe_path]
|
|
dsts = [target_exe_path]
|
|
if file_deps is not None:
|
|
dev_paths = [os.path.join(target_cwd, os.path.basename(f))
|
|
for f in file_deps]
|
|
srcs.extend(file_deps)
|
|
dsts.extend(dev_paths)
|
|
self.copy_in(srcs, dsts)
|
|
# TODO(jroelofs): capture the copy_in and delete_remote commands,
|
|
# and conjugate them with '&&'s around the first tuple element
|
|
# returned here:
|
|
return self._execute_command_remote(cmd, target_cwd, env)
|
|
finally:
|
|
if target_cwd:
|
|
self.delete_remote(target_cwd)
|
|
|
|
def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class SSHExecutor(RemoteExecutor):
|
|
def __init__(self, host, username=None):
|
|
super(SSHExecutor, self).__init__()
|
|
|
|
self.user_prefix = username + '@' if username else ''
|
|
self.host = host
|
|
self.scp_command = 'scp'
|
|
self.ssh_command = 'ssh'
|
|
|
|
# TODO(jroelofs): switch this on some -super-verbose-debug config flag
|
|
if False:
|
|
self.local_run = tracing.trace_function(
|
|
self.local_run, log_calls=True, log_results=True,
|
|
label='ssh_local')
|
|
|
|
def _remote_temp(self, is_dir):
|
|
# TODO: detect what the target system is, and use the correct
|
|
# mktemp command for it. (linux and darwin differ here, and I'm
|
|
# sure windows has another way to do it)
|
|
|
|
# Not sure how to do suffix on osx yet
|
|
dir_arg = '-d' if is_dir else ''
|
|
cmd = 'mktemp -q {} /tmp/libcxx.XXXXXXXXXX'.format(dir_arg)
|
|
_, temp_path, err, exitCode = self._execute_command_remote([cmd])
|
|
temp_path = temp_path.strip()
|
|
if exitCode != 0:
|
|
raise RuntimeError(err)
|
|
return temp_path
|
|
|
|
def _copy_in_file(self, src, dst):
|
|
scp = self.scp_command
|
|
remote = self.host
|
|
remote = self.user_prefix + remote
|
|
cmd = [scp, '-p', src, remote + ':' + dst]
|
|
self.local_run(cmd)
|
|
|
|
def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
|
|
remote = self.user_prefix + self.host
|
|
ssh_cmd = [self.ssh_command, '-oBatchMode=yes', remote]
|
|
if env:
|
|
env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()]
|
|
else:
|
|
env_cmd = []
|
|
remote_cmd = ' '.join(env_cmd + cmd)
|
|
if remote_work_dir != '.':
|
|
remote_cmd = 'cd ' + remote_work_dir + ' && ' + remote_cmd
|
|
out, err, rc = self.local_run(ssh_cmd + [remote_cmd])
|
|
return (remote_cmd, out, err, rc)
|