blob: 68437be559aaf99b5afb3bf0d70cdb0676530cd3 [file] [log] [blame]
#
# Copyright 2016 Google Inc. 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.
#
"""Classes related to building and installing platform-specific packages."""
import abc
import importlib
import logging
import os
import _env # pylint: disable=unused-import
import starboard
from starboard.tools import platform
def _ImportModule(path, root_module, module_name=None):
"""Load a platform specific python module using importlib.
Load the python module named |module_name| relative to |root_module|.
Args:
path: Path to the platform
root_module: An already-loaded module
module_name: Name of a python module to load. If None, load the platform
directory as a python module.
Returns:
A module loaded with importlib.import_module
Throws:
ImportError if the module fails to be loaded.
"""
# From the relative path to the |root_module|'s directory, construct a full
# python package name and attempt to load it.
relative_path = os.path.relpath(path, os.path.dirname(root_module.__file__))
full_path = os.path.join(root_module.__name__, relative_path)
# This normpath may collapse out the root module. This is generally OK as long
# as we stay within the workspace, as _env will add the workspace root to the
# system import path.
components = os.path.normpath(full_path).split(os.sep)
if module_name:
components.append(module_name)
full_package_name = '.'.join(components)
return importlib.import_module(full_package_name)
def _GetPackageClass(platform_info):
"""Loads the package class associated with the given platform.PlatformInfo."""
try:
module = _ImportModule(platform_info.path, starboard)
except ImportError as e:
logging.debug('Failed to import module for platform %s with error: %s',
platform_info.name, e)
return None
if not hasattr(module, 'Package'):
return None
return module.Package
def _GetPlatformInfosDict():
"""Find Starboard ports that support building packages.
Returns:
A dict of [platform_name, Class] where Class inherits from PackageBase
"""
packager_modules = {}
for platform_name in platform.GetAll():
platform_info = platform.Get(platform_name)
# From the relative path to the starboard directory, construct a full
# python package name and attempt to load it.
package_class = _GetPackageClass(platform_info)
if not package_class:
continue
# Populate a mapping from platform name to the module containing the
# Package class.
try:
for supported_name in package_class.SupportedPlatforms():
if supported_name in packager_modules:
logging.warning('Packager for %s is defined in multiple modules.',
supported_name)
else:
packager_modules[supported_name] = platform_info
except Exception as e: # pylint: disable=broad-except
# Catch all exceptions to avoid an error in one platform's Packager
# halting the script for other platforms' packagers.
logging.warning('Exception iterating supported platform for platform '
'%s: %s.', platform_info.name, e)
return packager_modules
class PackageBase(object):
"""Represents a build package that exists on the local filesystem."""
__metaclass__ = abc.ABCMeta
def __init__(self, source_dir, output_dir):
"""Initialize common paths for building Packages.
Args:
source_dir: The directory containing the application to be packaged.
output_dir: The directory into which the package should be placed.
"""
self.source_dir = source_dir
self.output_dir = output_dir
@abc.abstractmethod
def Install(self, targets=None):
"""Install the package to the specified list of targets.
Args:
targets: A list of targets to install the package to, or None on platforms
that support installing to a default target.
This method can be overridden to implement platform-specific steps to
install the package for that platform.
"""
del targets
@classmethod
def AddArguments(cls, arg_parser):
"""Add platform-specific command-line arguments to the ArgumentParser.
Platforms that require additional arguments to configure building a package
can override this method and add them to |arg_parser|.
Args:
arg_parser: An ArgumentParser object.
"""
del cls, arg_parser
@classmethod
def ExtractArguments(cls, options):
"""Extract arguments from an ArgumentParser's namespace object.
Platforms that add additional arguments can override this method to extract
the options and add them to a dict that will be passed to the Package
constructor.
Args:
options: A namespace object returned from ArgumentParser.parse_args
Returns:
A dict of kwargs to be passed to the Package constructor.
"""
del cls, options
return {}
class Packager(object):
"""Top level class for building a package."""
def __init__(self):
self.platform_infos = _GetPlatformInfosDict()
def SupportedPlatforms(self):
"""Get a list of platforms for which a package can be built."""
return self.platform_infos.keys()
def GetPlatformInfo(self, platform_name):
return self.platform_infos.get(platform_name, None)
def GetApplicationPackageInfo(self, platform_name, applciation_name):
"""Get application-specific packaging information."""
platform_info = self.GetPlatformInfo(platform_name)
try:
return _ImportModule(platform_info.path, starboard,
'%s.package' % applciation_name)
except ImportError as e:
# No package parameters specified for this platform.
logging.debug('Failed to import cobalt.package: %s', e)
return None
def BuildPackage(self, platform_name, source_dir, output_dir, **kwargs):
"""Build a package for the specified platform.
Args:
platform_name: The platform for which a package should be built.
source_dir: The directory containing the application to be packaged.
output_dir: The directory into which the package files should be placed.
**kwargs: Platform-specific arguments.
Returns:
A PackageBase instance.
"""
package_class = _GetPackageClass(self.platform_infos[platform_name])
return package_class(source_dir=source_dir, output_dir=output_dir, **kwargs)
def AddPlatformArguments(self, platform_name, argparser):
package_class = _GetPackageClass(self.platform_infos[platform_name])
package_class.AddArguments(argparser)
def ExtractPlatformArguments(self, platform_name, options):
package_class = _GetPackageClass(self.platform_infos[platform_name])
return package_class.ExtractArguments(options)