blob: 03f997af8ab5da0bec14327b2cba0a31253538c7 [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
Guillermo Maturana76caf9a2021-04-08 14:04:15 -07009from Launcher import ErrorMessage, Launcher, LauncherError
Srikrishna Iyer65783742021-02-10 21:42:12 -080010
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
Srikrishna Iyer65783742021-02-10 21:42:12 -080045 try:
46 f = open(self.deploy.get_log_path(),
47 "w",
48 encoding="UTF-8",
49 errors="surrogateescape")
50 f.write("[Executing]:\n{}\n\n".format(self.deploy.cmd))
51 f.flush()
Srikrishna Iyer9bff8ea2021-04-02 15:14:04 -070052 self.process = subprocess.Popen(shlex.split(self.deploy.cmd),
Srikrishna Iyer65783742021-02-10 21:42:12 -080053 bufsize=4096,
54 universal_newlines=True,
55 stdout=f,
56 stderr=f,
57 env=exports)
58 except subprocess.SubprocessError as e:
59 raise LauncherError('IO Error: {}\nSee {}'.format(
60 e, self.deploy.get_log_path()))
61 finally:
62 self._close_process()
63
64 self._link_odir("D")
65
66 def poll(self):
67 '''Check status of the running process
68
69 This returns 'D', 'P' or 'F'. If 'D', the job is still running. If 'P',
70 the job finished successfully. If 'F', the job finished with an error.
71
72 This function must only be called after running self.dispatch_cmd() and
73 must not be called again once it has returned 'P' or 'F'.
74 '''
75
76 assert self.process is not None
77 if self.process.poll() is None:
78 return 'D'
79
80 self.exit_code = self.process.returncode
Srikrishna Iyer4829d282021-03-17 00:18:26 -070081 status, err_msg = self._check_status()
82 self._post_finish(status, err_msg)
Srikrishna Iyer9b381412021-08-10 23:11:43 -070083 return self.status
Srikrishna Iyer65783742021-02-10 21:42:12 -080084
Srikrishna Iyer65783742021-02-10 21:42:12 -080085 def kill(self):
86 '''Kill the running process.
87
88 This must be called between dispatching and reaping the process (the
89 same window as poll()).
90
91 '''
92 assert self.process is not None
Srikrishna Iyer65783742021-02-10 21:42:12 -080093
94 # Try to kill the running process. Send SIGTERM first, wait a bit,
95 # and then send SIGKILL if it didn't work.
96 self.process.terminate()
97 try:
98 self.process.wait(timeout=2)
99 except subprocess.TimeoutExpired:
100 self.process.kill()
101
Guillermo Maturana76caf9a2021-04-08 14:04:15 -0700102 self._post_finish('K', ErrorMessage(line_number=None,
103 message='Job killed!',
104 context=[]))
Srikrishna Iyer4829d282021-03-17 00:18:26 -0700105
106 def _post_finish(self, status, err_msg):
Srikrishna Iyer4829d282021-03-17 00:18:26 -0700107 self._close_process()
108 self.process = None
Srikrishna Iyer9b381412021-08-10 23:11:43 -0700109 super()._post_finish(status, err_msg)
Srikrishna Iyer65783742021-02-10 21:42:12 -0800110
111 def _close_process(self):
112 '''Close the file descriptors associated with the process.'''
113
114 assert self.process
115 if self.process.stdout:
116 self.process.stdout.close()