blob: 0db74bb96033e7279d52f52ef3fbc729cf04cc10 [file] [log] [blame]
# Copyright 2019 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import logging
import os
import sys
from io import open
import psutil
from google.protobuf import json_format as jsonpb
from google.protobuf import text_format as textpb
from PB.go.chromium.org.luci.buildbucket.proto import common
from PB.go.chromium.org.luci.buildbucket.proto.build import Build
from ....third_party import luci_context
from ....util import fix_json_object
from ...engine import RecipeEngine
from ...global_shutdown import install_signal_handlers
from ...step_runner.subproc import SubprocessStepRunner
from ...stream.invariants import StreamEngineInvariants
from ...stream.luci import LUCIStreamEngine
from . import RunBuildContractViolation
LOG = logging.getLogger(__name__)
def _contract_in_env(key):
if key not in os.environ:
raise RunBuildContractViolation('Expected $%s in environment.' % key)
return os.environ[key]
def _contract_in_luci_context(section_name, key):
section = luci_context.read(section_name)
if section is None or key not in section:
raise RunBuildContractViolation('Expected %r in $LUCI_CONTEXT[%r].' % (
key, section_name))
return section[key]
def _synth_properties(build, current_properties):
# TODO(iannucci): expose this data natively as a Build message.
synth_props = {
'$recipe_engine/runtime': {
'is_experimental': build.input.experimental,
},
'$recipe_engine/buildbucket': {
'build': jsonpb.MessageToDict(build),
},
'$recipe_engine/path': {
'temp_dir': _contract_in_env('TMP'),
'cache_dir': _contract_in_luci_context('luciexe', 'cache_dir'),
},
}
# TODO(iannucci): These are all deprecated and have proper apis.
#
# When we have the warnings functionality in recipes, update the properties
# module to issue warnings for accessing these.
if 'buildername' not in current_properties and build.builder.builder:
synth_props['buildername'] = build.builder.builder
if 'buildnumber' not in current_properties and build.number:
synth_props['buildnumber'] = build.number
if 'bot_id' not in current_properties and 'SWARMING_BOT_ID' in os.environ:
synth_props['bot_id'] = os.environ['SWARMING_BOT_ID']
LOG.info('Synthesized properties: %r', synth_props)
return synth_props
def _tweak_env():
# These tweaks are recipe-engine-specific tweaks to be compatible with the
# behavior of `recipes.py run`.
os.environ['PYTHONUNBUFFERED'] = '1'
os.environ['PYTHONIOENCODING'] = 'UTF-8'
def main(args):
with install_signal_handlers():
return _main_impl(args)
def _main_impl(args):
LOG.info('luciexe started, parsing Build message from stdin.')
build = Build()
build.ParseFromString(open(sys.stdin.fileno(), 'rb').read())
LOG.info('finished parsing Build message')
LOG.debug('build proto: %s', jsonpb.MessageToJson(build))
properties = jsonpb.MessageToDict(build.input.properties)
properties.update(_synth_properties(build, properties))
properties = fix_json_object(properties)
_tweak_env()
luciexe_engine = LUCIStreamEngine(args.build_proto_stream_jsonpb)
raw_result = None
with StreamEngineInvariants.wrap(luciexe_engine) as stream_engine:
try:
raw_result, _ = RecipeEngine.run_steps(
args.recipe_deps, properties, stream_engine,
SubprocessStepRunner(), os.environ, os.getcwd(),
luci_context.read_full(), psutil.cpu_count(),
psutil.virtual_memory().total)
stream_engine.write_result(raw_result)
except:
LOG.exception("RecipeEngine.run_steps uncaught exception.")
raise
if args.output:
try:
final_build = luciexe_engine.current_build_proto
if args.output.endswith('.pb') :
out = final_build.SerializeToString(deterministic=True)
elif args.output.endswith('.json'):
out = jsonpb.MessageToJson(
final_build,
preserving_proto_field_name=True,
sort_keys=True,
indent=2).encode('utf-8')
elif args.output.endswith('.textpb'):
out = textpb.MessageToString(final_build).encode('utf-8')
else:
raise ValueError('unknown output file extension: %r' % args.output)
with open(args.output, 'wb') as f:
f.write(out)
except:
LOG.exception("Error while writing final build to output file.")
raise
return 0 if luciexe_engine.was_successful else 1