| # |
| # Copyright 2017 The Cobalt 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. |
| # |
| """Abstraction for running Cobalt development tools.""" |
| |
| import abc |
| import importlib |
| import os |
| import sys |
| |
| import _env # pylint: disable=unused-import |
| from starboard.tools import build |
| |
| |
| def _GetLauncherForPlatform(platform_name): |
| """Gets the module containing a platform's concrete launcher implementation. |
| |
| Args: |
| platform_name: Platform on which the app will be run, ex. "linux-x64x11". |
| |
| Returns: |
| The module containing the platform's launcher implementation. |
| """ |
| |
| gyp_config = build.GetPlatformConfig(platform_name) |
| if not hasattr(gyp_config, "GetLauncher"): |
| return None |
| else: |
| return gyp_config.GetLauncher() |
| |
| |
| def DynamicallyBuildOutDirectory(platform_name, config): |
| """Constructs the location used to store executable targets/their components. |
| |
| Args: |
| platform_name: The platform to run the executable on, ex. "linux-x64x11". |
| config: The build configuration, ex. "qa". |
| |
| Returns: |
| The path to the directory containing executables and/or their components. |
| """ |
| path = os.path.abspath( |
| os.path.join( |
| os.path.dirname(__file__), os.pardir, os.pardir, "out", |
| "{}_{}".format(platform_name, config))) |
| return path |
| |
| |
| def LauncherFactory(platform_name, |
| target_name, |
| config, |
| device_id=None, |
| target_params=None, |
| output_file=None, |
| out_directory=None, |
| env_variables=None): |
| """Creates the proper launcher based upon command line args. |
| |
| Args: |
| platform_name: The platform on which the app will run. |
| target_name: The name of the executable target (ex. "cobalt"). |
| config: Type of configuration used by the launcher (ex. "qa", "devel"). |
| device_id: The identifier for the devkit being used. Can be None. |
| target_params: Command line arguments to the executable. Can be None. |
| output_file: Open file object used for storing the launcher's output. If |
| None, sys.stdout is used. |
| out_directory: Directory containing the executable target. If None is |
| provided, the path to the directory is dynamically generated. |
| env_variables: Environment variables for the executable |
| |
| Returns: |
| An instance of the concrete launcher class for the desired platform. |
| |
| Raises: |
| RuntimeError: The platform does not exist, or there is no project root. |
| """ |
| |
| # Creates launcher for provided platform if the platform has a valid port |
| launcher_module = _GetLauncherForPlatform(platform_name) |
| |
| # TODO: Refactor when all old launchers have been deleted |
| # If a platform that does not have a new launcher is provided, attempt |
| # to create an adapter to the old one. |
| if not launcher_module: |
| old_path = os.path.abspath( |
| os.path.join( |
| os.path.dirname(__file__), os.pardir, os.pardir, "tools", |
| "lbshell")) |
| if os.path.exists(os.path.join(old_path, "app_launcher.py")): |
| sys.path.append(old_path) |
| bridge_module = importlib.import_module("app_launcher_bridge") |
| return bridge_module.LauncherAdapter( |
| platform_name, |
| target_name, |
| config, |
| device_id, |
| target_params=target_params, |
| output_file=output_file, |
| out_directory=out_directory, |
| env_variables=env_variables) |
| else: |
| raise RuntimeError("No launcher implemented for the given platform.") |
| else: |
| return launcher_module.Launcher( |
| platform_name, |
| target_name, |
| config, |
| device_id, |
| target_params=target_params, |
| output_file=output_file, |
| out_directory=out_directory, |
| env_variables=env_variables) |
| |
| |
| class AbstractLauncher(object): |
| """Class that specifies all required behavior for Cobalt app launchers.""" |
| |
| __metaclass__ = abc.ABCMeta |
| |
| def __init__(self, platform_name, target_name, config, device_id, **kwargs): |
| self.platform_name = platform_name |
| self.target_name = target_name |
| self.config = config |
| self.device_id = device_id |
| self.out_directory = kwargs.get("out_directory", None) |
| |
| # The following pattern makes sure that variables will be initialized |
| # properly whether a kwarg is passed in with a value of None or it |
| # is not passed in at all. |
| output_file = kwargs.get("output_file", None) |
| if not output_file: |
| output_file = sys.stdout |
| self.output_file = output_file |
| |
| target_command_line_params = kwargs.get("target_params", None) |
| if target_command_line_params is None: |
| target_command_line_params = [] |
| self.target_command_line_params = target_command_line_params |
| |
| env_variables = kwargs.get("env_variables", None) |
| if env_variables is None: |
| env_variables = {} |
| self.env_variables = env_variables |
| |
| # Launchers that need different startup timeout times should reassign |
| # this variable during initialization. |
| self.startup_timeout_seconds = 2 * 60 |
| |
| @abc.abstractmethod |
| def Run(self): |
| """Runs the launcher's executable. Must be implemented in subclasses. |
| |
| Returns: |
| The return code from the launcher's executable. |
| """ |
| pass |
| |
| @abc.abstractmethod |
| def Kill(self): |
| """Kills the launcher. Must be implemented in subclasses.""" |
| pass |
| |
| def SupportsSuspendResume(self): |
| return False |
| |
| # The Send*() functions are guaranteed to resolve sequences of calls (e.g. |
| # SendSuspend() followed immediately by SendResume()) in the order that they |
| # are sent. |
| def SendResume(self): |
| """Sends resume signal to the launcher's executable. |
| |
| Raises: |
| RuntimeError: Resume signal not supported on platform. |
| """ |
| raise RuntimeError("Resume not supported for this platform.") |
| |
| def SendSuspend(self): |
| """sends suspend signal to the launcher's executable. |
| |
| When implementing this function, please coordinate with other functions |
| sending platform signals(e.g. SendResume()) to ensure Cobalt receives these |
| signals in the same order they are sent. |
| |
| Raises: |
| RuntimeError: Suspend signal not supported on platform. |
| """ |
| |
| raise RuntimeError("Suspend not supported for this platform.") |
| |
| def GetStartupTimeout(self): |
| """Gets the number of seconds to wait before assuming a launcher timeout.""" |
| |
| return self.startup_timeout_seconds |
| |
| def GetHostAndPortGivenPort(self, port): |
| """Creates a host/port tuple for use on the target device. |
| |
| This is used to gain access to a service on the target device, and |
| can be overridden when the host/port of that service is only accessible |
| at runtime and/or via forwarding. |
| |
| Args: |
| port: Port number for the desired service on the target device. |
| |
| Returns: |
| (Host, port) tuple for use in connecting to the target device. |
| """ |
| return self.device_id, port |
| |
| def GetTargetPath(self): |
| """Constructs the path to an executable target. |
| |
| The default path returned by this method takes the form of: |
| |
| "/path/to/out/<platform>_<config>/target_name" |
| |
| Returns: |
| The path to an executable target. |
| """ |
| if self.out_directory: |
| out_directory = self.out_directory |
| else: |
| out_directory = DynamicallyBuildOutDirectory(self.platform_name, |
| self.config) |
| |
| return os.path.abspath(os.path.join(out_directory, self.target_name)) |