745e15f2587c775b05507209fe5936b6ecf952c1
[platform/framework/web/crosswalk.git] / src / tools / cr / cr / base / host.py
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Module for build host support."""
6
7 import os
8 import pipes
9 import subprocess
10
11 import cr
12
13 # Controls what verbosity level turns on command trail logging
14 _TRAIL_VERBOSITY = 2
15
16
17 class Host(cr.Plugin, cr.Plugin.Type):
18   """Base class for implementing cr hosts.
19
20   The host is the main access point to services provided by the machine cr
21   is running on. It exposes information about the machine, and runs external
22   commands on behalf of the actions.
23   """
24
25   def __init__(self):
26     super(Host, self).__init__()
27
28   def Matches(self):
29     """Detects whether this is the correct host implementation.
30
31     This method is overridden by the concrete implementations.
32     Returns:
33       true if the plugin matches the machine it is running on.
34     """
35     return False
36
37   @classmethod
38   def Select(cls, context):
39     for host in cls.Plugins():
40       if host.Matches():
41         return host
42
43   def _Execute(self, context, command,
44                shell=False, capture=False, silent=False,
45                ignore_dry_run=False, return_status=False):
46     """This is the only method that launches external programs.
47
48     It is a thin wrapper around subprocess.Popen that handles cr specific
49     issues. The command is expanded in the context, so that context variables
50     are substituted.
51     Args:
52       context: the cr context to run under.
53       command: the command to run.
54       shell: whether to run the command using the shell.
55       capture: controls wether the output of the command is captured.
56       ignore_dry_run: Normally, if the context is in dry run mode the command is
57         printed but not executed. This flag overrides that behaviour, causing
58         the command to be run anyway.
59       return_status: switches the function to returning the status code rather
60         the output.
61     Returns:
62       the status if return_status is true, or the output if capture is true,
63       otherwise nothing.
64     """
65     with context.Trace():
66       command = [context.Substitute(arg) for arg in command if arg]
67     trail = context.trail
68     if not command:
69       print 'Empty command passed to execute'
70       exit(1)
71     if context.verbose:
72       print ' '.join(command)
73       if context.verbose >= _TRAIL_VERBOSITY:
74         print 'Command expanded the following variables:'
75         for key, value in trail:
76           print '   ', key, '=', value
77     if ignore_dry_run or not context.dry_run:
78       out = None
79       if capture:
80         out = subprocess.PIPE
81       elif silent:
82         out = open(os.devnull, "w")
83       try:
84         p = subprocess.Popen(
85             command, shell=shell,
86             env={k: str(v) for k, v in context.exported.items()},
87             stdout=out)
88       except OSError:
89         print 'Failed to exec', command
90         # Don't log the trail if we already have
91         if context.verbose < _TRAIL_VERBOSITY:
92           print 'Variables used to build the command were:'
93           for key, value in trail:
94             print '   ', key, '=', value
95         exit(1)
96       try:
97         output, _ = p.communicate()
98       except KeyboardInterrupt:
99         p.terminate()
100         p.wait()
101         exit(1)
102       finally:
103         if silent:
104           out.close()
105       if return_status:
106         return p.returncode
107       if p.returncode != 0:
108         print 'Error {0} executing command {1}'.format(p.returncode, command)
109         exit(p.returncode)
110       return output or ''
111     return ''
112
113   @cr.Plugin.activemethod
114   def Shell(self, context, *command):
115     command = ' '.join([pipes.quote(arg) for arg in command])
116     return self._Execute(context, [command], shell=True)
117
118   @cr.Plugin.activemethod
119   def Execute(self, context, *command):
120     return self._Execute(context, command, shell=False)
121
122   @cr.Plugin.activemethod
123   def ExecuteSilently(self, context, *command):
124     return self._Execute(context, command, shell=False, silent=True)
125
126   @cr.Plugin.activemethod
127   def CaptureShell(self, context, *command):
128     return self._Execute(context, command,
129                          shell=True, capture=True, ignore_dry_run=True)
130
131   @cr.Plugin.activemethod
132   def Capture(self, context, *command):
133     return self._Execute(context, command, capture=True, ignore_dry_run=True)
134
135   @cr.Plugin.activemethod
136   def ExecuteStatus(self, context, *command):
137     return self._Execute(context, command,
138                          ignore_dry_run=True, return_status=True)
139
140   @cr.Plugin.activemethod
141   def YesNo(self, question, default=True):
142     """Ask the user a yes no question
143
144     This blocks until the user responds.
145     Args:
146       question: The question string to show the user
147       default: True if the default response is Yes
148     Returns:
149       True if the response was yes.
150     """
151     options = 'Y/n' if default else 'y/N'
152     result = raw_input(question + ' [' + options + '] ').lower()
153     if result == '':
154       return default
155     return result in ['y', 'yes']
156
157   @classmethod
158   def SearchPath(cls, name):
159     """Searches the PATH for an executable.
160
161     Args:
162       name: the name of the binary to search for.
163     Returns:
164       the set of executables found, or an empty list if none.
165     """
166     result = []
167     extensions = ['']
168     extensions.extend(os.environ.get('PATHEXT', '').split(os.pathsep))
169     for path in os.environ.get('PATH', '').split(os.pathsep):
170       partial = os.path.join(path, name)
171       for extension in extensions:
172         filename = partial + extension
173         if os.path.exists(filename) and filename not in result:
174           result.append(filename)
175     return result