host.py revision 010d83a9304c5a91596085d917d248abff47903a
1f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)# found in the LICENSE file.
4f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
5f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)"""Module for build host support."""
6f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
7f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import os
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import pipes
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import signal
10f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import subprocess
11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
12f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)import cr
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
14f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)# Controls what verbosity level turns on command trail logging
15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)_TRAIL_VERBOSITY = 2
16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
17010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)def PrintTrail(trail):
18010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  print 'Command expanded the following variables:'
19010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  for key, value in trail:
20010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    if value == None:
21010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      value = ''
22010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    print '   ', key, '=', value
23010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class Host(cr.Plugin, cr.Plugin.Type):
26f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """Base class for implementing cr hosts.
27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  The host is the main access point to services provided by the machine cr
29f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  is running on. It exposes information about the machine, and runs external
30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  commands on behalf of the actions.
31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  """
32f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def __init__(self):
34f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    super(Host, self).__init__()
35f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
36f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def Matches(self):
37f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Detects whether this is the correct host implementation.
38f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    This method is overridden by the concrete implementations.
40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Returns:
41f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      true if the plugin matches the machine it is running on.
42f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
43f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return False
44f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
45f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @classmethod
46effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def Select(cls):
47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    for host in cls.Plugins():
48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if host.Matches():
49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return host
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
51effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def _Execute(self, command,
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               shell=False, capture=False, silent=False,
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               ignore_dry_run=False, return_status=False,
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               ignore_interrupt_signal=False):
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """This is the only method that launches external programs.
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    It is a thin wrapper around subprocess.Popen that handles cr specific
58effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    issues. The command is expanded in the active context so that variables
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    are substituted.
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Args:
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      command: the command to run.
62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      shell: whether to run the command using the shell.
63f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      capture: controls wether the output of the command is captured.
64f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      ignore_dry_run: Normally, if the context is in dry run mode the command is
65f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        printed but not executed. This flag overrides that behaviour, causing
66f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        the command to be run anyway.
67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return_status: switches the function to returning the status code rather
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        the output.
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      ignore_interrupt_signal: Ignore the interrupt signal (i.e., Ctrl-C) while
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        the command is running. Useful for letting interactive programs manage
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Ctrl-C by themselves.
72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Returns:
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      the status if return_status is true, or the output if capture is true,
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      otherwise nothing.
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
76effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    with cr.context.Trace():
77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      command = [cr.context.Substitute(arg) for arg in command if arg]
78effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    trail = cr.context.trail
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if not command:
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      print 'Empty command passed to execute'
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      exit(1)
82effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    if cr.context.verbose:
83f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      print ' '.join(command)
84effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      if cr.context.verbose >= _TRAIL_VERBOSITY:
85010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        PrintTrail(trail)
86effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    if ignore_dry_run or not cr.context.dry_run:
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      out = None
88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if capture:
89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        out = subprocess.PIPE
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      elif silent:
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        out = open(os.devnull, "w")
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
93f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        p = subprocess.Popen(
94f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            command, shell=shell,
95effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch            env={k: str(v) for k, v in cr.context.exported.items()},
96f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            stdout=out)
97f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      except OSError:
98f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        print 'Failed to exec', command
99f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # Don't log the trail if we already have
100effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        if cr.context.verbose < _TRAIL_VERBOSITY:
101010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          PrintTrail(trail)
102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        exit(1)
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      try:
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if ignore_interrupt_signal:
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          signal.signal(signal.SIGINT, signal.SIG_IGN)
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        output, _ = p.communicate()
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      finally:
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if ignore_interrupt_signal:
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          signal.signal(signal.SIGINT, signal.SIG_DFL)
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if silent:
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          out.close()
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if return_status:
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return p.returncode
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if p.returncode != 0:
115f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        print 'Error {0} executing command {1}'.format(p.returncode, command)
116f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        exit(p.returncode)
117f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return output or ''
118f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return ''
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @cr.Plugin.activemethod
121effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def Shell(self, *command):
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    command = ' '.join([pipes.quote(arg) for arg in command])
123effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute([command], shell=True, ignore_interrupt_signal=True)
124f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
125f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @cr.Plugin.activemethod
126effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def Execute(self, *command):
127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute(command, shell=False)
128f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
129f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @cr.Plugin.activemethod
130effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def ExecuteSilently(self, *command):
131effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute(command, shell=False, silent=True)
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  @cr.Plugin.activemethod
134effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def CaptureShell(self, *command):
135effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute(command,
136f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                         shell=True, capture=True, ignore_dry_run=True)
137f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @cr.Plugin.activemethod
139effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def Capture(self, *command):
140effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute(command, capture=True, ignore_dry_run=True)
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @cr.Plugin.activemethod
143effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def ExecuteStatus(self, *command):
144effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return self._Execute(command,
145f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                         ignore_dry_run=True, return_status=True)
146f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  @cr.Plugin.activemethod
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def YesNo(self, question, default=True):
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Ask the user a yes no question
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    This blocks until the user responds.
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Args:
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      question: The question string to show the user
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      default: True if the default response is Yes
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Returns:
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      True if the response was yes.
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    options = 'Y/n' if default else 'y/N'
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    result = raw_input(question + ' [' + options + '] ').lower()
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if result == '':
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return default
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return result in ['y', 'yes']
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
164f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  @classmethod
165effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def SearchPath(cls, name, paths=[]):
166f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """Searches the PATH for an executable.
167f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
168f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Args:
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      name: the name of the binary to search for.
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    Returns:
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      the set of executables found, or an empty list if none.
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    """
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    result = []
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    extensions = ['']
175f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    extensions.extend(os.environ.get('PATHEXT', '').split(os.pathsep))
176effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    paths = [cr.context.Substitute(path) for path in paths if path]
177effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    paths = paths + os.environ.get('PATH', '').split(os.pathsep)
178effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    for path in paths:
179f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      partial = os.path.join(path, name)
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      for extension in extensions:
181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        filename = partial + extension
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        if os.path.exists(filename) and filename not in result:
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          result.append(filename)
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return result
185