OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2014 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 | |
6 """Recipe module to ensure a checkout is consistant on a bot.""" | |
7 | |
8 from recipe_engine import recipe_api | |
9 | |
10 | |
11 # This is just for testing, to indicate if a master is using a Git scheduler | |
12 # or not. | |
13 SVN_MASTERS = ( | |
14 'experimental.svn', | |
15 ) | |
16 | |
17 | |
18 def jsonish_to_python(spec, is_top=False): | |
19 """Turn a json spec into a python parsable object. | |
20 | |
21 This exists because Gclient specs, while resembling json, is actually | |
22 ingested using a python "eval()". Therefore a bit of plumming is required | |
23 to turn our newly constructed Gclient spec into a gclient-readable spec. | |
24 """ | |
25 ret = '' | |
26 if is_top: # We're the 'top' level, so treat this dict as a suite. | |
27 ret = '\n'.join( | |
28 '%s = %s' % (k, jsonish_to_python(spec[k])) for k in sorted(spec) | |
29 ) | |
30 else: | |
31 if isinstance(spec, dict): | |
32 ret += '{' | |
33 ret += ', '.join( | |
34 "%s: %s" % (repr(str(k)), jsonish_to_python(spec[k])) | |
35 for k in sorted(spec) | |
36 ) | |
37 ret += '}' | |
38 elif isinstance(spec, list): | |
39 ret += '[' | |
40 ret += ', '.join(jsonish_to_python(x) for x in spec) | |
41 ret += ']' | |
42 elif isinstance(spec, basestring): | |
43 ret = repr(str(spec)) | |
44 else: | |
45 ret = repr(spec) | |
46 return ret | |
47 | |
48 | |
49 class BotUpdateApi(recipe_api.RecipeApi): | |
50 | |
51 def __init__(self, *args, **kwargs): | |
iannucci
2016/01/30 00:40:17
no PROPERTIES ?
| |
52 self._properties = {} | |
53 super(BotUpdateApi, self).__init__(*args, **kwargs) | |
54 | |
55 def __call__(self, name, cmd, **kwargs): | |
56 """Wrapper for easy calling of bot_update.""" | |
57 assert isinstance(cmd, (list, tuple)) | |
58 bot_update_path = self.resource('bot_update.py') | |
59 kwargs.setdefault('infra_step', True) | |
60 return self.m.python(name, bot_update_path, cmd, **kwargs) | |
61 | |
62 @property | |
63 def properties(self): | |
64 return self._properties | |
65 | |
66 def ensure_checkout(self, gclient_config=None, suffix=None, | |
67 patch=True, update_presentation=True, | |
68 force=False, patch_root=None, no_shallow=False, | |
69 with_branch_heads=False, refs=None, | |
70 patch_project_roots=None, patch_oauth2=False, | |
71 output_manifest=True, clobber=False, | |
72 root_solution_revision=None, **kwargs): | |
73 refs = refs or [] | |
74 # We can re-use the gclient spec from the gclient module, since all the | |
75 # data bot_update needs is already configured into the gclient spec. | |
76 cfg = gclient_config or self.m.gclient.c | |
77 spec_string = jsonish_to_python(cfg.as_jsonish(), True) | |
78 | |
79 # Construct our bot_update command. This basically be inclusive of | |
80 # everything required for bot_update to know: | |
81 root = patch_root | |
82 if root is None: | |
83 root = cfg.solutions[0].name | |
84 additional = self.m.rietveld.calculate_issue_root(patch_project_roots) | |
85 if additional: | |
86 root = self.m.path.join(root, additional) | |
87 | |
88 if patch: | |
89 issue = self.m.properties.get('issue') | |
90 patchset = self.m.properties.get('patchset') | |
91 patch_url = self.m.properties.get('patch_url') | |
92 gerrit_repo = self.m.properties.get('repository') | |
93 gerrit_ref = self.m.properties.get('event.patchSet.ref') | |
94 else: | |
95 # The trybot recipe sometimes wants to de-apply the patch. In which case | |
96 # we pretend the issue/patchset/patch_url never existed. | |
97 issue = patchset = patch_url = email_file = key_file = None | |
98 gerrit_repo = gerrit_ref = None | |
99 | |
100 # Issue and patchset must come together. | |
101 if issue: | |
102 assert patchset | |
103 if patchset: | |
104 assert issue | |
105 if patch_url: | |
106 # If patch_url is present, bot_update will actually ignore issue/ps. | |
107 issue = patchset = None | |
108 | |
109 # The gerrit_ref and gerrit_repo must be together or not at all. If one is | |
110 # missing, clear both of them. | |
111 if not gerrit_ref or not gerrit_repo: | |
112 gerrit_repo = gerrit_ref = None | |
113 assert (gerrit_ref != None) == (gerrit_repo != None) | |
114 | |
115 # Point to the oauth2 auth files if specified. | |
116 # These paths are where the bots put their credential files. | |
117 if patch_oauth2: | |
118 email_file = self.m.path['build'].join( | |
119 'site_config', '.rietveld_client_email') | |
120 key_file = self.m.path['build'].join( | |
121 'site_config', '.rietveld_secret_key') | |
122 else: | |
123 email_file = key_file = None | |
124 | |
125 rev_map = {} | |
126 if self.m.gclient.c: | |
127 rev_map = self.m.gclient.c.got_revision_mapping.as_jsonish() | |
128 | |
129 flags = [ | |
130 # 1. What do we want to check out (spec/root/rev/rev_map). | |
131 ['--spec', spec_string], | |
132 ['--root', root], | |
133 ['--revision_mapping_file', self.m.json.input(rev_map)], | |
134 | |
135 # 2. How to find the patch, if any (issue/patchset/patch_url). | |
136 ['--issue', issue], | |
137 ['--patchset', patchset], | |
138 ['--patch_url', patch_url], | |
139 ['--rietveld_server', self.m.properties.get('rietveld')], | |
140 ['--gerrit_repo', gerrit_repo], | |
141 ['--gerrit_ref', gerrit_ref], | |
142 ['--apply_issue_email_file', email_file], | |
143 ['--apply_issue_key_file', key_file], | |
144 | |
145 # 3. Hookups to JSON output back into recipes. | |
146 ['--output_json', self.m.json.output()],] | |
147 | |
148 | |
149 # Collect all fixed revisions to simulate them in the json output. | |
150 # Fixed revision are the explicit input revisions of bot_update.py, i.e. | |
151 # every command line parameter "--revision name@value". | |
152 fixed_revisions = {} | |
153 | |
154 revisions = {} | |
155 for solution in cfg.solutions: | |
156 if solution.revision: | |
157 revisions[solution.name] = solution.revision | |
158 elif solution == cfg.solutions[0]: | |
159 revisions[solution.name] = ( | |
160 self.m.properties.get('parent_got_revision') or | |
161 self.m.properties.get('revision') or | |
162 'HEAD') | |
163 if self.m.gclient.c and self.m.gclient.c.revisions: | |
164 revisions.update(self.m.gclient.c.revisions) | |
165 if cfg.solutions and root_solution_revision: | |
166 revisions[cfg.solutions[0].name] = root_solution_revision | |
167 # Allow for overrides required to bisect into rolls. | |
168 revisions.update(self.m.properties.get('deps_revision_overrides', {})) | |
169 for name, revision in sorted(revisions.items()): | |
170 fixed_revision = self.m.gclient.resolve_revision(revision) | |
171 if fixed_revision: | |
172 fixed_revisions[name] = fixed_revision | |
173 flags.append(['--revision', '%s@%s' % (name, fixed_revision)]) | |
174 | |
175 # Add extra fetch refspecs. | |
176 for ref in refs: | |
177 flags.append(['--refs', ref]) | |
178 | |
179 # Filter out flags that are None. | |
180 cmd = [item for flag_set in flags | |
181 for item in flag_set if flag_set[1] is not None] | |
182 | |
183 if clobber: | |
184 cmd.append('--clobber') | |
185 if force: | |
186 cmd.append('--force') | |
187 if no_shallow: | |
188 cmd.append('--no_shallow') | |
189 if output_manifest: | |
190 cmd.append('--output_manifest') | |
191 if with_branch_heads or cfg.with_branch_heads: | |
192 cmd.append('--with_branch_heads') | |
193 | |
194 # Inject Json output for testing. | |
195 git_mode = self.m.properties.get('mastername') not in SVN_MASTERS | |
196 first_sln = cfg.solutions[0].name | |
197 step_test_data = lambda: self.test_api.output_json( | |
198 root, first_sln, rev_map, git_mode, force, | |
199 self.m.properties.get('fail_patch', False), | |
200 output_manifest=output_manifest, fixed_revisions=fixed_revisions) | |
201 | |
202 # Add suffixes to the step name, if specified. | |
203 name = 'bot_update' | |
204 if not patch: | |
205 name += ' (without patch)' | |
206 if suffix: | |
207 name += ' - %s' % suffix | |
208 | |
209 # Ah hah! Now that everything is in place, lets run bot_update! | |
210 try: | |
211 # 87 and 88 are the 'patch failure' codes for patch download and patch | |
212 # apply, respectively. We don't actually use the error codes, and instead | |
213 # rely on emitted json to determine cause of failure. | |
214 self(name, cmd, step_test_data=step_test_data, | |
215 ok_ret=(0, 87, 88), **kwargs) | |
216 finally: | |
217 step_result = self.m.step.active_result | |
218 self._properties = step_result.json.output.get('properties', {}) | |
219 | |
220 if update_presentation: | |
221 # Set properties such as got_revision. | |
222 for prop_name, prop_value in self.properties.iteritems(): | |
223 step_result.presentation.properties[prop_name] = prop_value | |
224 # Add helpful step description in the step UI. | |
225 if 'step_text' in step_result.json.output: | |
226 step_text = step_result.json.output['step_text'] | |
227 step_result.presentation.step_text = step_text | |
228 # Add log line output. | |
229 if 'log_lines' in step_result.json.output: | |
230 for log_name, log_lines in step_result.json.output['log_lines']: | |
231 step_result.presentation.logs[log_name] = log_lines.splitlines() | |
232 | |
233 # Set the "checkout" path for the main solution. | |
234 # This is used by the Chromium module to figure out where to look for | |
235 # the checkout. | |
236 # If there is a patch failure, emit another step that said things failed. | |
237 if step_result.json.output.get('patch_failure'): | |
238 return_code = step_result.json.output.get('patch_apply_return_code') | |
239 if return_code == 3: | |
240 # This is download failure, hence an infra failure. | |
241 # Sadly, python.failing_step doesn't support kwargs. | |
242 self.m.python.inline( | |
243 'Patch failure', | |
244 ('import sys;' | |
245 'print "Patch download failed. See bot_update step for details";' | |
246 'sys.exit(1)'), | |
247 infra_step=True, | |
248 step_test_data=lambda: self.m.raw_io.test_api.output( | |
249 'Patch download failed. See bot_update step for details', | |
250 retcode=1) | |
251 ) | |
252 else: | |
253 # This is actual patch failure. | |
254 step_result.presentation.properties['failure_type'] = 'PATCH_FAILURE' | |
255 self.m.python.failing_step( | |
256 'Patch failure', 'Check the bot_update step for details') | |
257 | |
258 # bot_update actually just sets root to be the folder name of the | |
259 # first solution. | |
260 if step_result.json.output['did_run']: | |
261 co_root = step_result.json.output['root'] | |
262 cwd = kwargs.get('cwd', self.m.path['slave_build']) | |
263 if 'checkout' not in self.m.path: | |
264 self.m.path['checkout'] = cwd.join(*co_root.split(self.m.path.sep)) | |
265 | |
266 return step_result | |
OLD | NEW |