blob: 30c4ed12253867ae0cd4eef99d19293d17f166b5 [file] [log] [blame]
# 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)
self._goma_dir = None
self._goma_started = False
self._goma_ctl_env = {}
self._goma_jobs = None
@property
def service_account_json_path(self):
return '/creds/service_accounts/service-account-goma-client.json'
@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
def ensure_goma(self, canary=False):
with self.m.step.nest('ensure_goma'):
with self.m.step.context({'infra_step': 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
@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 start(self, env=None, **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
self._goma_ctl_env['GOMA_CACHE_DIR'] = (
self.m.path.join(self.m.path['cache'], 'goma'))
self._goma_ctl_env['GOMA_SERVICE_ACCOUNT_JSON_FILE'] = (
self.service_account_json_path)
# GLOG_log_dir should not be set.
assert env is None or 'GLOG_log_dir' not in env
goma_ctl_start_env = self._goma_ctl_env.copy()
if env is not None:
goma_ctl_start_env.update(env)
try:
self.m.python(
name='start_goma',
script=self.goma_ctl,
args=['restart'], env=goma_ctl_start_env, 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'], env=self._goma_ctl_env, **kwargs)
except self.m.step.StepFailure:
pass
raise e
def stop(self, **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():
self.m.python(name='goma_jsonstatus', script=self.goma_ctl,
args=['jsonstatus', self.json_path],
env=self._goma_ctl_env, **kwargs)
self.m.python(name='goma_stat', script=self.goma_ctl,
args=['stat'],
env=self._goma_ctl_env, **kwargs)
self.m.python(name='stop_goma', script=self.goma_ctl,
args=['stop'], env=self._goma_ctl_env, **kwargs)
self._goma_started = False
self._goma_ctl_env = {}
@contextmanager
def build_with_goma(self, env=None):
"""Make context wrapping goma start/stop.
Raises:
StepFailure or InfraFailure if it fails to build.
"""
self.start(env)
try:
yield
finally:
self.stop()