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 # TODO(borenet): This module was copied from build.git and heavily modified to 7 # remove dependencies on other modules in build.git. It belongs in a different 8 # repo. Remove this once it has been moved. 9 10 11 from recipe_engine import recipe_api 12 13 14 class SwarmingClientApi(recipe_api.RecipeApi): 15 """Code that both isolate and swarming recipe modules depend on. 16 17 Both swarming and isolate scripts live in a single repository called 18 'swarming client'. This module include common functionality like finding 19 existing swarming client checkout, fetching a new one, getting version of 20 a swarming script, etc. 21 """ 22 23 def __init__(self, **kwargs): 24 super(SwarmingClientApi, self).__init__(**kwargs) 25 self._client_path = None 26 self._script_version = {} 27 28 def checkout(self, revision=None, curl_trace_file=None, can_fail_build=True): 29 """Returns a step to checkout swarming client into a separate directory. 30 31 Ordinarily swarming client is checked out via Chromium DEPS into 32 src/tools/swarming_client. This step configures recipe module to use 33 a separate checkout. 34 35 If |revision| is None, this requires the build property 36 'parent_got_swarming_client_revision' to be present, and raises an exception 37 otherwise. Fail-fast behavior is used because if machines silently fell back 38 to checking out the entire workspace, that would cause dramatic increases 39 in cycle time if a misconfiguration were made and it were no longer possible 40 for the bot to check out swarming_client separately. 41 """ 42 # If the following line throws an exception, it either means the 43 # bot is misconfigured, or, if you're testing locally, that you 44 # need to pass in some recent legal revision for this property. 45 if revision is None: 46 revision = self.m.properties['parent_got_swarming_client_revision'] 47 self._client_path = self.m.path['start_dir'].join('swarming.client') 48 self.m.git.checkout( 49 url='https://chromium.googlesource.com/external/swarming.client.git', 50 ref=revision, 51 dir_path=self._client_path, 52 step_suffix='swarming_client', 53 curl_trace_file=curl_trace_file, 54 can_fail_build=can_fail_build) 55 56 @property 57 def path(self): 58 """Returns path to a swarming client checkout. 59 60 It's subdirectory of Chromium src/ checkout or a separate directory if 61 'checkout_swarming_client' step was used. 62 """ 63 if self._client_path: 64 return self._client_path 65 # Default is swarming client path in chromium src/ checkout. 66 # TODO(vadimsh): This line assumes the recipe is working with 67 # Chromium checkout. 68 return self.m.path['checkout'].join('tools', 'swarming_client') 69 70 def query_script_version(self, script, step_test_data=None): 71 """Yields a step to query a swarming script for its version. 72 73 Version tuple is later accessible via 'get_script_version' method. If 74 |step_test_data| is given, it is a tuple with version to use in expectation 75 tests by default. 76 77 Does nothing if script's version is already known. 78 """ 79 # Convert |step_test_data| from tuple of ints back to a version string. 80 if step_test_data: 81 assert isinstance(step_test_data, tuple) 82 assert all(isinstance(x, int) for x in step_test_data) 83 as_text = '.'.join(map(str, step_test_data)) 84 step_test_data_cb = lambda: self.m.raw_io.test_api.stream_output(as_text) 85 else: 86 step_test_data_cb = None 87 88 if script not in self._script_version: 89 try: 90 self.m.python( 91 name='%s --version' % script, 92 script=self.path.join(script), 93 args=['--version'], 94 stdout=self.m.raw_io.output_text(), 95 step_test_data=step_test_data_cb) 96 finally: 97 step_result = self.m.step.active_result 98 version = step_result.stdout.strip() 99 step_result.presentation.step_text = version 100 self._script_version[script] = tuple(map(int, version.split('.'))) 101 102 return step_result 103 104 def get_script_version(self, script): 105 """Returns a version of some swarming script as a tuple (Major, Minor, Rev). 106 107 It should have been queried by 'query_script_version' step before. Raises 108 AssertionError if it wasn't. 109 """ 110 assert script in self._script_version, script 111 return self._script_version[script] 112 113 def ensure_script_version(self, script, min_version, step_test_data=None): 114 """Yields steps to ensure a script version is not older than |min_version|. 115 116 Will abort recipe execution if it is. 117 """ 118 step_result = self.query_script_version( 119 script, step_test_data=step_test_data or min_version) 120 version = self.get_script_version(script) 121 if version < min_version: 122 expecting = '.'.join(map(str, min_version)) 123 got = '.'.join(map(str, version)) 124 abort_reason = 'Expecting at least v%s, got v%s' % (expecting, got) 125 126 # TODO(martiniss) remove once recipe 1.5 migration done 127 step_result = self.m.python.inline( 128 '%s is too old' % script, 129 'import sys; sys.exit(1)', 130 add_python_log=False) 131 # TODO(martiniss) get rid of this bare string. 132 step_result.presentation.status = self.m.step.FAILURE 133 step_result.presentation.step_text = abort_reason 134 135 raise self.m.step.StepFailure(abort_reason) 136