]> git.saurik.com Git - wxWidgets.git/blame - wxPython/distrib/all/taskrunner.py
slight refactor
[wxWidgets.git] / wxPython / distrib / all / taskrunner.py
CommitLineData
e4bb5998
RD
1#----------------------------------------------------------------------
2# Name: taskrunner.py
3# Purpose: Classes that can manage running of external processes,
4# either consecutively, simultaneously, or both, and can
5# log the output of those jobs
6#
7# Author: Robin Dunn
8#
9# Created: 05-Nov-2004
10# RCS-ID: $Id$
11# Copyright: (c) 2004 by Total Control Software
12# Licence: wxWindows license
13#----------------------------------------------------------------------
14
15import sys
16import os
17import signal
18import select
19import fcntl
20from subprocess import Popen, PIPE, STDOUT
21
22
23__all__ = ["Job", "Task", "TaskRunner"]
24
25#----------------------------------------------------------------------
26
f1a9f331
KO
27# For environment settings
28class Config:
29 def asDict(self):
30 return self.__dict__.copy()
31
32 def write(self, filename="config", outfile=None):
33 if outfile is None:
34 f = file(filename, "w")
35 else:
36 f = outfile
37 for k, v in self.__dict__.items():
38 f.write('%s="%s"\n' % (k, v))
39
40 def read(self, filename="config"):
41 myfile = open(filename, "r")
42 for line in myfile.readlines():
43 line = line.strip()
44 if len(line) > 0 and line[0] == "#":
45 continue # it's a comment, move on
46 data = line.split("=")
47 if len(data) == 2:
48 self.__dict__[data[0]] = data[1]
49 myfile.close()
e4bb5998
RD
50
51class Job(object):
52 """
53 Each Job is a monitor wrapped around an externally executing
54 process. It handles starting the process, polling if it is still
55 running, reading and logging it's output, and killing it if
56 needed.
57 """
58
59 LOGBASE="."
60
f1a9f331 61 def __init__(self, label, command, args=[], env=os.environ):
e4bb5998 62 self.label = label
f1a9f331 63 self.command = command
e4bb5998 64 self.args = args
f1a9f331 65 self.env = env
e4bb5998
RD
66 self.proc = None
67 if self.label:
f1a9f331 68 if not os.path.exists(self.LOGBASE):
582241e4 69 os.makedirs(self.LOGBASE)
e4bb5998
RD
70 self.log = file("%s/%s.log" % (self.LOGBASE, label), "w", 0)
71
72 def start(self):
f1a9f331
KO
73 self.proc = Popen([self.command] + self.args, # the command and args to execute
74 stdout=PIPE, stderr=STDOUT, env=self.env,
75 bufsize=0 # line-buffered
e4bb5998
RD
76 )
77 # put the file in non-blocking mode
78 #flags = fcntl.fcntl (self.proc.stdout, fcntl.F_GETFL, 0)
79 #flags = flags | os.O_NONBLOCK
80 #fcntl.fcntl (self.proc.stdout, fcntl.F_SETFL, flags)
81
82
83 def stop(self):
84 if self.proc is not None and self.proc.returncode is None:
85 os.kill(self.proc.pid, signal.SIGTERM)
86 self.logLines()
87
88
89 def fileno(self):
90 if self.proc is not None:
91 return self.proc.stdout.fileno()
92 else:
93 return -1
94
95
96 def logLines(self):
97 if self.proc is not None:
98 while self.linesAvailable():
99 line = self.proc.stdout.readline()
100 if not line: break
101 if self.label:
102 self.log.write(line)
103 line = "** %s: %s" % (self.label, line)
104 sys.stdout.write(line)
105
106
107 def linesAvailable(self):
108 if self.proc is None:
109 return False
110 ind, outd, err = select.select([self], [], [], 0)
111 if ind:
112 return True
113 else:
114 return False
115
116
117 def finished(self):
118 if self.proc is None:# or self.linesAvailable():
119 return False
120 return self.proc.poll() is not None
121
122
123 def wait(self):
124 if self.proc is None: return None
125 return self.proc.wait()
126
127
128 def poll(self):
129 if self.proc is None: return None
130 return self.proc.poll()
131
132
133 def returnCode(self):
134 if self.proc is None: return None
135 return self.proc.returncode
136
137
138#----------------------------------------------------------------------
139
140class Task(object):
141 """
142 This class helps manage the running of a Task, which is a simply a
143 sequence of one or more Jobs, where subesquent jobs are not
144 started until prior ones are completed.
145 """
146 def __init__(self, jobs=[]):
147 if type(jobs) != list:
148 jobs = [jobs]
149 self.jobs = jobs[:]
150 self.active = 0
151
152 def append(self, job):
153 self.jobs.append(job)
154
155 def activeJob(self):
156 if self.active > len(self.jobs)-1:
157 return None
158 else:
159 return self.jobs[self.active]
160
161 def next(self):
162 self.active += 1
163 if self.active < len(self.jobs):
164 self.jobs[self.active].start()
165
166#----------------------------------------------------------------------
167
168class TaskRunner(object):
169 """
170 Manages the running of multiple tasks.
171 """
172 def __init__(self, tasks=[]):
173 if type(tasks) != list:
174 tasks = [tasks]
175 self.tasks = tasks[:]
176
177 def append(self, task):
178 self.tasks.append(task)
179
180 def run(self):
181 # start all the active jobs
182 for task in self.tasks:
183 task.activeJob().start()
184
185 try:
186 # loop, getting output from the jobs, etc.
187 while True:
188 # get all active Jobs
189 jobs = [t.activeJob() for t in self.tasks if t.activeJob()]
190 if not jobs:
191 break
192
193 # wait for a job to have output ready, then log it
194 input, output, err = select.select(jobs, [], [], 1)
195 for job in input:
196 job.logLines()
197
198 # check for finished jobs
199 for task in self.tasks:
200 job = task.activeJob()
201 if job and job.finished():
202 if job.returnCode() != 0:
203 rc = job.returnCode()
204 print "JOB RETURNED FAILURE CODE! (%d)" % rc
205 self.stopAllJobs()
206 return rc
207 else:
208 task.next()
209 except KeyboardInterrupt:
210 print "STOPPING JOBS..."
211 self.stopAllJobs()
2c12daf9 212 return 1
e4bb5998
RD
213
214 except:
215 print "Unknown exception..."
216 self.stopAllJobs()
217 raise
218
219 return 0
220
221
222 def stopAllJobs(self):
223 for task in self.tasks:
224 job = task.activeJob()
225 if job:
226 job.stop()
227
228#----------------------------------------------------------------------
229
230
231if __name__ == "__main__":
232
233 j1 = Job("label1", ["./tmp/job-1.py", "TEST-1"])
234 j2 = Job("label2", ["./tmp/job-2.sh", "TEST-2"])
235
236 t1 = Task()
237 t1.append(j1)
238 t1.append(j2)
239
240 j3 = Job("task2a", ["./tmp/job-1.py", "TASK-2a"])
241 j4 = Job("task2b", ["./tmp/job-2.sh", "TASK-2b"])
242
243 t2 = Task()
244 t2.append(j4)
245 t2.append(j3)
246
247 t3 = Task([Job("error", ["./tmp/job-3.sh", "TASK-3"])])
248
249 tr = TaskRunner()
250 tr.append(t1)
251 tr.append(t2)
252 tr.append(t3)
253
254 for task in tr.tasks:
255 for job in task.jobs:
256 print job.label
257
258 print tr.run()
259