| # 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. |
| """Base platform build configuration.""" |
| |
| import imp |
| import inspect |
| import logging |
| import os |
| |
| import _env # pylint: disable=unused-import, relative-import |
| from starboard.build.application_configuration import ApplicationConfiguration |
| from starboard.optional import get_optional_tests |
| from starboard.sabi import sabi |
| from starboard.tools import cache |
| from starboard.tools import environment |
| from starboard.tools import paths |
| from starboard.tools import platform |
| from starboard.tools.config import Config |
| |
| |
| def _GetApplicationConfigurationClass(application_path): |
| """Gets the ApplicationConfiguration class from the given path.""" |
| if not os.path.isdir(application_path): |
| return None |
| |
| application_configuration_path = os.path.join(application_path, |
| 'configuration.py') |
| if not os.path.isfile(application_configuration_path): |
| return None |
| |
| application_configuration = imp.load_source('application_configuration', |
| application_configuration_path) |
| for name, cls in inspect.getmembers(application_configuration): |
| if not inspect.isclass(cls): |
| continue |
| if issubclass(cls, ApplicationConfiguration): |
| logging.debug('Found ApplicationConfiguration: %s in %s', name, |
| application_configuration_path) |
| return cls |
| |
| return None |
| |
| |
| class PlatformConfiguration(object): |
| """Base platform build configuration class for all Starboard platforms. |
| |
| Should be derived by platform specific configurations. |
| """ |
| |
| def __init__(self, |
| platform_name, |
| asan_enabled_by_default=False, |
| directory=None): |
| self._platform_name = platform_name |
| self._asan_default = 1 if asan_enabled_by_default else 0 |
| if directory: |
| self._directory = directory |
| else: |
| self._directory = os.path.realpath(os.path.dirname(__file__)) |
| self._application_configuration = None |
| self._application_configuration_search_path = [self._directory] |
| # Default build accelerator is ccache. |
| self.build_accelerator = self.GetBuildAccelerator(cache.Accelerator.CCACHE) |
| |
| def GetBuildAccelerator(self, accelerator): |
| """Returns the build accelerator name.""" |
| build_accelerator = cache.Cache(accelerator) |
| name = build_accelerator.GetName() |
| if build_accelerator.Use(): |
| logging.info('Using %s build accelerator.', name) |
| return name |
| else: |
| logging.info('Not using %s build accelerator.', name) |
| return '' |
| |
| def GetBuildFormat(self): |
| """Returns the desired build format.""" |
| return 'ninja' |
| |
| def GetName(self): |
| """Returns the platform name.""" |
| return self._platform_name |
| |
| def GetDirectory(self): |
| """Returns the directory of the platform configuration.""" |
| return self._directory |
| |
| # This happens post-construction to maintain compatibility with legacy |
| # configurations. |
| # TODO: Find some other way to do this. |
| def SetDirectory(self, directory): |
| """Sets the directory of the platform configuration.""" |
| try: |
| i = self._application_configuration_search_path.index(self._directory) |
| self._application_configuration_search_path.pop(i) |
| except ValueError: |
| i = 0 |
| self._directory = directory |
| self._application_configuration_search_path.insert(i, self._directory) |
| |
| def AppendApplicationConfigurationPath(self, path): |
| """Appends a path to search for ApplicationConfiguration modules.""" |
| self._application_configuration_search_path.append(path) |
| |
| def GetApplicationConfiguration(self, application_name): |
| """Get the instance of ApplicationConfiguration for this app, if any. |
| |
| Looks for a class deriving from ApplicationConfiguration defined for |
| this platform. If it finds one, this function will load, instantiate, and |
| return the configuration. Otherwise, it will return None. |
| |
| Args: |
| application_name: The name of the application to load, in a canonical |
| filesystem-friendly form. |
| |
| Returns: |
| An instance of ApplicationConfiguration defined for the application being |
| loaded. |
| """ |
| if not self._application_configuration: |
| application_path = os.path.join(self.GetDirectory(), application_name) |
| for directory in self._application_configuration_search_path: |
| configuration_path = os.path.join(directory, application_name) |
| logging.debug('Searching for ApplicationConfiguration in %s', |
| configuration_path) |
| configuration_class = _GetApplicationConfigurationClass( |
| configuration_path) |
| if configuration_class: |
| logging.info( |
| 'Using platform-specific ApplicationConfiguration for ' |
| '%s.', application_name) |
| break |
| |
| if not configuration_class: |
| configuration_class = environment.GetApplicationClass(application_name) |
| if configuration_class: |
| logging.info('Using default ApplicationConfiguration for %s.', |
| application_name) |
| |
| if not configuration_class: |
| logging.info('Using base ApplicationConfiguration.') |
| configuration_class = ApplicationConfiguration |
| |
| self._application_configuration = configuration_class( |
| self, application_name, application_path) |
| |
| assert self._application_configuration |
| return self._application_configuration |
| |
| def SetupPlatformTools(self, build_number): |
| """Install tools, SDKs, etc. |
| |
| needed to build for the platform. |
| |
| Installs tools needed to build for this platform, possibly downloading |
| and/or interacting with the user to do so. Raises a RuntimeError if the |
| tools can't be installed or something is wrong that will prevent building. |
| |
| Args: |
| build_number: The number identifying this build, generated at GYP time. |
| """ |
| pass |
| |
| def GetIncludes(self): |
| """Get a list of absolute paths to gypi files to include in order. |
| |
| These files will be included by GYP before any processed .gyp file. The |
| application may add more gypi files to be processed before or after the |
| files specified by this function. |
| |
| Returns: |
| An ordered list containing absolute paths to .gypi files. |
| """ |
| platform_info = platform.Get(self.GetName()) |
| if not platform_info: |
| return [] |
| |
| return [os.path.join(platform_info.path, 'gyp_configuration.gypi')] |
| |
| def GetEnvironmentVariables(self): |
| """Get a Mapping of environment variable overrides. |
| |
| The environment variables returned by this function are set before calling |
| GYP and can be used to manipulate the GYP behavior. They will override any |
| environment variables of the same name in the calling environment, or any |
| that are set by default. They may be overridden further by the application |
| configuration. |
| |
| Returns: |
| A dictionary containing environment variables. |
| """ |
| return {} |
| |
| def GetVariables(self, config_name, use_clang=0): |
| """Get a Mapping of GYP variable overrides. |
| |
| Get a Mapping of GYP variable names to values. Any defined values will |
| override any values defined by default, but can themselves by overridden by |
| the application configuration. GYP files can then have conditionals that |
| check the values of these variables. |
| |
| Args: |
| config_name: The name of the starboard.tools.config build type. |
| use_clang: Whether the toolchain is a flavor of Clang. |
| |
| Raises: |
| RuntimeError: When given conflicting configuration input. |
| |
| Returns: |
| A Mapping of GYP variables to be merged into the global Mapping provided |
| to GYP. |
| """ |
| use_asan = 0 |
| use_tsan = 0 |
| use_source_code_coverage = 0 |
| |
| if use_clang: |
| # Enable source coverage instrumentation if USE_SOURCE_CODE_COVERAGE |
| # was set to 1 in the environment. |
| use_source_code_coverage = int( |
| os.environ.get('USE_SOURCE_CODE_COVERAGE', 0)) |
| |
| # Enable TSAN if USE_TSAN was set to 1 in the environment, unless |
| # use_source_code_coverage is set. |
| use_tsan = int(os.environ.get('USE_TSAN', |
| 0)) if not use_source_code_coverage else 0 |
| |
| # Enable ASAN by default for debug and devel builds only if neither |
| # use_tsan nor use_source_code_coverage is set. |
| use_asan_default = self._asan_default if ( |
| not use_tsan and not use_source_code_coverage and |
| config_name in (Config.DEBUG, Config.DEVEL)) else 0 |
| use_asan = int(os.environ.get('USE_ASAN', use_asan_default)) |
| if use_asan == 1 and use_tsan == 1: |
| raise RuntimeError('ASAN and TSAN are mutually exclusive') |
| |
| if use_source_code_coverage: |
| logging.info('Using Source-Based Code Coverage') |
| |
| if use_asan: |
| logging.info('Using Address Sanitizer') |
| |
| if use_tsan: |
| logging.info('Using Thread Sanitizer') |
| |
| variables = { |
| 'clang': use_clang, |
| |
| # Whether to build with clang's Source Based Code Coverage |
| # instrumentation. |
| # See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html |
| 'use_source_code_coverage': use_source_code_coverage, |
| |
| # Whether to build with clang's Address Sanitizer instrumentation. |
| 'use_asan': use_asan, |
| # Whether to build with clang's Thread Sanitizer instrumentation. |
| 'use_tsan': use_tsan, |
| |
| # Which JavaScript engine to use. Currently, both SpiderMonkey 45 and |
| # V8 are supported. Note that V8 can only be used on platforms that |
| # support JIT. |
| 'javascript_engine': 'v8', |
| |
| # If the path to the Starboard ABI file is able to be formatted, it will |
| # be using the experimental Starboard API version of the file, i.e. |
| # |sabi.SB_API_VERSION|. Otherwise, the value provided is just assumed |
| # to be a complete path to a Starboard ABI file. |
| 'sabi_json_path': |
| self.GetPathToSabiJsonFile().format( |
| sb_api_version=sabi.SB_API_VERSION), |
| |
| # TODO: Remove these compatibility variables. |
| 'cobalt_config': config_name, |
| 'cobalt_fastbuild': 0, |
| 'custom_media_session_client': 0, |
| 'enable_vr': 0, |
| } |
| return variables |
| |
| def GetGeneratorVariables(self, config_name): |
| """Get a Mapping of generator variable overrides. |
| |
| Args: |
| config_name: The name of the starboard.tools.config build type. |
| |
| Returns: |
| A Mapping of generator variable names and values. |
| """ |
| del config_name |
| return {} |
| |
| def GetToolchain(self): |
| """Returns the instance of the toolchain implementation class.""" |
| return None |
| |
| def GetTargetToolchain(self, **kwargs): |
| """Returns a list of target tools.""" |
| # TODO: If this method throws |NotImplementedError|, GYP will fall back to |
| # the legacy toolchain. Once all platforms are migrated to the |
| # abstract toolchain, this method should be made |@abstractmethod|. |
| raise NotImplementedError() |
| |
| def GetHostToolchain(self, **kwargs): |
| """Returns a list of host tools.""" |
| # TODO: If this method throws |NotImplementedError|, GYP will fall back to |
| # the legacy toolchain. Once all platforms are migrated to the |
| # abstract toolchain, this method should be made |@abstractmethod|. |
| raise NotImplementedError() |
| |
| def GetLauncherPath(self): |
| """Gets the path to the launcher module for this platform.""" |
| return self.GetDirectory() |
| |
| def GetLauncher(self): |
| """Gets the module used to launch applications on this platform.""" |
| module_path = os.path.abspath( |
| os.path.join(self.GetLauncherPath(), 'launcher.py')) |
| try: |
| return imp.load_source('launcher', module_path) |
| except (IOError, ImportError, RuntimeError) as error: |
| logging.error('Unable to load launcher module from %s.', module_path) |
| logging.error(error) |
| return None |
| |
| def GetTestEnvVariables(self): |
| """Gets a dict of environment variables needed by unit test binaries.""" |
| return {} |
| |
| def GetTestFilters(self): |
| """Gets all tests to be excluded from a unit test run. |
| |
| Returns: |
| A list of initialized starboard.tools.testing.TestFilter objects. |
| """ |
| return [] |
| |
| def GetDeployPathPatterns(self): |
| """Gets deployment paths patterns for files to be included for deployement. |
| |
| Example: ['deploy/*.exe', 'content/*'] |
| |
| Returns: |
| A list of path wildcard patterns within the PRODUCT_DIR |
| (src/out/<PLATFORM>_<CONFIG>) that need to be deployed in order for the |
| platform launcher to run target executable(s). |
| """ |
| raise NotImplementedError() |
| |
| def GetPathToSabiJsonFile(self): |
| """Gets the path to the JSON file with Starboard ABI information for the |
| build. |
| |
| Examples: |
| 'starboard/sabi/arm64/sabi-v12.json' |
| 'starboard/sabi/arm64/sabi-v{sb_api_version}.json' |
| |
| Returns: |
| A string path to the appropriate Starboard ABI JSON file. This path can |
| either be a complete path, or be capable of being formatted with an |
| integer representing the desired Starboard API version. This file is |
| required for a variety of definitions and variables pertaining to the ABI. |
| """ |
| return 'starboard/sabi/default/sabi.json' |
| |
| def GetTestTargets(self): |
| """Gets all tests to be run in a unit test run. |
| |
| Returns: |
| A list of strings of test target names. |
| """ |
| tests = [ |
| 'app_key_files_test', |
| 'app_key_test', |
| 'drain_file_test', |
| 'elf_loader_test', |
| 'installation_manager_test', |
| 'nplb', |
| 'nplb_blitter_pixel_tests', |
| 'player_filter_tests', |
| 'slot_management_test', |
| 'starboard_platform_tests', |
| ] |
| tests.extend(get_optional_tests.GetOptionalTestTargets()) |
| return tests |
| |
| def GetDefaultTargetBuildFile(self): |
| """Gets the build file to build by default.""" |
| return os.path.join(paths.STARBOARD_ROOT, 'starboard_all.gyp') |