Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(916)

Side by Side Diff: scripts/slave/recipe_modules/path/api.py

Issue 24737002: Add Paths as first-class types in configs. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import os 5 import os
6 import functools
7
6 from slave import recipe_api 8 from slave import recipe_api
9 from slave import recipe_config_types
7 10
8 11
9 def path_method(api, name, base): 12 def PathHandler(api, test):
10 """Returns a shortcut static method which functions like os.path.join but 13 def PathHandler_inner(path):
11 with a fixed first component |base|. 14 assert isinstance(path, recipe_config_types.Path)
12 """ 15 suffix = ''
13 def path_func_inner(*pieces, **kwargs): 16 if path.wrapper and api.m.platform.is_win:
14 """Return a path to a file in '%s'. 17 suffix = '.bat'
15 18 base_path = None
16 It supports the following kwargs: 19 if path.base in api.c.dynamic_paths:
17 wrapper (bool): If true, the path should be considered to be a wrapper 20 base_path = api.c.dynamic_paths[path.base]
18 script, and will gain the appropriate '.bat' extension 21 elif path.base in api.c.base_paths:
19 on windows. 22 if test.enabled:
20 """ 23 # TODO(iannucci): Remove special case in followup cl
21 use_wrapper = kwargs.get('wrapper') and api.m.platform.is_win 24 if path.base == 'root':
22 WRAPPER_EXTENSION = '.bat' if use_wrapper else '' 25 base_path = '[ROOT]'
23 assert api.pardir not in pieces 26 else:
24 return api.join(base, *filter(bool, pieces)) + WRAPPER_EXTENSION 27 base_path = '[%s_ROOT]' % path.base.upper()
25 path_func_inner.__name__ = name 28 else: # pragma: no cover
26 path_func_inner.__doc__ = path_func_inner.__doc__ % base 29 base_path = api.join(*api.c.base_paths[path.base])
27 return path_func_inner 30 assert base_path, 'Could not get base %r for path' % path.base
31 return api.join(base_path, *path.pieces) + suffix
32 return PathHandler_inner
28 33
29 34
30 class mock_path(object): 35 def string_filter(func):
36 @functools.wraps(func)
37 def inner(*args, **kwargs):
38 return func(*map(str, args), **kwargs)
39 return inner
40
41
42 class fake_path(object):
31 """Standin for os.path when we're in test mode. 43 """Standin for os.path when we're in test mode.
32 44
33 This class simulates the os.path interface exposed by PathApi, respecting the 45 This class simulates the os.path interface exposed by PathApi, respecting the
34 current platform according to the `platform` module. This allows us to 46 current platform according to the `platform` module. This allows us to
35 simulate path functions according to the platform being tested, rather than 47 simulate path functions according to the platform being tested, rather than
36 the platform which is currently running. 48 the platform which is currently running.
37 """ 49 """
38 50
39 def __init__(self, api, _mock_path_exists): 51 def __init__(self, api, _mock_path_exists):
40 self._api = api 52 self._api = api
41 self._mock_path_exists = set(_mock_path_exists) 53 self._mock_path_exists = set(_mock_path_exists)
42 self._pth = None 54 self._pth = None
43 55
44 def __getattr__(self, name): 56 def __getattr__(self, name):
45 if not self._pth: 57 if not self._pth:
46 if self._api.platform.is_win: 58 if self._api.m.platform.is_win:
47 import ntpath as pth 59 import ntpath as pth
48 elif self._api.platform.is_mac or self._api.platform.is_linux: 60 elif self._api.m.platform.is_mac or self._api.m.platform.is_linux:
49 import posixpath as pth 61 import posixpath as pth
50 self._pth = pth 62 self._pth = pth
51 return getattr(self._pth, name) 63 return getattr(self._pth, name)
52 64
53 def _initialize_exists(self): # pylint: disable=E0202 65 def _initialize_exists(self): # pylint: disable=E0202
54 """ 66 """
55 Calculates all the parent paths of the mock'd paths and makes exists() 67 Calculates all the parent paths of the mock'd paths and makes exists()
56 read from this new set(). 68 read from this new set().
57 """ 69 """
58 self._initialize_exists = lambda: None 70 self._initialize_exists = lambda: None
59 for path in list(self._mock_path_exists): 71 for path in list(self._mock_path_exists):
60 self.mock_add_paths(path) 72 self.mock_add_paths(path)
61 self.exists = lambda path: path in self._mock_path_exists 73 self.exists = lambda path: path in self._mock_path_exists
62 74
63 def mock_add_paths(self, path): 75 def mock_add_paths(self, path):
64 """ 76 """
65 Adds a path and all of its parents to the set of existing paths. 77 Adds a path and all of its parents to the set of existing paths.
66 """ 78 """
67 self._initialize_exists() 79 self._initialize_exists()
80 path = str(path)
68 while path: 81 while path:
69 self._mock_path_exists.add(path) 82 self._mock_path_exists.add(path)
70 path = self.dirname(path) 83 path = self.dirname(path)
71 84
72 def exists(self, path): # pylint: disable=E0202 85 def exists(self, path): # pylint: disable=E0202
73 """Return True if path refers to an existing path.""" 86 """Return True if path refers to an existing path."""
74 self._initialize_exists() 87 self._initialize_exists()
75 return self.exists(path) 88 return self.exists(path)
76 89
77 def abspath(self, path): 90 def abspath(self, path):
78 """Returns the absolute version of path.""" 91 """Returns the absolute version of path."""
79 path = self.normpath(path) 92 path = self.normpath(path)
80 if path[0] != '[': # pragma: no cover 93 if path[0] != '[': # pragma: no cover
81 # We should never really hit this, but simulate the effect. 94 # We should never really hit this, but simulate the effect.
82 return self.api.slave_build(path) 95 return self.api.slave_build(path)
83 else: 96 else:
84 return path 97 return path
85 98
86 99
87 class PathApi(recipe_api.RecipeApi): 100 class PathApi(recipe_api.RecipeApi):
88 """ 101 """
89 PathApi provides common os.path functions as well as convenience functions 102 PathApi provides common os.path functions as well as convenience functions
90 for generating absolute paths to things in a testable way. 103 for generating absolute paths to things in a testable way.
91 104
92 Mocks: 105 Mocks:
93 exists (list): Paths which should exist in the test case. Thes must be paths 106 exists (list): Paths which should exist in the test case. Thes must be paths
94 using the [*_ROOT] placeholders. ex. '[BUILD_ROOT]/scripts'. 107 using the [*_ROOT] placeholders. ex. '[BUILD_ROOT]/scripts'.
95 """ 108 """
96 109
97 OK_METHODS = ('abspath', 'basename', 'exists', 'join', 'pardir', 110 OK_ATTRS = ('pardir', 'sep', 'pathsep')
98 'pathsep', 'sep', 'split', 'splitext') 111
112 # Because the native 'path' type in python is a str, we filter the *args
113 # of these methods to stringify them first (otherwise they would be getting
114 # recipe_util_types.Path instances).
115 FILTER_METHODS = ('abspath', 'basename', 'exists', 'join', 'split',
agable 2013/09/26 21:46:02 Could still call them "OK_METHODS", though, since
iannucci 2013/09/27 02:08:20 Yeah, but I wanted to be extra-clear, and we just
agable 2013/09/27 17:48:16 I would actually prefer that it be called OK_METHO
116 'splitext')
117
118 def get_config_defaults(self):
119 return { 'CURRENT_WORKING_DIR': self._startup_cwd }
99 120
100 def __init__(self, **kwargs): 121 def __init__(self, **kwargs):
101 super(PathApi, self).__init__(**kwargs) 122 super(PathApi, self).__init__(**kwargs)
123 recipe_config_types.Path.set_handler(PathHandler(self, self._test_data))
102 124
103 if not self._test_data.enabled: # pragma: no cover 125 if not self._test_data.enabled: # pragma: no cover
104 self._path_mod = os.path 126 self._path_mod = os.path
105 # e.g. /b/build/slave/<slavename>/build 127 # Capture the cwd on process start to avoid shenanigans.
106 self.slave_build = path_method( 128 startup_cwd = os.path.abspath(os.getcwd()).split(os.path.sep)
107 self, 'slave_build', self.abspath(os.getcwd())) 129 # Guarantee that the firt element is an absolute drive or the posix root.
130 if ':' in startup_cwd[0]:
agable 2013/09/26 21:46:02 if startup_cwd[0].endswith(':'): Also, are windows
iannucci 2013/09/27 02:08:20 Done
131 startup_cwd[0] += '\\'
132 elif startup_cwd[0] == '':
133 startup_cwd[0] = '/'
134 else:
135 assert False, 'What is this, I don\'t even: %r' % startup_cwd
agable 2013/09/26 21:46:02 As much as I approve... error strings should proba
iannucci 2013/09/27 02:08:20 :( Done. :(
136 self._startup_cwd = startup_cwd
137 else:
138 self._path_mod = fake_path(self, self._test_data.get('exists', []))
139 self._startup_cwd = ['/', 'BAG OF CATS']
agable 2013/09/26 21:46:02 'FAKE TESTING CWD'? '------------M-A-G-I-C---D-I-V
iannucci 2013/09/27 02:08:20 Done. Though I was very tempted by 'FUNGIBLE BUNNY
108 140
109 # e.g. /b 141 # For now everything works on buildbot, so set it 'automatically' here.
110 r = self.abspath(self.join(self.slave_build(), *([self.pardir]*4))) 142 self.set_config('buildbot', include_deps=False)
111 for token in ('build_internal', 'build', 'depot_tools'):
112 # e.g. /b/{token}
113 setattr(self, token, path_method(self, token, self.join(r, token)))
114 self.root = path_method(self, 'root', r)
115 else:
116 self._path_mod = mock_path(self.m, self._test_data.get('exists', []))
117 self.slave_build = path_method(self, 'slave_build', '[SLAVE_BUILD_ROOT]')
118 self.build_internal = path_method(
119 self, 'build_internal', '[BUILD_INTERNAL_ROOT]')
120 self.build = path_method(self, 'build', '[BUILD_ROOT]')
121 self.depot_tools = path_method(self, 'depot_tools', '[DEPOT_TOOLS_ROOT]')
122 self.root = path_method(self, 'root', '[ROOT]')
123
124 # Because it only makes sense to call self.checkout() after
125 # a checkout has been defined, make calls to self.checkout()
126 # explode with a helpful message until that point.
127 def _boom(*_args, **_kwargs): # pragma: no cover
128 assert False, ('Cannot call path.checkout() without calling '
129 'path.add_checkout()')
130
131 self._checkouts = []
132 self._checkout = _boom
133
134 def checkout(self, *args, **kwargs):
135 """
136 Build a path into the checked out source.
137
138 The checked out source is often a forest of trees possibly inside other
139 trees. One of these trees' root is designated as special/primary and
140 this method builds paths inside of it. For Chrome, that would be 'src'.
141 This defaults to the special root of the first checkout.
142 """
143 return self._checkout(*args, **kwargs)
144 143
145 def mock_add_paths(self, path): 144 def mock_add_paths(self, path):
146 """For testing purposes, assert that |path| exists.""" 145 """For testing purposes, assert that |path| exists."""
147 if self._test_data.enabled: 146 if self._test_data.enabled:
148 self._path_mod.mock_add_paths(path) 147 self._path_mod.mock_add_paths(path)
149 148
150 def add_checkout(self, checkout, *pieces):
151 """Assert that we have a source directory with this name. """
152 checkout = self.join(checkout, *pieces)
153 self.assert_absolute(checkout)
154 if not self._checkouts:
155 self._checkout = path_method(self, 'checkout', checkout)
156 self._checkouts.append(checkout)
157
158 def choose_checkout(self, checkout, *pieces): # pragma: no cover
159 assert checkout in self._checkouts, 'No such checkout'
160 checkout = self.join(checkout, *pieces)
161 self.assert_absolute(checkout)
162 self._checkout = path_method(self, 'checkout', checkout)
163
164 def assert_absolute(self, path): 149 def assert_absolute(self, path):
165 assert self.abspath(path) == path, '%s is not absolute' % path 150 assert self.abspath(path) == str(path), '%s is not absolute' % path
166 151
167 def makedirs(self, name, path, mode=0777): 152 def makedirs(self, name, path, mode=0777):
168 """ 153 """
169 Like os.makedirs, except that if the directory exists, then there is no 154 Like os.makedirs, except that if the directory exists, then there is no
170 error. 155 error.
171 """ 156 """
172 self.assert_absolute(path) 157 self.assert_absolute(path)
173 yield self.m.python.inline( 158 yield self.m.python.inline(
174 'makedirs ' + name, 159 'makedirs ' + name,
175 """ 160 """
176 import sys, os 161 import sys, os
177 path = sys.argv[1] 162 path = sys.argv[1]
178 mode = int(sys.argv[2]) 163 mode = int(sys.argv[2])
179 if not os.path.isdir(path): 164 if not os.path.isdir(path):
180 if os.path.exists(path): 165 if os.path.exists(path):
181 print "%s exists but is not a dir" % path 166 print "%s exists but is not a dir" % path
182 sys.exit(1) 167 sys.exit(1)
183 os.makedirs(path, mode) 168 os.makedirs(path, mode)
184 """, 169 """,
185 args=[path, str(mode)], 170 args=[path, str(mode)],
186 ) 171 )
187 self.mock_add_paths(path) 172 self.mock_add_paths(path)
188 173
174 def set_dynamic_path(self, pathname, path, default=False):
175 assert isinstance(path, recipe_config_types.Path), (
176 'Setting dynamic path to something other than a Path: %r' % path)
177 assert pathname in self.c.dynamic_paths, (
178 'Must declare dynamic path (%r) in config before setting it.' % path)
179 assert path.base in self.c.base_paths, (
180 'Dynamic path values must be based on a base_path.')
181 if default and self.c.dynamic_paths.get(pathname):
agable 2013/09/26 21:46:02 'default' is insufficiently descriptive. Maybe 'ov
iannucci 2013/09/27 02:08:20 Done.
182 return
183 self.c.dynamic_paths[pathname] = path
184
189 def __getattr__(self, name): 185 def __getattr__(self, name):
190 if name in self.OK_METHODS: 186 if name in self.c.dynamic_paths:
187 r = self.c.dynamic_paths[name]
188 if r is None:
189 # Pass back a Path referring to this dynamic path in order to late-bind
190 # it. Attempting to evaluate this path as a string before it's set is
191 # an error.
192 r = recipe_config_types.Path(name)
193 return r
194 if name in self.c.base_paths:
195 return recipe_config_types.Path(name)
196 if name in self.OK_ATTRS:
191 return getattr(self._path_mod, name) 197 return getattr(self._path_mod, name)
198 if name in self.FILTER_METHODS:
199 return string_filter(getattr(self._path_mod, name))
192 raise AttributeError("'%s' object has no attribute '%s'" % 200 raise AttributeError("'%s' object has no attribute '%s'" %
193 (self._path_mod, name)) # pragma: no cover 201 (self._path_mod, name)) # pragma: no cover
194 202
195 def __dir__(self): # pragma: no cover 203 def __dir__(self): # pragma: no cover
196 # Used for helping out show_me_the_modules.py 204 # Used for helping out show_me_the_modules.py
197 return self.__dict__.keys() + list(self.OK_METHODS) 205 return self.__dict__.keys() + list(self.OK_METHODS)
198
199 # TODO(iannucci): Custom paths?
agable 2013/09/26 21:46:02 Yayyy.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698