| # Copyright 2015 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. |
| |
| from contextlib import contextmanager |
| |
| from recipe_engine import recipe_api |
| |
| |
| class GomaApi(recipe_api.RecipeApi): |
| """GomaApi contains helper functions for using goma.""" |
| |
| def __init__(self, **kwargs): |
| super(GomaApi, self).__init__(**kwargs) |
| |
| # Flag to represent local goma module running. |
| self._is_local = False |
| |
| self._goma_dir = None |
| self._goma_started = False |
| self._goma_jobs = None |
| |
| @property |
| def service_account_json_path(self): |
| return self.m.service_account.get_json_path('goma-client') |
| |
| @property |
| def json_path(self): |
| assert self._goma_dir |
| return self.m.path.join(self._goma_dir, 'jsonstatus') |
| |
| @property |
| def recommended_goma_jobs(self): |
| """ |
| Return the recommended number of jobs for parallel build using Goma. |
| |
| This function caches the _goma_jobs. |
| """ |
| if self._goma_jobs: |
| return self._goma_jobs |
| |
| # We need to use python.inline not to change behavior of recipes. |
| step_result = self.m.python.inline( |
| 'calculate the number of recommended jobs', |
| """ |
| import multiprocessing |
| import sys |
| |
| job_limit = 200 |
| if sys.platform.startswith('linux'): |
| # Use 80 for linux not to load goma backend. |
| job_limit = 80 |
| |
| try: |
| jobs = min(job_limit, multiprocessing.cpu_count() * 10) |
| except NotImplementedError: |
| jobs = 50 |
| |
| print jobs |
| """, |
| stdout=self.m.raw_io.output(), |
| step_test_data=( |
| lambda: self.m.raw_io.test_api.stream_output('50\n')) |
| ) |
| self._goma_jobs = int(step_result.stdout) |
| |
| return self._goma_jobs |
| |
| @property |
| def goma_ctl(self): |
| return self.m.path.join(self._goma_dir, 'goma_ctl.py') |
| |
| @property |
| def goma_dir(self): |
| assert self._goma_dir |
| return self._goma_dir |
| |
| def set_goma_dir(self, goma_dir): # pragma: no cover |
| """ This function is for local recipe test only.""" |
| self._goma_dir = goma_dir |
| self._is_local = True |
| |
| def ensure_goma(self, canary=False): |
| if self._is_local: # pragma: no cover |
| return self._goma_dir |
| |
| with self.m.step.nest('ensure_goma'): |
| with self.m.context(infra_steps=True): |
| self.m.cipd.set_service_account_credentials( |
| self.service_account_json_path) |
| |
| goma_package = ('infra_internal/goma/client/%s' % |
| self.m.cipd.platform_suffix()) |
| ref='release' |
| if canary: |
| ref='candidate' |
| self._goma_dir = self.m.path['start_dir'].join('cipd', 'goma') |
| |
| self.m.cipd.ensure(self._goma_dir, {goma_package: ref}) |
| |
| return self._goma_dir |
| |
| @contextmanager |
| def goma_env(self, env): |
| if 'GOMA_DEPS_CACHE_FILE' not in env: |
| env['GOMA_DEPS_CACHE_FILE'] = 'goma_deps_cache' |
| if 'GOMA_CACHE_DIR' not in env: |
| env['GOMA_CACHE_DIR'] = self.m.path['cache'].join('goma') |
| if not self._is_local: |
| env['GOMA_SERVICE_ACCOUNT_JSON_FILE'] = self.service_account_json_path |
| with self.m.context(env=env): |
| yield |
| |
| def start(self, env={}, **kwargs): |
| """Start goma compiler_proxy. |
| |
| A user MUST execute ensure_goma beforehand. |
| It is user's responsibility to handle failure of starting compiler_proxy. |
| """ |
| assert self._goma_dir |
| assert not self._goma_started |
| |
| # GLOG_log_dir should not be set. |
| assert 'GLOG_log_dir' not in self.m.context.env, ( |
| 'GLOG_log_dir must not be set in env during goma.start()') |
| |
| with self.goma_env(env): |
| try: |
| self.m.python( |
| name='start_goma', |
| script=self.goma_ctl, |
| args=['restart'], infra_step=True, **kwargs) |
| self._goma_started = True |
| except self.m.step.InfraFailure as e: # pragma: no cover |
| try: |
| with self.m.step.defer_results(): |
| self.m.python( |
| name='stop_goma (start failure)', |
| script=self.goma_ctl, |
| args=['stop'], **kwargs) |
| except self.m.step.StepFailure: |
| pass |
| raise e |
| |
| def stop(self, env={}, **kwargs): |
| """Stop goma compiler_proxy. |
| |
| A user MUST execute start beforehand. |
| It is user's responsibility to handle failure of stopping compiler_proxy. |
| |
| Raises: |
| StepFailure if it fails to stop goma. |
| """ |
| assert self._goma_dir |
| assert self._goma_started |
| |
| with self.m.step.defer_results(): |
| with self.goma_env(env): |
| self.m.python(name='goma_jsonstatus', script=self.goma_ctl, |
| args=['jsonstatus', self.json_path], |
| **kwargs) |
| self.m.python(name='goma_stat', script=self.goma_ctl, |
| args=['stat'], |
| **kwargs) |
| self.m.python(name='stop_goma', script=self.goma_ctl, |
| args=['stop'], **kwargs) |
| |
| self._goma_started = False |
| |
| @contextmanager |
| def build_with_goma(self, env={}): |
| """Make context wrapping goma start/stop. |
| |
| Raises: |
| StepFailure or InfraFailure if it fails to build. |
| """ |
| |
| self.start(env) |
| try: |
| yield |
| finally: |
| self.stop(env) |