# Copyright 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # TODO(borenet): This module was copied from build.git and heavily modified to # remove dependencies on other modules in build.git. It belongs in a different # repo. Remove this once it has been moved. from recipe_engine import recipe_api class SwarmingClientApi(recipe_api.RecipeApi): """Code that both isolate and swarming recipe modules depend on. Both swarming and isolate scripts live in a single repository called 'swarming client'. This module include common functionality like finding existing swarming client checkout, fetching a new one, getting version of a swarming script, etc. """ def __init__(self, **kwargs): super(SwarmingClientApi, self).__init__(**kwargs) self._client_path = None self._script_version = {} def checkout(self, revision=None, curl_trace_file=None, can_fail_build=True): """Returns a step to checkout swarming client into a separate directory. Ordinarily swarming client is checked out via Chromium DEPS into src/tools/swarming_client. This step configures recipe module to use a separate checkout. If |revision| is None, this requires the build property 'parent_got_swarming_client_revision' to be present, and raises an exception otherwise. Fail-fast behavior is used because if machines silently fell back to checking out the entire workspace, that would cause dramatic increases in cycle time if a misconfiguration were made and it were no longer possible for the bot to check out swarming_client separately. """ # If the following line throws an exception, it either means the # bot is misconfigured, or, if you're testing locally, that you # need to pass in some recent legal revision for this property. if revision is None: revision = self.m.properties['parent_got_swarming_client_revision'] self._client_path = self.m.path['start_dir'].join('swarming.client') self.m.git.checkout( url='https://chromium.googlesource.com/external/swarming.client.git', ref=revision, dir_path=self._client_path, step_suffix='swarming_client', curl_trace_file=curl_trace_file, can_fail_build=can_fail_build) @property def path(self): """Returns path to a swarming client checkout. It's subdirectory of Chromium src/ checkout or a separate directory if 'checkout_swarming_client' step was used. """ if self._client_path: return self._client_path # Default is swarming client path in chromium src/ checkout. # TODO(vadimsh): This line assumes the recipe is working with # Chromium checkout. return self.m.path['checkout'].join('tools', 'swarming_client') def query_script_version(self, script, step_test_data=None): """Yields a step to query a swarming script for its version. Version tuple is later accessible via 'get_script_version' method. If |step_test_data| is given, it is a tuple with version to use in expectation tests by default. Does nothing if script's version is already known. """ # Convert |step_test_data| from tuple of ints back to a version string. if step_test_data: assert isinstance(step_test_data, tuple) assert all(isinstance(x, int) for x in step_test_data) as_text = '.'.join(map(str, step_test_data)) step_test_data_cb = lambda: self.m.raw_io.test_api.stream_output(as_text) else: step_test_data_cb = None if script not in self._script_version: try: self.m.python( name='%s --version' % script, script=self.path.join(script), args=['--version'], stdout=self.m.raw_io.output_text(), step_test_data=step_test_data_cb) finally: step_result = self.m.step.active_result version = step_result.stdout.strip() step_result.presentation.step_text = version self._script_version[script] = tuple(map(int, version.split('.'))) return step_result def get_script_version(self, script): """Returns a version of some swarming script as a tuple (Major, Minor, Rev). It should have been queried by 'query_script_version' step before. Raises AssertionError if it wasn't. """ assert script in self._script_version, script return self._script_version[script] def ensure_script_version(self, script, min_version, step_test_data=None): """Yields steps to ensure a script version is not older than |min_version|. Will abort recipe execution if it is. """ step_result = self.query_script_version( script, step_test_data=step_test_data or min_version) version = self.get_script_version(script) if version < min_version: expecting = '.'.join(map(str, min_version)) got = '.'.join(map(str, version)) abort_reason = 'Expecting at least v%s, got v%s' % (expecting, got) # TODO(martiniss) remove once recipe 1.5 migration done step_result = self.m.python.inline( '%s is too old' % script, 'import sys; sys.exit(1)', add_python_log=False) # TODO(martiniss) get rid of this bare string. step_result.presentation.status = self.m.step.FAILURE step_result.presentation.step_text = abort_reason raise self.m.step.StepFailure(abort_reason)