summaryrefslogtreecommitdiff
path: root/buildstream/_frontend/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildstream/_frontend/main.py')
-rw-r--r--buildstream/_frontend/main.py90
1 files changed, 81 insertions, 9 deletions
diff --git a/buildstream/_frontend/main.py b/buildstream/_frontend/main.py
index fd2384ec3..5ec6884a0 100644
--- a/buildstream/_frontend/main.py
+++ b/buildstream/_frontend/main.py
@@ -21,6 +21,7 @@
import os
import sys
import resource
+import datetime
from contextlib import contextmanager
from blessings import Terminal
@@ -33,8 +34,8 @@ from .. import Scope
# Import various buildstream internals
from .._context import Context
from .._project import Project
-from .._exceptions import BstError
-from .._message import MessageType, unconditional_messages
+from .._exceptions import BstError, PipelineError
+from .._message import Message, MessageType, unconditional_messages
from .._pipeline import Pipeline
from .._scheduler import Scheduler
from .._profile import Topics, profile_start, profile_end
@@ -50,10 +51,14 @@ INDENT = 4
##################################################################
# Main Application State #
##################################################################
-
class App():
def __init__(self, main_options):
+
+ # Snapshot the start time of the session at the earliest opportunity,
+ # this is used for inclusive session time logging
+ self.session_start = datetime.datetime.now()
+
self.main_options = main_options
self.logger = None
self.status = None
@@ -104,13 +109,39 @@ class App():
# Set soft limit to hard limit
resource.setrlimit(resource.RLIMIT_NOFILE, (limits[1], limits[1]))
+ # initialized()
#
- # Initialize the main pipeline
+ # Context manager to initialize the application and optionally run a session
+ # within the context manager.
#
- def initialize(self, elements, except_=tuple(), rewritable=False,
- use_configured_remote_caches=False, add_remote_cache=None,
- track_elements=None, fetch_subprojects=False):
-
+ # This context manager will take care of catching errors from within the
+ # context and report them consistently, so the CLI need not take care of
+ # reporting the errors and exiting with a consistent error status.
+ #
+ # Args:
+ # elements (list of elements): The elements to load recursively
+ # session_name (str): The name of the session, or None for no session
+ # except_ (list of elements): The elements to except
+ # rewritable (bool): Whether we should load the YAML files for roundtripping
+ # use_configured_remote_caches (bool): Whether we should contact remotes
+ # add_remote_cache (str): The URL for an explicitly mentioned remote cache
+ # track_elements (list of elements): Elements which are to be tracked
+ # fetch_subprojects (bool): Whether we should fetch subprojects as a part of the
+ # loading process, if they are not yet locally cached
+ #
+ # Note that the except_ argument may have a subtly different meaning depending
+ # on the activity performed on the Pipeline. In normal circumstances the except_
+ # argument excludes elements from the `elements` list. In a build session, the
+ # except_ elements are excluded from the tracking plan.
+ #
+ # If a session_name is provided, we treat the block as a session, and print
+ # the session header and summary, and time the main session from startup time.
+ #
+ @contextmanager
+ def initialized(self, elements, *, session_name=None,
+ except_=tuple(), rewritable=False,
+ use_configured_remote_caches=False, add_remote_cache=None,
+ track_elements=None, fetch_subprojects=False):
profile_start(Topics.LOAD_PIPELINE, "_".join(t.replace(os.sep, '-') for t in elements))
directory = self.main_options['directory']
@@ -150,7 +181,7 @@ class App():
self.interactive_failures = False
# Create the application's scheduler
- self.scheduler = Scheduler(self.context,
+ self.scheduler = Scheduler(self.context, self.session_start,
interrupt_callback=self.interrupt_handler,
ticker_callback=self.tick,
job_start_callback=self.job_started,
@@ -176,6 +207,9 @@ class App():
# Propagate pipeline feedback to the user
self.context._set_message_handler(self.message_handler)
+ if session_name:
+ self.message(MessageType.START, session_name)
+
try:
self.project = Project(directory, self.context, cli_options=self.main_options['option'])
except BstError as e:
@@ -209,6 +243,44 @@ class App():
profile_end(Topics.LOAD_PIPELINE, "_".join(t.replace(os.sep, '-') for t in elements))
+ # Print the heading
+ if session_name:
+ self.print_heading()
+
+ # Run the body of the session here, once everything is loaded
+ try:
+ yield
+
+ # Catch the error outside of the session timer and summarize what happened
+ except BstError as e:
+ elapsed = self.scheduler.elapsed_time()
+
+ if session_name:
+ if isinstance(e, PipelineError) and e.terminated: # pylint: disable=no-member
+ self.message(MessageType.WARN, session_name + ' Terminated', elapsed=elapsed)
+ else:
+ self.message(MessageType.FAIL, session_name, elapsed=elapsed)
+
+ self.print_error(e)
+
+ if session_name:
+ self.print_summary()
+
+ sys.exit(-1)
+
+ # No exceptions occurred, print the summary
+ else:
+ if session_name:
+ self.message(MessageType.SUCCESS, session_name, elapsed=self.scheduler.elapsed_time())
+ self.print_summary()
+
+ # Local message propagator
+ #
+ def message(self, message_type, message, **kwargs):
+ args = dict(kwargs)
+ self.context._message(
+ Message(None, message_type, message, **args))
+
#
# Render the status area, conditional on some internal state
#