summaryrefslogtreecommitdiff
path: root/scripts/wrapper.py
blob: 2715ad8ff49282000e3a72e72d73e840979d4b15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/python2
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Wrapper around chromite executable scripts.

This takes care of creating a consistent environment for chromite scripts
(like setting up import paths) so we don't have to duplicate the logic in
lots of places.
"""

from __future__ import print_function

import os
import sys

CHROMITE_PATH = None


class ChromiteImporter(object):
  """Virtual chromite module

  If the checkout is not named 'chromite', trying to do 'from chromite.xxx'
  to import modules fails horribly.  Instead, manually locate the chromite
  directory (whatever it is named), load & return it whenever someone tries
  to import it.  This lets us use the stable name 'chromite' regardless of
  how things are structured on disk.

  This also lets us keep the sys.path search clean.  Otherwise we'd have to
  worry about what other dirs chromite were checked out near to as doing an
  import would also search those for .py modules.
  """

  # When trying to load the chromite dir from disk, we'll get called again,
  # so make sure to disable our logic to avoid an infinite loop.
  _loading = False

  def find_module(self, fullname, _path=None):
    """Handle the 'chromite' module"""
    if fullname == 'chromite' and not self._loading:
      return self
    return None

  def load_module(self, _fullname):
    """Return our cache of the 'chromite' module"""
    # Locate the top of the chromite dir by searching for the PRESUBMIT.cfg
    # file.  This assumes that file isn't found elsewhere in the tree.
    path = os.path.dirname(os.path.realpath(__file__))
    while not os.path.exists(os.path.join(path, 'PRESUBMIT.cfg')):
      path = os.path.dirname(path)

    # pylint: disable=W0603
    global CHROMITE_PATH
    CHROMITE_PATH = path + '/'

    # Finally load the chromite dir.
    path, mod = os.path.split(path)
    sys.path.insert(0, path)
    self._loading = True
    try:
      # This violates PEP302 slightly because __import__ will return the
      # cached module from sys.modules rather than reloading it from disk.
      # But the imp module does not work cleanly with meta_path currently
      # which makes it hard to use.  Until that is fixed, we won't bother
      # trying to address the edge case since it doesn't matter to us.
      return __import__(mod)
    finally:
      # We can't pop by index as the import might have changed sys.path.
      sys.path.remove(path)
      self._loading = False

sys.meta_path.insert(0, ChromiteImporter())

from chromite.lib import commandline
from chromite.lib import cros_import


def FindTarget(target):
  """Turn the path into something we can import from the chromite tree.

  This supports a variety of ways of running chromite programs:
  # Loaded via depot_tools in $PATH.
  $ cros_sdk --help
  # Loaded via .../chromite/bin in $PATH.
  $ cros --help
  # No $PATH needed.
  $ ./bin/cros --help
  # Loaded via ~/bin in $PATH to chromite bin/ subdir.
  $ ln -s $PWD/bin/cros ~/bin; cros --help
  # No $PATH needed.
  $ ./cbuildbot/cbuildbot --help
  # No $PATH needed, but symlink inside of chromite dir.
  $ ln -s ./cbuildbot/cbuildbot; ./cbuildbot --help
  # Loaded via ~/bin in $PATH to non-chromite bin/ subdir.
  $ ln -s $PWD/cbuildbot/cbuildbot ~/bin/; cbuildbot --help
  # No $PATH needed, but a relative symlink to a symlink to the chromite dir.
  $ cd ~; ln -s bin/cbuildbot ./; ./cbuildbot --help

  Args:
    target: Path to the script we're trying to run.

  Returns:
    The module main functor.
  """
  while True:
    # Walk back one symlink at a time until we get into the chromite dir.
    parent, base = os.path.split(target)
    parent = os.path.realpath(parent)
    if parent.startswith(CHROMITE_PATH):
      target = base
      break
    target = os.path.join(os.path.dirname(target), os.readlink(target))
  assert parent.startswith(CHROMITE_PATH), (
      'could not figure out leading path\n'
      '\tparent: %s\n'
      '\tCHROMITE_PATH: %s' % (parent, CHROMITE_PATH))
  parent = parent[len(CHROMITE_PATH):].split(os.sep)
  target = ['chromite'] + parent + [target]

  if target[-2] == 'bin':
    # Convert <path>/bin/foo -> <path>/scripts/foo.
    target[-2] = 'scripts'
  elif target[1] == 'bootstrap' and len(target) == 3:
    # Convert <git_repo>/bootstrap/foo -> <git_repo>/bootstrap/scripts/foo.
    target.insert(2, 'scripts')

  module = cros_import.ImportModule(target)

  # Run the module's main func if it has one.
  main = getattr(module, 'main', None)
  if main:
    return main

  # Is this a unittest?
  if target[-1].rsplit('_', 1)[-1] in ('test', 'unittest'):
    from chromite.lib import cros_test_lib
    return lambda _argv: cros_test_lib.main(module=module)


if __name__ == '__main__':
  commandline.ScriptWrapperMain(FindTarget)