Pin pip to 20.3.3 and disable tmpfs in DIB
[ta/build-tools.git] / tools / executor.py
1 # Copyright 2019 Nokia
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import os
16 import logging
17 import shlex
18 import subprocess
19
20
21 class ExecutionError(Exception):
22     def __init__(self, msg, result):
23         super(ExecutionError, self).__init__(msg)
24         self.result = result
25
26
27 class Result(object):
28     def __init__(self, status, stdout, stderr):
29         self.status = status
30         self.stdout = stdout
31         self.stderr = stderr
32
33     @property
34     def str_status(self):
35         return 'Status:"{}"'.format(self.status)
36
37     @property
38     def str_stdout(self):
39         return 'Stdout:"{}"'.format(self.stdout)
40
41     @property
42     def str_stderr(self):
43         return 'Stderr:"{}"'.format(self.stderr)
44
45     def __str__(self):
46         return '\n'.join([self.str_status, self.str_stdout, self.str_stderr])
47
48
49 def run(*args, **kwargs):
50     return Executor().run(*args, **kwargs)
51
52
53 class Executor(object):
54     def __init__(self, shell=False, chdir=None):
55         self.shell = shell
56         self.chdir = chdir
57
58     def run(self, cmd, raise_on_error=True, raise_on_stderr=True, retries=0):
59         result = self._run(cmd, retries)
60         if raise_on_error and result.status != 0:
61             raise ExecutionError('Command "{}" execution status NOT zero: {}'.format(
62                 cmd, str(result)), result.status)
63         if raise_on_stderr and result.stderr:
64             raise ExecutionError('Command "{}" execution stderr not empty: {}'.format(
65                 cmd, str(result)), result.status)
66         return result
67
68     @staticmethod
69     def _log_result(result):
70         logging.debug('Result: %s', str(result))
71
72     def _run(self, cmd, retries):
73         logstr = 'Executing command: "{}"'.format(cmd)
74         cwd = os.getcwd()
75         if self.chdir is not None:
76             os.chdir(self.chdir)
77             logstr += ' in dir {}'.format(self.chdir)
78         logging.debug(logstr)
79         result = self._run_with_retries(cmd, retries)
80         if self.chdir is not None:
81             os.chdir(cwd)
82         return result
83
84     def _run_with_retries(self, cmd, retries):
85         result = self._execute(cmd)
86         while result.status != 0 and retries > 0:
87             logging.debug('Retrying, retries left: %s', retries)
88             retries -= 1
89             result = self._execute(cmd)
90             self._log_result(result)
91             if result.status == 0:
92                 break
93         return result
94
95     def _execute(self, cmd):
96         if not self.shell:
97             if isinstance(cmd, list):
98                 cmd_args = cmd[:]
99             else:
100                 cmd_args = shlex.split(cmd)
101         else:
102             cmd_args = cmd
103         process = subprocess.Popen(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
104                                    shell=self.shell)
105         stdout, stderr = process.communicate()
106         result = Result(process.returncode, stdout, stderr)
107         return result