Chromium Code Reviews| 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 |