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