You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
6.6 KiB
196 lines
6.6 KiB
# Copyright 2020 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Utilities for RBE-enabled builds."""
|
|
|
|
import os
|
|
import random
|
|
import subprocess
|
|
import tempfile
|
|
|
|
# These are the environment variables that control RBE usage with the
|
|
# --use_rbe flag. If defined on the environment, the values will be
|
|
# propagated to the build; otherwise, those defaults will be used.
|
|
TOOLS_DIR = 'prebuilts/remoteexecution-client/latest'
|
|
_RBE_ENV = {
|
|
'USE_RBE': 'true',
|
|
'RBE_DIR': TOOLS_DIR,
|
|
'NINJA_REMOTE_NUM_JOBS': '500',
|
|
'FLAG_log_dir': 'out',
|
|
'FLAG_server_address': 'unix:///tmp/reproxy_%s.sock' % random.randint(0,100000),
|
|
'FLAG_exec_root': '/src',
|
|
'FLAG_invocation_id': 'treble-%s' % random.randint(0,100000),
|
|
'RBE_use_application_default_credentials': 'true',
|
|
'RBE_reproxy_wait_seconds': '20',
|
|
'RBE_output_dir': 'out',
|
|
'RBE_proxy_log_dir': 'out',
|
|
'RBE_cpp_dependency_scanner_plugin': os.path.join(TOOLS_DIR, 'dependency_scanner_go_plugin.so'),
|
|
'RBE_re_proxy': os.path.join(TOOLS_DIR, 'reproxy'),
|
|
'RBE_JAVAC': 'true',
|
|
'RBE_D8': 'true',
|
|
'RBE_R8': 'true',
|
|
}
|
|
|
|
|
|
def get_nsjail_bin_wrapper():
|
|
"""Returns the command executed in a closed network namespace."""
|
|
return ['netns-exec', 'rbe-closed-ns']
|
|
|
|
|
|
def env_array_to_dict(env_array):
|
|
"""Converts an env var array to a dict.
|
|
|
|
Args:
|
|
env: An array of environment variables in the `var=val` syntax.
|
|
|
|
Returns:
|
|
A dict of string values keyed by string names.
|
|
"""
|
|
env_dict = {}
|
|
for var in env_array:
|
|
var = var.split('=')
|
|
name = var[0]
|
|
value = var[1]
|
|
env_dict[name] = value
|
|
return env_dict
|
|
|
|
def prepare_env(env):
|
|
"""Prepares an env dict for enabling RBE.
|
|
|
|
Checks that all environment variables required to be set
|
|
by the user are defined and sets some default
|
|
values for optional environment variables
|
|
|
|
Args:
|
|
env: An array of environment variables in the `var=val` syntax.
|
|
|
|
Returns:
|
|
An array of environment variables in the `var=val` syntax.
|
|
"""
|
|
# Start with the default values
|
|
prepared_env = _RBE_ENV.copy()
|
|
|
|
# Host environment variables take precedence over defaults.
|
|
for k,v in os.environ.items():
|
|
if k.startswith('RBE_'):
|
|
prepared_env[k] = v
|
|
|
|
# Input parameter variables take precedence over everything else
|
|
prepared_env.update(env_array_to_dict(env))
|
|
|
|
if 'RBE_instance' not in prepared_env:
|
|
raise EnvironmentError('The RBE_instance environment '
|
|
'variables must be defined')
|
|
|
|
if 'RBE_service' not in prepared_env:
|
|
raise EnvironmentError('The RBE_service environment '
|
|
'variables must be defined')
|
|
|
|
return ['%s=%s' % (k,v) for k,v in prepared_env.items()]
|
|
|
|
|
|
def get_readonlybind_mounts():
|
|
"""Returns a dictionary of readonly bind mounts"""
|
|
creds_file = '.config/gcloud/application_default_credentials.json'
|
|
# Bind the gcloud credentials file, if present, to authenticate.
|
|
source_creds_file = os.path.join(os.getenv('HOME'), creds_file)
|
|
dest_creds_file = os.path.join('/tmp', creds_file)
|
|
if not os.path.exists(source_creds_file):
|
|
raise IOError('Required credentials file not found: ' + source_creds_file)
|
|
return ['%s:%s' % (source_creds_file, dest_creds_file)]
|
|
|
|
|
|
def get_extra_nsjail_args():
|
|
"""Returns a dictionary of extra nsjail.run arguments for RBE."""
|
|
# The nsjail should be invoked in a closed network namespace.
|
|
return ['--disable_clone_newnet']
|
|
|
|
|
|
def setup(env, build_log=subprocess.DEVNULL):
|
|
"""Prerequisite for having RBE enabled for the build.
|
|
|
|
Calls RBE http proxy in a separate network namespace.
|
|
|
|
Args:
|
|
env: An array of environment variables in the `var=val` syntax.
|
|
build_log: a file handle to write executed commands to.
|
|
|
|
Returns:
|
|
A cleanup function to be called after the build is done.
|
|
"""
|
|
env_dict = env_array_to_dict(env)
|
|
|
|
# Create the RBE http proxy allowlist file.
|
|
if 'RBE_service' in env_dict:
|
|
rbe_service = env_dict['RBE_service']
|
|
else:
|
|
rbe_service = os.getenv('RBE_service')
|
|
if not rbe_service:
|
|
raise EnvironmentError('The RBE_service environment '
|
|
'variables must be defined')
|
|
if ':' in rbe_service:
|
|
rbe_service = rbe_service.split(':', 1)[0]
|
|
rbe_allowlist = [
|
|
rbe_service,
|
|
'oauth2.googleapis.com',
|
|
'accounts.google.com',
|
|
]
|
|
with open('/tmp/rbe_allowlist.txt', 'w+') as t:
|
|
for w in rbe_allowlist:
|
|
t.write(w + '\n')
|
|
|
|
# Restart RBE http proxy.
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
proxy_kill_command = ['killall', 'tinyproxy']
|
|
port = 8000 + random.randint(0,1000)
|
|
new_conf_contents = ''
|
|
with open(os.path.join(script_dir, 'rbe_http_proxy.conf'), 'r') as base_conf:
|
|
new_conf_contents = base_conf.read()
|
|
with tempfile.NamedTemporaryFile(prefix='rbe_http_proxy_', mode='w', delete=False) as new_conf:
|
|
new_conf.write(new_conf_contents)
|
|
new_conf.write('\nPort %i\n' % port)
|
|
new_conf.close()
|
|
env.append("RBE_HTTP_PROXY=10.1.2.1:%i" % port)
|
|
|
|
proxy_command = [
|
|
'netns-exec', 'rbe-open-ns', 'tinyproxy', '-c', new_conf.name, '-d']
|
|
rbe_proxy_log = tempfile.NamedTemporaryFile(prefix='tinyproxy_', delete=False)
|
|
if build_log != subprocess.DEVNULL:
|
|
print('RBE http proxy restart commands:', file=build_log)
|
|
print(' '.join(proxy_kill_command), file=build_log)
|
|
print('cd ' + script_dir, file=build_log)
|
|
print(' '.join(proxy_command) + ' &> ' + rbe_proxy_log.name + ' &',
|
|
file=build_log)
|
|
subprocess.call(
|
|
proxy_kill_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
rbe_proxy = subprocess.Popen(
|
|
proxy_command,
|
|
cwd=script_dir,
|
|
stdout=rbe_proxy_log,
|
|
stderr=rbe_proxy_log)
|
|
|
|
def cleanup():
|
|
"""Should be called after an RBE build is done."""
|
|
if build_log != subprocess.DEVNULL:
|
|
print('RBE http proxy kill command:', file=build_log)
|
|
print(' '.join(proxy_kill_command), file=build_log)
|
|
rbe_proxy.terminate()
|
|
# TODO(diegowilson): Calling wait() sometimes dead locks.
|
|
# Not sure if it's a tinyproxy bug or the issue described in the wait() documentation
|
|
# https://docs.python.org/2/library/subprocess.html#subprocess.Popen.wait
|
|
# rbe_proxy.wait()
|
|
rbe_proxy_log.close()
|
|
|
|
return cleanup
|