microproduct/atmosphericDelay/ISCEApp/site-packages/stone/compiler.py

127 lines
4.4 KiB
Python
Raw Normal View History

2023-08-28 10:17:29 +00:00
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
import inspect
import os
import shutil
import traceback
from stone.backend import (
Backend,
remove_aliases_from_api,
)
class BackendException(Exception):
"""Saves the traceback of an exception raised by a backend."""
def __init__(self, backend_name, tb):
"""
:type backend_name: str
:type tb: str
"""
super(BackendException, self).__init__()
self.backend_name = backend_name
self.traceback = tb
class Compiler(object):
"""
Applies a collection of backends found in a single backend module to an
API specification.
"""
backend_extension = '.stoneg'
def __init__(self,
api,
backend_module,
backend_args,
build_path,
clean_build=False):
"""
Creates a Compiler.
:param stone.ir.Api api: A Stone description of the API.
:param backend_module: Python module that contains at least one
top-level class definition that descends from a
:class:`stone.backend.Backend`.
:param list(str) backend_args: A list of command-line arguments to
pass to the backend.
:param str build_path: Location to save compiled sources to. If None,
source files are compiled into the same directories.
:param bool clean_build: If True, the build_path is removed before
source files are compiled into them.
"""
self._logger = logging.getLogger('stone.compiler')
self.api = api
self.backend_module = backend_module
self.backend_args = backend_args
self.build_path = build_path
# Remove existing build directory if it's a clean build
if clean_build and os.path.exists(self.build_path):
logging.info('Cleaning existing build directory %s...',
self.build_path)
shutil.rmtree(self.build_path)
def build(self):
"""Creates outputs. Outputs are files made by a backend."""
if os.path.exists(self.build_path) and not os.path.isdir(self.build_path):
self._logger.error('Output path must be a folder if it already exists')
return
Compiler._mkdir(self.build_path)
self._execute_backend_on_spec()
@staticmethod
def _mkdir(path):
"""
Creates a directory at path if it doesn't exist. If it does exist,
this function does nothing. Note that if path is a file, it will not
be converted to a directory.
"""
try:
os.makedirs(path)
except OSError as e:
if e.errno != 17:
raise
@classmethod
def is_stone_backend(cls, path):
"""
Returns True if the file name matches the format of a stone backend,
ie. its inner extension of "stoneg". For example: xyz.stoneg.py
"""
path_without_ext, _ = os.path.splitext(path)
_, second_ext = os.path.splitext(path_without_ext)
return second_ext == cls.backend_extension
def _execute_backend_on_spec(self):
"""Renders a source file into its final form."""
api_no_aliases_cache = None
for attr_key in dir(self.backend_module):
attr_value = getattr(self.backend_module, attr_key)
if (inspect.isclass(attr_value) and
issubclass(attr_value, Backend) and
not inspect.isabstract(attr_value)):
self._logger.info('Running backend: %s', attr_value.__name__)
backend = attr_value(self.build_path, self.backend_args)
if backend.preserve_aliases:
api = self.api
else:
if not api_no_aliases_cache:
api_no_aliases_cache = remove_aliases_from_api(self.api)
api = api_no_aliases_cache
try:
backend.generate(api)
except Exception:
# Wrap this exception so that it isn't thought of as a bug
# in the stone parser, but rather a bug in the backend.
# Remove the last char of the traceback b/c it's a newline.
raise BackendException(
attr_value.__name__, traceback.format_exc()[:-1])