xref: /netbsd-src/external/apache2/llvm/dist/libcxx/utils/ssh.py (revision 4d6fc14bc9b0c5bf3e30be318c143ee82cadd108)
1*4d6fc14bSjoerg#!/usr/bin/env python
2*4d6fc14bSjoerg#===----------------------------------------------------------------------===##
3*4d6fc14bSjoerg#
4*4d6fc14bSjoerg# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5*4d6fc14bSjoerg# See https://llvm.org/LICENSE.txt for license information.
6*4d6fc14bSjoerg# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7*4d6fc14bSjoerg#
8*4d6fc14bSjoerg#===----------------------------------------------------------------------===##
9*4d6fc14bSjoerg
10*4d6fc14bSjoerg"""
11*4d6fc14bSjoergRuns an executable on a remote host.
12*4d6fc14bSjoerg
13*4d6fc14bSjoergThis is meant to be used as an executor when running the C++ Standard Library
14*4d6fc14bSjoergconformance test suite.
15*4d6fc14bSjoerg"""
16*4d6fc14bSjoerg
17*4d6fc14bSjoergimport argparse
18*4d6fc14bSjoergimport os
19*4d6fc14bSjoergimport posixpath
20*4d6fc14bSjoergimport shlex
21*4d6fc14bSjoergimport subprocess
22*4d6fc14bSjoergimport sys
23*4d6fc14bSjoergimport tarfile
24*4d6fc14bSjoergimport tempfile
25*4d6fc14bSjoerg
26*4d6fc14bSjoergtry:
27*4d6fc14bSjoerg   from shlex import quote as cmd_quote
28*4d6fc14bSjoergexcept ImportError:
29*4d6fc14bSjoerg   # for Python 2 compatibility
30*4d6fc14bSjoerg   from pipes import quote as cmd_quote
31*4d6fc14bSjoerg
32*4d6fc14bSjoergdef ssh(args, command):
33*4d6fc14bSjoerg    cmd = ['ssh', '-oBatchMode=yes']
34*4d6fc14bSjoerg    if args.extra_ssh_args is not None:
35*4d6fc14bSjoerg        cmd.extend(shlex.split(args.extra_ssh_args))
36*4d6fc14bSjoerg    return cmd + [args.host, command]
37*4d6fc14bSjoerg
38*4d6fc14bSjoerg
39*4d6fc14bSjoergdef scp(args, src, dst):
40*4d6fc14bSjoerg    cmd = ['scp', '-q', '-oBatchMode=yes']
41*4d6fc14bSjoerg    if args.extra_scp_args is not None:
42*4d6fc14bSjoerg        cmd.extend(shlex.split(args.extra_scp_args))
43*4d6fc14bSjoerg    return cmd + [src, '{}:{}'.format(args.host, dst)]
44*4d6fc14bSjoerg
45*4d6fc14bSjoerg
46*4d6fc14bSjoergdef main():
47*4d6fc14bSjoerg    parser = argparse.ArgumentParser()
48*4d6fc14bSjoerg    parser.add_argument('--host', type=str, required=True)
49*4d6fc14bSjoerg    parser.add_argument('--execdir', type=str, required=True)
50*4d6fc14bSjoerg    parser.add_argument('--tempdir', type=str, required=False, default='/tmp')
51*4d6fc14bSjoerg    parser.add_argument('--extra-ssh-args', type=str, required=False)
52*4d6fc14bSjoerg    parser.add_argument('--extra-scp-args', type=str, required=False)
53*4d6fc14bSjoerg    parser.add_argument('--codesign_identity', type=str, required=False, default=None)
54*4d6fc14bSjoerg    parser.add_argument('--env', type=str, nargs='*', required=False, default=dict())
55*4d6fc14bSjoerg    parser.add_argument("command", nargs=argparse.ONE_OR_MORE)
56*4d6fc14bSjoerg    args = parser.parse_args()
57*4d6fc14bSjoerg    commandLine = args.command
58*4d6fc14bSjoerg
59*4d6fc14bSjoerg    # Create a temporary directory where the test will be run.
60*4d6fc14bSjoerg    # That is effectively the value of %T on the remote host.
61*4d6fc14bSjoerg    tmp = subprocess.check_output(ssh(args, 'mktemp -d {}/libcxx.XXXXXXXXXX'.format(args.tempdir)), universal_newlines=True).strip()
62*4d6fc14bSjoerg
63*4d6fc14bSjoerg    # HACK:
64*4d6fc14bSjoerg    # If an argument is a file that ends in `.tmp.exe`, assume it is the name
65*4d6fc14bSjoerg    # of an executable generated by a test file. We call these test-executables
66*4d6fc14bSjoerg    # below. This allows us to do custom processing like codesigning test-executables
67*4d6fc14bSjoerg    # and changing their path when running on the remote host. It's also possible
68*4d6fc14bSjoerg    # for there to be no such executable, for example in the case of a .sh.cpp
69*4d6fc14bSjoerg    # test.
70*4d6fc14bSjoerg    isTestExe = lambda exe: exe.endswith('.tmp.exe') and os.path.exists(exe)
71*4d6fc14bSjoerg    pathOnRemote = lambda file: posixpath.join(tmp, os.path.basename(file))
72*4d6fc14bSjoerg
73*4d6fc14bSjoerg    try:
74*4d6fc14bSjoerg        # Do any necessary codesigning of test-executables found in the command line.
75*4d6fc14bSjoerg        if args.codesign_identity:
76*4d6fc14bSjoerg            for exe in filter(isTestExe, commandLine):
77*4d6fc14bSjoerg                subprocess.check_call(['xcrun', 'codesign', '-f', '-s', args.codesign_identity, exe], env={})
78*4d6fc14bSjoerg
79*4d6fc14bSjoerg        # tar up the execution directory (which contains everything that's needed
80*4d6fc14bSjoerg        # to run the test), and copy the tarball over to the remote host.
81*4d6fc14bSjoerg        try:
82*4d6fc14bSjoerg            tmpTar = tempfile.NamedTemporaryFile(suffix='.tar', delete=False)
83*4d6fc14bSjoerg            with tarfile.open(fileobj=tmpTar, mode='w') as tarball:
84*4d6fc14bSjoerg                tarball.add(args.execdir, arcname=os.path.basename(args.execdir))
85*4d6fc14bSjoerg
86*4d6fc14bSjoerg            # Make sure we close the file before we scp it, because accessing
87*4d6fc14bSjoerg            # the temporary file while still open doesn't work on Windows.
88*4d6fc14bSjoerg            tmpTar.close()
89*4d6fc14bSjoerg            remoteTarball = pathOnRemote(tmpTar.name)
90*4d6fc14bSjoerg            subprocess.check_call(scp(args, tmpTar.name, remoteTarball))
91*4d6fc14bSjoerg        finally:
92*4d6fc14bSjoerg            # Make sure we close the file in case an exception happens before
93*4d6fc14bSjoerg            # we've closed it above -- otherwise close() is idempotent.
94*4d6fc14bSjoerg            tmpTar.close()
95*4d6fc14bSjoerg            os.remove(tmpTar.name)
96*4d6fc14bSjoerg
97*4d6fc14bSjoerg        # Untar the dependencies in the temporary directory and remove the tarball.
98*4d6fc14bSjoerg        remoteCommands = [
99*4d6fc14bSjoerg            'tar -xf {} -C {} --strip-components 1'.format(remoteTarball, tmp),
100*4d6fc14bSjoerg            'rm {}'.format(remoteTarball)
101*4d6fc14bSjoerg        ]
102*4d6fc14bSjoerg
103*4d6fc14bSjoerg        # Make sure all test-executables in the remote command line have 'execute'
104*4d6fc14bSjoerg        # permissions on the remote host. The host that compiled the test-executable
105*4d6fc14bSjoerg        # might not have a notion of 'executable' permissions.
106*4d6fc14bSjoerg        for exe in map(pathOnRemote, filter(isTestExe, commandLine)):
107*4d6fc14bSjoerg            remoteCommands.append('chmod +x {}'.format(exe))
108*4d6fc14bSjoerg
109*4d6fc14bSjoerg        # Execute the command through SSH in the temporary directory, with the
110*4d6fc14bSjoerg        # correct environment. We tweak the command line to run it on the remote
111*4d6fc14bSjoerg        # host by transforming the path of test-executables to their path in the
112*4d6fc14bSjoerg        # temporary directory on the remote host.
113*4d6fc14bSjoerg        commandLine = (pathOnRemote(x) if isTestExe(x) else x for x in commandLine)
114*4d6fc14bSjoerg        remoteCommands.append('cd {}'.format(tmp))
115*4d6fc14bSjoerg        if args.env:
116*4d6fc14bSjoerg            remoteCommands.append('export {}'.format(cmd_quote(' '.join(args.env))))
117*4d6fc14bSjoerg        remoteCommands.append(subprocess.list2cmdline(commandLine))
118*4d6fc14bSjoerg
119*4d6fc14bSjoerg        # Finally, SSH to the remote host and execute all the commands.
120*4d6fc14bSjoerg        rc = subprocess.call(ssh(args, ' && '.join(remoteCommands)))
121*4d6fc14bSjoerg        return rc
122*4d6fc14bSjoerg
123*4d6fc14bSjoerg    finally:
124*4d6fc14bSjoerg        # Make sure the temporary directory is removed when we're done.
125*4d6fc14bSjoerg        subprocess.check_call(ssh(args, 'rm -r {}'.format(tmp)))
126*4d6fc14bSjoerg
127*4d6fc14bSjoerg
128*4d6fc14bSjoergif __name__ == '__main__':
129*4d6fc14bSjoerg    exit(main())
130