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

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: Address comments 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 PathTostring(api, test):
10 """Returns a shortcut static method which functions like os.path.join but 13 def PathTostring_inner(path):
11 with a fixed first component |base|. 14 assert isinstance(path, recipe_config_types.Path)
12 """ 15 base_path = None
13 def path_func_inner(*pieces, **kwargs): 16 suffix = path.platform_ext.get(api.m.platform.name, '')
14 """Return a path to a file in '%s'. 17 if path.base in api.c.dynamic_paths:
15 18 base_path = api.c.dynamic_paths[path.base]
16 It supports the following kwargs: 19 elif path.base in api.c.base_paths:
17 wrapper (bool): If true, the path should be considered to be a wrapper 20 if test.enabled:
18 script, and will gain the appropriate '.bat' extension 21 # TODO(iannucci): Remove special case in followup cl
19 on windows. 22 if path.base == 'root':
20 """ 23 base_path = '[ROOT]'
21 use_wrapper = kwargs.get('wrapper') and api.m.platform.is_win 24 else:
22 WRAPPER_EXTENSION = '.bat' if use_wrapper else '' 25 base_path = '[%s_ROOT]' % path.base.upper()
23 assert api.pardir not in pieces 26 else: # pragma: no cover
24 return api.join(base, *filter(bool, pieces)) + WRAPPER_EXTENSION 27 base_path = api.join(*api.c.base_paths[path.base])
25 path_func_inner.__name__ = name 28 assert base_path, 'Could not get base %r for path' % path.base
26 path_func_inner.__doc__ = path_func_inner.__doc__ % base 29 return api.join(base_path, *path.pieces) + suffix
27 return path_func_inner 30 return PathTostring_inner
28 31
29 32
30 class mock_path(object): 33 def string_filter(func):
34 @functools.wraps(func)
35 def inner(*args, **kwargs):
36 return func(*map(str, args), **kwargs)
37 return inner
38
39
40 class fake_path(object):
31 """Standin for os.path when we're in test mode. 41 """Standin for os.path when we're in test mode.
32 42
33 This class simulates the os.path interface exposed by PathApi, respecting the 43 This class simulates the os.path interface exposed by PathApi, respecting the
34 current platform according to the `platform` module. This allows us to 44 current platform according to the `platform` module. This allows us to
35 simulate path functions according to the platform being tested, rather than 45 simulate path functions according to the platform being tested, rather than
36 the platform which is currently running. 46 the platform which is currently running.
37 """ 47 """
38 48
39 def __init__(self, api, _mock_path_exists): 49 def __init__(self, api, _mock_path_exists):
40 self._api = api 50 self._api = api
41 self._mock_path_exists = set(_mock_path_exists) 51 self._mock_path_exists = set(_mock_path_exists)
42 self._pth = None 52 self._pth = None
43 53
44 def __getattr__(self, name): 54 def __getattr__(self, name):
45 if not self._pth: 55 if not self._pth:
46 if self._api.platform.is_win: 56 if self._api.m.platform.is_win:
47 import ntpath as pth 57 import ntpath as pth
48 elif self._api.platform.is_mac or self._api.platform.is_linux: 58 elif self._api.m.platform.is_mac or self._api.m.platform.is_linux:
49 import posixpath as pth 59 import posixpath as pth
50 self._pth = pth 60 self._pth = pth
51 return getattr(self._pth, name) 61 return getattr(self._pth, name)
52 62
53 def _initialize_exists(self): # pylint: disable=E0202 63 def _initialize_exists(self): # pylint: disable=E0202
54 """ 64 """
55 Calculates all the parent paths of the mock'd paths and makes exists() 65 Calculates all the parent paths of the mock'd paths and makes exists()
56 read from this new set(). 66 read from this new set().
57 """ 67 """
58 self._initialize_exists = lambda: None 68 self._initialize_exists = lambda: None
59 for path in list(self._mock_path_exists): 69 for path in list(self._mock_path_exists):
60 self.mock_add_paths(path) 70 self.mock_add_paths(path)
61 self.exists = lambda path: path in self._mock_path_exists 71 self.exists = lambda path: path in self._mock_path_exists
62 72
63 def mock_add_paths(self, path): 73 def mock_add_paths(self, path):
64 """ 74 """
65 Adds a path and all of its parents to the set of existing paths. 75 Adds a path and all of its parents to the set of existing paths.
66 """ 76 """
67 self._initialize_exists() 77 self._initialize_exists()
78 path = str(path)
68 while path: 79 while path:
69 self._mock_path_exists.add(path) 80 self._mock_path_exists.add(path)
70 path = self.dirname(path) 81 path = self.dirname(path)
71 82
72 def exists(self, path): # pylint: disable=E0202 83 def exists(self, path): # pylint: disable=E0202
73 """Return True if path refers to an existing path.""" 84 """Return True if path refers to an existing path."""
74 self._initialize_exists() 85 self._initialize_exists()
75 return self.exists(path) 86 return self.exists(path)
76 87
77 def abspath(self, path): 88 def abspath(self, path):
78 """Returns the absolute version of path.""" 89 """Returns the absolute version of path."""
79 path = self.normpath(path) 90 path = self.normpath(path)
80 if path[0] != '[': # pragma: no cover 91 if path[0] != '[': # pragma: no cover
81 # We should never really hit this, but simulate the effect. 92 # We should never really hit this, but simulate the effect.
82 return self.api.slave_build(path) 93 return self.api.slave_build(path)
83 else: 94 else:
84 return path 95 return path
85 96
86 97
87 class PathApi(recipe_api.RecipeApi): 98 class PathApi(recipe_api.RecipeApi):
88 """ 99 """
89 PathApi provides common os.path functions as well as convenience functions 100 PathApi provides common os.path functions as well as convenience functions
90 for generating absolute paths to things in a testable way. 101 for generating absolute paths to things in a testable way.
91 102
92 Mocks: 103 Mocks:
93 exists (list): Paths which should exist in the test case. Thes must be paths 104 exists (list): Paths which should exist in the test case. Thes must be paths
94 using the [*_ROOT] placeholders. ex. '[BUILD_ROOT]/scripts'. 105 using the [*_ROOT] placeholders. ex. '[BUILD_ROOT]/scripts'.
95 """ 106 """
96 107
97 OK_METHODS = ('abspath', 'basename', 'exists', 'join', 'pardir', 108 OK_ATTRS = ('pardir', 'sep', 'pathsep')
98 'pathsep', 'sep', 'split', 'splitext') 109
110 # Because the native 'path' type in python is a str, we filter the *args
111 # of these methods to stringify them first (otherwise they would be getting
112 # recipe_util_types.Path instances).
113 FILTER_METHODS = ('abspath', 'basename', 'exists', 'join', 'split',
114 'splitext')
115
116 def get_config_defaults(self):
117 return { 'CURRENT_WORKING_DIR': self._startup_cwd }
99 118
100 def __init__(self, **kwargs): 119 def __init__(self, **kwargs):
101 super(PathApi, self).__init__(**kwargs) 120 super(PathApi, self).__init__(**kwargs)
121 recipe_config_types.Path.set_tostring_fn(
122 PathTostring(self, self._test_data))
102 123
103 if not self._test_data.enabled: # pragma: no cover 124 if not self._test_data.enabled: # pragma: no cover
104 self._path_mod = os.path 125 self._path_mod = os.path
105 # e.g. /b/build/slave/<slavename>/build 126 # Capture the cwd on process start to avoid shenanigans.
106 self.slave_build = path_method( 127 startup_cwd = os.path.abspath(os.getcwd()).split(os.path.sep)
107 self, 'slave_build', self.abspath(os.getcwd())) 128 # Guarantee that the firt element is an absolute drive or the posix root.
129 if startup_cwd[0].endswith(':'):
130 startup_cwd[0] += '\\'
131 elif startup_cwd[0] == '':
132 startup_cwd[0] = '/'
133 else:
134 assert False, 'Got unexpected startup_cwd format: %r' % startup_cwd
135 self._startup_cwd = startup_cwd
136 else:
137 self._path_mod = fake_path(self, self._test_data.get('exists', []))
138 self._startup_cwd = ['/', 'FakeTestingCWD']
108 139
109 # e.g. /b 140 # For now everything works on buildbot, so set it 'automatically' here.
110 r = self.abspath(self.join(self.slave_build(), *([self.pardir]*4))) 141 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 142
145 def mock_add_paths(self, path): 143 def mock_add_paths(self, path):
146 """For testing purposes, assert that |path| exists.""" 144 """For testing purposes, assert that |path| exists."""
147 if self._test_data.enabled: 145 if self._test_data.enabled:
148 self._path_mod.mock_add_paths(path) 146 self._path_mod.mock_add_paths(path)
149 147
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): 148 def assert_absolute(self, path):
165 assert self.abspath(path) == path, '%s is not absolute' % path 149 assert self.abspath(path) == str(path), '%s is not absolute' % path
166 150
167 def makedirs(self, name, path, mode=0777): 151 def makedirs(self, name, path, mode=0777):
168 """ 152 """
169 Like os.makedirs, except that if the directory exists, then there is no 153 Like os.makedirs, except that if the directory exists, then there is no
170 error. 154 error.
171 """ 155 """
172 self.assert_absolute(path) 156 self.assert_absolute(path)
173 yield self.m.python.inline( 157 yield self.m.python.inline(
174 'makedirs ' + name, 158 'makedirs ' + name,
175 """ 159 """
176 import sys, os 160 import sys, os
177 path = sys.argv[1] 161 path = sys.argv[1]
178 mode = int(sys.argv[2]) 162 mode = int(sys.argv[2])
179 if not os.path.isdir(path): 163 if not os.path.isdir(path):
180 if os.path.exists(path): 164 if os.path.exists(path):
181 print "%s exists but is not a dir" % path 165 print "%s exists but is not a dir" % path
182 sys.exit(1) 166 sys.exit(1)
183 os.makedirs(path, mode) 167 os.makedirs(path, mode)
184 """, 168 """,
185 args=[path, str(mode)], 169 args=[path, str(mode)],
186 ) 170 )
187 self.mock_add_paths(path) 171 self.mock_add_paths(path)
188 172
173 def set_dynamic_path(self, pathname, path, overwrite=True):
174 """Set a named dynamic path to a concrete value.
175 * path must be based on a real base_path (not another dynamic path)
176 * if overwrite is False and the path is already set, do nothing.
177 """
178 assert isinstance(path, recipe_config_types.Path), (
179 'Setting dynamic path to something other than a Path: %r' % path)
180 assert pathname in self.c.dynamic_paths, (
181 'Must declare dynamic path (%r) in config before setting it.' % path)
182 assert path.base in self.c.base_paths, (
183 'Dynamic path values must be based on a base_path.')
184 if not overwrite and self.c.dynamic_paths.get(pathname):
185 return
186 self.c.dynamic_paths[pathname] = path
187
189 def __getattr__(self, name): 188 def __getattr__(self, name):
190 if name in self.OK_METHODS: 189 if name in self.c.dynamic_paths:
190 r = self.c.dynamic_paths[name]
191 if r is None:
192 # Pass back a Path referring to this dynamic path in order to late-bind
193 # it. Attempting to evaluate this path as a string before it's set is
194 # an error.
195 r = recipe_config_types.Path(name)
196 return r
197 if name in self.c.base_paths:
198 return recipe_config_types.Path(name)
199 if name in self.OK_ATTRS:
191 return getattr(self._path_mod, name) 200 return getattr(self._path_mod, name)
201 if name in self.FILTER_METHODS:
202 return string_filter(getattr(self._path_mod, name))
192 raise AttributeError("'%s' object has no attribute '%s'" % 203 raise AttributeError("'%s' object has no attribute '%s'" %
193 (self._path_mod, name)) # pragma: no cover 204 (self._path_mod, name)) # pragma: no cover
194 205
195 def __dir__(self): # pragma: no cover 206 def __dir__(self): # pragma: no cover
196 # Used for helping out show_me_the_modules.py 207 # Used for helping out show_me_the_modules.py
197 return self.__dict__.keys() + list(self.OK_METHODS) 208 return self.__dict__.keys() + list(self.OK_METHODS)
198
199 # TODO(iannucci): Custom paths?
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698