| OLD | NEW |
| 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 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 """This module holds utilities which make writing recipes easier.""" | 5 """This module holds utilities which make writing recipes easier.""" |
| 6 | 6 |
| 7 import contextlib as _contextlib | 7 import contextlib as _contextlib |
| 8 import os as _os | 8 import os as _os |
| 9 | 9 |
| 10 # These imports are intended to be passed through to recipes |
| 11 # pylint: disable=W0611 |
| 12 from common.chromium_utils import IsWindows, IsMac, IsLinux |
| 13 |
| 10 # e.g. /b/build/slave/<slave-name>/build | 14 # e.g. /b/build/slave/<slave-name>/build |
| 11 SLAVE_BUILD_ROOT = _os.path.abspath(_os.getcwd()) | 15 SLAVE_BUILD_ROOT = _os.path.abspath(_os.getcwd()) |
| 12 # e.g. /b | 16 # e.g. /b |
| 13 ROOT = _os.path.abspath(_os.path.join(SLAVE_BUILD_ROOT, _os.pardir, _os.pardir, | 17 ROOT = _os.path.abspath(_os.path.join(SLAVE_BUILD_ROOT, _os.pardir, _os.pardir, |
| 14 _os.pardir, _os.pardir)) | 18 _os.pardir, _os.pardir)) |
| 15 # e.g. /b/build_internal | 19 # e.g. /b/build_internal |
| 16 BUILD_INTERNAL_ROOT = _os.path.join(ROOT, 'build_internal') | 20 BUILD_INTERNAL_ROOT = _os.path.join(ROOT, 'build_internal') |
| 17 # e.g. /b/build | 21 # e.g. /b/build |
| 18 BUILD_ROOT = _os.path.join(ROOT, 'build') | 22 BUILD_ROOT = _os.path.join(ROOT, 'build') |
| 19 # e.g. /b/depot_tools | 23 # e.g. /b/depot_tools |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 parameters --factory-properties={...} and --build-properties={...} after | 207 parameters --factory-properties={...} and --build-properties={...} after |
| 204 your recipe terminates. | 208 your recipe terminates. |
| 205 | 209 |
| 206 Note that the 'steps' key will be absent from factory-properties. If you | 210 Note that the 'steps' key will be absent from factory-properties. If you |
| 207 need to pass the list of steps to some of the steps, you will need to do | 211 need to pass the list of steps to some of the steps, you will need to do |
| 208 that manually in your recipe (preferably with json.dumps()). | 212 that manually in your recipe (preferably with json.dumps()). |
| 209 | 213 |
| 210 This placeholder can be automatically added when you use the Steps.step() | 214 This placeholder can be automatically added when you use the Steps.step() |
| 211 method in this module. | 215 method in this module. |
| 212 """ | 216 """ |
| 213 pass | |
| 214 PropertyPlaceholder = PropertyPlaceholder() | 217 PropertyPlaceholder = PropertyPlaceholder() |
| 215 | 218 |
| 219 class JsonOutputPlaceholder(object): |
| 220 """JsonOutputPlaceholder is meant to be a singleton object which, when added |
| 221 to a step's cmd list, will be replaced by annotated_run with the command |
| 222 parameters --json-output=/path/to/file during the evaluation of your recipe |
| 223 generator. |
| 224 |
| 225 This placeholder can be optionally added when you use the Steps.step() |
| 226 method in this module. |
| 227 |
| 228 After the termination of the step, this file is expected to contain a valid |
| 229 JSON document, which will be set as the json_output for that step in the |
| 230 step_history OrderedDict passed to your recipe generator. |
| 231 """ |
| 232 JsonOutputPlaceholder = JsonOutputPlaceholder() |
| 233 |
| 216 | 234 |
| 217 def _url_method(name): | 235 def _url_method(name): |
| 218 """Returns a shortcut static method which functions like os.path.join and uses | 236 """Returns a shortcut static method which functions like os.path.join and uses |
| 219 a fixed first url component which is chosen from the urls defined in | 237 a fixed first url component which is chosen from the urls defined in |
| 220 SOURCE_URLS based on |name|. | 238 SOURCE_URLS based on |name|. |
| 221 """ | 239 """ |
| 222 # note that we do the __name__ munging for each function separately because | 240 # note that we do the __name__ munging for each function separately because |
| 223 # staticmethod hides these attributes. | 241 # staticmethod hides these attributes. |
| 224 bases = SOURCE_URLS[name] | 242 bases = SOURCE_URLS[name] |
| 225 if len(bases) == 1: | 243 if len(bases) == 1: |
| (...skipping 24 matching lines...) Expand all Loading... |
| 250 def __init__(self, build_properties): | 268 def __init__(self, build_properties): |
| 251 self.build_properties = build_properties | 269 self.build_properties = build_properties |
| 252 self.use_mirror = self.build_properties.get('use_mirror', True) | 270 self.use_mirror = self.build_properties.get('use_mirror', True) |
| 253 | 271 |
| 254 def mirror_only(self, obj): | 272 def mirror_only(self, obj): |
| 255 """Returns obj if we're using mirrors. Otherwise returns the 'empty' | 273 """Returns obj if we're using mirrors. Otherwise returns the 'empty' |
| 256 version of obj. | 274 version of obj. |
| 257 """ | 275 """ |
| 258 return obj if self.use_mirror else obj.__class__() | 276 return obj if self.use_mirror else obj.__class__() |
| 259 | 277 |
| 260 def gclient_common_spec(self, solution_name): | |
| 261 """Returns a single gclient solution object (python dict) for common | |
| 262 solutions. | |
| 263 """ | |
| 264 return GCLIENT_COMMON_SPECS[solution_name](self) | |
| 265 | |
| 266 @staticmethod | 278 @staticmethod |
| 267 def step(name, cmd, add_properties=False, **kwargs): | 279 def step(name, cmd, add_properties=False, add_json_output=False, **kwargs): |
| 268 """Returns a step dictionary which is compatible with annotator.py. | 280 """Returns a step dictionary which is compatible with annotator.py. |
| 269 | 281 |
| 270 Uses PropertyPlaceholder as a stand-in for build-properties and | 282 Uses PropertyPlaceholder as a stand-in for build-properties and |
| 271 factory-properties so that annotated_run can fill them in after the recipe | 283 factory-properties so that annotated_run can fill them in after the recipe |
| 272 completes. | 284 completes. |
| 273 | 285 |
| 274 Args: | 286 Args: |
| 275 name: The name of this step. | 287 name: The name of this step. |
| 276 cmd: A list of strings in the style of subprocess.Popen. | 288 cmd: A list of strings in the style of subprocess.Popen. |
| 277 add_properties: Add PropertyPlaceholder iff True | 289 add_properties: Add PropertyPlaceholder iff True |
| 290 add_json_output: Add JsonOutputPlaceholder iff True |
| 278 **kwargs: Additional entries to add to the annotator.py step dictionary. | 291 **kwargs: Additional entries to add to the annotator.py step dictionary. |
| 279 | 292 |
| 280 Returns: | 293 Returns: |
| 281 A step dictionary which is compatible with annotator.py. | 294 A step dictionary which is compatible with annotator.py. |
| 282 """ | 295 """ |
| 283 assert 'shell' not in kwargs | 296 assert 'shell' not in kwargs |
| 284 assert isinstance(cmd, list) | 297 assert isinstance(cmd, list) |
| 285 if add_properties: | 298 if add_properties: |
| 286 cmd += [PropertyPlaceholder] | 299 cmd += [PropertyPlaceholder] |
| 300 if add_json_output: |
| 301 cmd += [JsonOutputPlaceholder] |
| 287 ret = kwargs | 302 ret = kwargs |
| 288 ret.update({'name': name, 'cmd': cmd}) | 303 ret.update({'name': name, 'cmd': cmd}) |
| 289 return ret | 304 return ret |
| 290 | 305 |
| 291 def apply_issue_step(self, *root_pieces): | 306 def apply_issue(self, *root_pieces): |
| 292 return self.step('apply_issue', [ | 307 return self.step('apply_issue', [ |
| 293 depot_tools_path('apply_issue'), | 308 depot_tools_path('apply_issue'), |
| 294 '-r', checkout_path(*root_pieces), | 309 '-r', checkout_path(*root_pieces), |
| 295 '-i', self.build_properties['issue'], | 310 '-i', self.build_properties['issue'], |
| 296 '-p', self.build_properties['patchset'], | 311 '-p', self.build_properties['patchset'], |
| 297 '-s', self.build_properties['rietveld'], | 312 '-s', self.build_properties['rietveld'], |
| 298 '-e', 'commit-bot@chromium.org']) | 313 '-e', 'commit-bot@chromium.org']) |
| 299 | 314 |
| 300 def git_step(self, *args): | 315 def git(self, *args, **kwargs): |
| 301 name = 'git '+args[0] | 316 name = 'git '+args[0] |
| 302 # Distinguish 'git config' commands by the variable they are setting. | 317 # Distinguish 'git config' commands by the variable they are setting. |
| 303 if args[0] == 'config' and not args[1].startswith('-'): | 318 if args[0] == 'config' and not args[1].startswith('-'): |
| 304 name += ' ' + args[1] | 319 name += ' ' + args[1] |
| 305 return self.step(name, [ | 320 return self.step(name, [ |
| 306 'git', '--work-tree', checkout_path(), | 321 'git', '--work-tree', checkout_path(), |
| 307 '--git-dir', checkout_path('.git')] + list(args)) | 322 '--git-dir', checkout_path('.git')] + list(args), **kwargs) |
| 323 |
| 324 def generator_script(self, path_to_script): |
| 325 def step_generator(step_history, _failure): |
| 326 yield self.step( |
| 327 'gen step(%s)' % _os.path.basename(path_to_script), |
| 328 [path_to_script], |
| 329 add_properties=True, |
| 330 add_json_output=True, |
| 331 cwd=checkout_path()) |
| 332 new_steps = step_history.last_step().json_data |
| 333 assert isinstance(new_steps, list) |
| 334 yield new_steps |
| 335 return step_generator |
| 336 |
| 337 def git_checkout(self, url, dir_path=None, branch='master', recursive=False): |
| 338 if not dir_path: |
| 339 dir_path = url.rsplit('/', 1)[-1] |
| 340 if dir_path.endswith('.git'): # ex: https://host/foobar.git |
| 341 dir_path = dir_path[:-len('.git')] |
| 342 if not dir_path: # ex: ssh://host:repo/foobar/.git |
| 343 dir_path = dir_path.rsplit('/', 1)[-1] |
| 344 dir_path = slave_build_path(dir_path) |
| 345 assert _os.pardir not in dir_path |
| 346 recursive_args = ['--recurse-submodules'] if recursive else [] |
| 347 return [ |
| 348 self.step( |
| 349 'git setup', [ |
| 350 build_path('scripts', 'slave', 'git_setup.py'), |
| 351 '--path', dir_path, |
| 352 '--url', url, |
| 353 ], |
| 354 static_json_data={ |
| 355 'CheckoutRoot': dir_path, |
| 356 'CheckoutSCM': 'git', |
| 357 'CheckoutSpec': { |
| 358 'url': url, |
| 359 'recursive': recursive, |
| 360 }, |
| 361 }), |
| 362 self.git('fetch', 'origin', *recursive_args), |
| 363 self.git('update-ref', 'refs/heads/'+branch, 'origin/'+branch), |
| 364 self.git('clean', '-f', '-d', '-X'), |
| 365 self.git('checkout', '-f', branch), |
| 366 self.git('submodule', 'update', '--init', '--recursive'), |
| 367 ] |
| 368 |
| 369 def gclient_checkout(self, common_repo_name, git_mode=False): |
| 370 """Returns a step generator function for gclient checkouts.""" |
| 371 spec = GCLIENT_COMMON_SPECS[common_repo_name](self) |
| 372 spec_string = '' |
| 373 for key in spec: |
| 374 # We should be using json.dumps here, but gclient directly execs the dict |
| 375 # that it receives as the argument to --spec, so we have to have True, |
| 376 # False, and None instead of JSON's true, false, and null. |
| 377 spec_string += '%s = %s\n' % (key, str(spec[key])) |
| 378 gclient = depot_tools_path('gclient') + ('.bat' if IsWindows() else '') |
| 379 |
| 380 if not git_mode: |
| 381 clean_step = self.step('gclient clean', [gclient, 'revert', '--nohooks']) |
| 382 sync_step = self.step('gclient sync', [gclient, 'sync', '--nohooks']) |
| 383 else: |
| 384 # clean() isn't used because the gclient sync flags passed in checkout() |
| 385 # do much the same thing, and they're more correct than doing a separate |
| 386 # 'gclient revert' because it makes sure the other args are correct when |
| 387 # a repo was deleted and needs to be re-cloned (notably |
| 388 # --with_branch_heads), whereas 'revert' uses default args for clone |
| 389 # operations. |
| 390 # |
| 391 # TODO(mmoss): To be like current official builders, this step could just |
| 392 # delete the whole <slave_name>/build/ directory and start each build |
| 393 # from scratch. That might be the least bad solution, at least until we |
| 394 # have a reliable gclient method to produce a pristine working dir for |
| 395 # git-based builds (e.g. maybe some combination of 'git reset/clean -fx' |
| 396 # and removing the 'out' directory). |
| 397 clean_step = None |
| 398 sync_step = self.step('gclient sync', [ |
| 399 gclient, 'sync', '--verbose', '--with_branch_heads', '--nohooks', |
| 400 '--reset', '--delete_unversioned_trees', '--force']) |
| 401 steps = [ |
| 402 self.step( |
| 403 'gclient setup', |
| 404 [gclient, 'config', '--spec', spec_string], |
| 405 static_json_data={ |
| 406 'CheckoutRoot': slave_build_path(spec['solutions'][0]['name']), |
| 407 'CheckoutSCM': 'gclient', |
| 408 'CheckoutSpec': spec |
| 409 } |
| 410 ), |
| 411 ] |
| 412 if clean_step: |
| 413 steps.append(clean_step) |
| 414 if sync_step: |
| 415 steps.append(sync_step) |
| 416 |
| 417 return steps |
| OLD | NEW |