blob: cc6e3c543a360f177bb5ae839f8f5a7f13ddbdcc [file] [log] [blame]
# Copyright 2017 The Tulsi Authors. All rights reserved.
#
# 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
#
# http://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.
"""Parses a stream of JSON build event protocol messages from a file."""
import json
class _FileLineReader(object):
"""Reads lines from a streaming file.
This will repeatedly check the file for an entire line to read. It will
buffer partial lines until they are completed.
This is meant for files that are being modified by an long-living external
program.
"""
def __init__(self, file_obj):
"""Creates a new FileLineReader object.
Args:
file_obj: The file object to watch.
Returns:
A FileLineReader instance.
"""
self._file_obj = file_obj
self._buffer = []
def check_for_changes(self):
"""Checks the file for any changes, returning the line read if any."""
line = self._file_obj.readline()
self._buffer.append(line)
# Only parse complete lines.
if not line.endswith('\n'):
return None
full_line = ''.join(self._buffer)
del self._buffer[:]
return full_line
class BazelBuildEvent(object):
"""Represents a Bazel Build Event.
Public Properties:
event_dict: the source dictionary for this event.
stdout: stdout string, if any.
stderr: stderr string, if any.
files: list of file URIs.
"""
def __init__(self, event_dict):
"""Creates a new BazelBuildEvent object.
Args:
event_dict: Dictionary representing a build event
Returns:
A BazelBuildEvent instance.
"""
self.event_dict = event_dict
self.stdout = None
self.stderr = None
self.files = []
if 'progress' in event_dict:
self._update_fields_for_progress(event_dict['progress'])
if 'namedSetOfFiles' in event_dict:
self._update_fields_for_named_set_of_files(event_dict['namedSetOfFiles'])
def _update_fields_for_progress(self, progress_dict):
self.stdout = progress_dict.get('stdout')
self.stderr = progress_dict.get('stderr')
def _update_fields_for_named_set_of_files(self, named_set):
files = named_set.get('files', [])
for file_obj in files:
uri = file_obj.get('uri', '')
if uri.startswith('file://'):
self.files.append(uri[7:])
class BazelBuildEventsWatcher(object):
"""Watches a build events JSON file."""
def __init__(self, json_file, warning_handler=None):
"""Creates a new BazelBuildEventsWatcher object.
Args:
json_file: The JSON file object to watch.
warning_handler: Handler function for warnings accepting a single string.
Returns:
A BazelBuildEventsWatcher instance.
"""
self.file_reader = _FileLineReader(json_file)
self.warning_handler = warning_handler
self._read_any_events = False
def has_read_events(self):
return self._read_any_events
def check_for_new_events(self):
"""Checks the file for new BazelBuildEvents.
Returns:
A list of all new BazelBuildEvents.
"""
new_events = []
while True:
line = self.file_reader.check_for_changes()
if not line:
break
try:
build_event_dict = json.loads(line)
except (UnicodeDecodeError, ValueError) as e:
handler = self.warning_handler
if handler:
handler('Could not decode BEP event "%s"\n' % line)
handler('Received error of %s, "%s"\n' % (type(e), e))
break
self._read_any_events = True
build_event = BazelBuildEvent(build_event_dict)
new_events.append(build_event)
return new_events