2 #===----------------------------------------------------------------------===##
4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 # See https://llvm.org/LICENSE.txt for license information.
6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 #===----------------------------------------------------------------------===##
11 Runs an executable on a remote host.
13 This is meant to be used as an executor when running the C++ Standard Library
14 conformance test suite.
27 parser
= argparse
.ArgumentParser()
28 parser
.add_argument('--host', type=str, required
=True)
29 parser
.add_argument('--execdir', type=str, required
=True)
30 parser
.add_argument('--codesign_identity', type=str, required
=False, default
=None)
31 parser
.add_argument('--env', type=str, nargs
='*', required
=False, default
=dict())
32 (args
, remaining
) = parser
.parse_known_args(sys
.argv
[1:])
34 if len(remaining
) < 2:
35 sys
.stderr
.write('Missing actual commands to run')
38 commandLine
= remaining
[1:] # Skip the '--'
40 ssh
= lambda command
: ['ssh', '-oBatchMode=yes', args
.host
, command
]
41 scp
= lambda src
, dst
: ['scp', '-q', '-oBatchMode=yes', src
, '{}:{}'.format(args
.host
, dst
)]
43 # Create a temporary directory where the test will be run.
44 # That is effectively the value of %T on the remote host.
45 tmp
= subprocess
.check_output(ssh('mktemp -d /tmp/libcxx.XXXXXXXXXX'), universal_newlines
=True).strip()
48 # If an argument is a file that ends in `.tmp.exe`, assume it is the name
49 # of an executable generated by a test file. We call these test-executables
50 # below. This allows us to do custom processing like codesigning test-executables
51 # and changing their path when running on the remote host. It's also possible
52 # for there to be no such executable, for example in the case of a .sh.cpp
54 isTestExe
= lambda exe
: exe
.endswith('.tmp.exe') and os
.path
.exists(exe
)
55 pathOnRemote
= lambda file: posixpath
.join(tmp
, os
.path
.basename(file))
58 # Do any necessary codesigning of test-executables found in the command line.
59 if args
.codesign_identity
:
60 for exe
in filter(isTestExe
, commandLine
):
61 subprocess
.check_call(['xcrun', 'codesign', '-f', '-s', args
.codesign_identity
, exe
], env
={})
63 # tar up the execution directory (which contains everything that's needed
64 # to run the test), and copy the tarball over to the remote host.
66 tmpTar
= tempfile
.NamedTemporaryFile(suffix
='.tar', delete
=False)
67 with tarfile
.open(fileobj
=tmpTar
, mode
='w') as tarball
:
68 tarball
.add(args
.execdir
, arcname
=os
.path
.basename(args
.execdir
))
70 # Make sure we close the file before we scp it, because accessing
71 # the temporary file while still open doesn't work on Windows.
73 remoteTarball
= pathOnRemote(tmpTar
.name
)
74 subprocess
.check_call(scp(tmpTar
.name
, remoteTarball
))
76 # Make sure we close the file in case an exception happens before
77 # we've closed it above -- otherwise close() is idempotent.
79 os
.remove(tmpTar
.name
)
81 # Untar the dependencies in the temporary directory and remove the tarball.
83 'tar -xf {} -C {} --strip-components 1'.format(remoteTarball
, tmp
),
84 'rm {}'.format(remoteTarball
)
87 # Make sure all test-executables in the remote command line have 'execute'
88 # permissions on the remote host. The host that compiled the test-executable
89 # might not have a notion of 'executable' permissions.
90 for exe
in map(pathOnRemote
, filter(isTestExe
, commandLine
)):
91 remoteCommands
.append('chmod +x {}'.format(exe
))
93 # Execute the command through SSH in the temporary directory, with the
94 # correct environment. We tweak the command line to run it on the remote
95 # host by transforming the path of test-executables to their path in the
96 # temporary directory on the remote host.
97 commandLine
= (pathOnRemote(x
) if isTestExe(x
) else x
for x
in commandLine
)
98 remoteCommands
.append('cd {}'.format(tmp
))
100 remoteCommands
.append('export {}'.format(' '.join(args
.env
)))
101 remoteCommands
.append(subprocess
.list2cmdline(commandLine
))
103 # Finally, SSH to the remote host and execute all the commands.
104 rc
= subprocess
.call(ssh(' && '.join(remoteCommands
)))
108 # Make sure the temporary directory is removed when we're done.
109 subprocess
.check_call(ssh('rm -r {}'.format(tmp
)))
112 if __name__
== '__main__':