blob: bf6c8f5ae15aea7b7c8ddbf79a592289f528b9df [file] [log] [blame]
Srikrishna Iyer65783742021-02-10 21:42:12 -08001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4
Srikrishna Iyer65783742021-02-10 21:42:12 -08005import os
Srikrishna Iyer65783742021-02-10 21:42:12 -08006import shlex
7import subprocess
8
9from Launcher import Launcher, LauncherError
10
11
12class LocalLauncher(Launcher):
13 """
14 Implementation of Launcher to launch jobs in the user's local workstation.
15 """
16
17 # Misc common LocalLauncher settings.
18 max_odirs = 5
19
20 def __init__(self, deploy):
21 '''Initialize common class members.'''
22
23 super().__init__(deploy)
24
25 # Popen object when launching the job.
26 self.process = None
27
28 def _do_launch(self):
29 # Update the shell's env vars with self.exports. Values in exports must
30 # replace the values in the shell's env vars if the keys match.
31 exports = os.environ.copy()
32 if self.deploy.exports:
33 exports.update(self.deploy.exports)
34
35 # Clear the magic MAKEFLAGS variable from exports if necessary. This
36 # variable is used by recursive Make calls to pass variables from one
37 # level to the next. Here, self.cmd is a call to Make but it's
38 # logically a top-level invocation: we don't want to pollute the flow's
39 # Makefile with Make variables from any wrapper that called dvsim.
40 if 'MAKEFLAGS' in exports:
41 del exports['MAKEFLAGS']
42
43 self._dump_env_vars(exports)
44
45 args = shlex.split(self.deploy.cmd)
46 try:
47 f = open(self.deploy.get_log_path(),
48 "w",
49 encoding="UTF-8",
50 errors="surrogateescape")
51 f.write("[Executing]:\n{}\n\n".format(self.deploy.cmd))
52 f.flush()
53 self.process = subprocess.Popen(args,
54 bufsize=4096,
55 universal_newlines=True,
56 stdout=f,
57 stderr=f,
58 env=exports)
59 except subprocess.SubprocessError as e:
60 raise LauncherError('IO Error: {}\nSee {}'.format(
61 e, self.deploy.get_log_path()))
62 finally:
63 self._close_process()
64
65 self._link_odir("D")
66
67 def poll(self):
68 '''Check status of the running process
69
70 This returns 'D', 'P' or 'F'. If 'D', the job is still running. If 'P',
71 the job finished successfully. If 'F', the job finished with an error.
72
73 This function must only be called after running self.dispatch_cmd() and
74 must not be called again once it has returned 'P' or 'F'.
75 '''
76
77 assert self.process is not None
78 if self.process.poll() is None:
79 return 'D'
80
81 self.exit_code = self.process.returncode
Srikrishna Iyer4829d282021-03-17 00:18:26 -070082 status, err_msg = self._check_status()
83 self._post_finish(status, err_msg)
Srikrishna Iyer65783742021-02-10 21:42:12 -080084 return status
85
Srikrishna Iyer65783742021-02-10 21:42:12 -080086 def kill(self):
87 '''Kill the running process.
88
89 This must be called between dispatching and reaping the process (the
90 same window as poll()).
91
92 '''
93 assert self.process is not None
Srikrishna Iyer65783742021-02-10 21:42:12 -080094
95 # Try to kill the running process. Send SIGTERM first, wait a bit,
96 # and then send SIGKILL if it didn't work.
97 self.process.terminate()
98 try:
99 self.process.wait(timeout=2)
100 except subprocess.TimeoutExpired:
101 self.process.kill()
102
Srikrishna Iyer4829d282021-03-17 00:18:26 -0700103 self._post_finish('K', 'Job killed!')
104
105 def _post_finish(self, status, err_msg):
106 super()._post_finish(status, err_msg)
107 self._close_process()
108 self.process = None
Srikrishna Iyer65783742021-02-10 21:42:12 -0800109
110 def _close_process(self):
111 '''Close the file descriptors associated with the process.'''
112
113 assert self.process
114 if self.process.stdout:
115 self.process.stdout.close()