# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from abc import ABC, abstractmethod import importlib import importlib.util import os import sys from .exceptions import set_deprecations_are_errors, UserException from .pyproject import PyProject, PyProjectOptionException class AbstractProject(ABC): """ This specifies the API of a project. """ @classmethod def bootstrap(cls, tool, tool_description='', arguments=None): """ Return an AbstractProject instance fully configured for a particular command line tool. """ # We have to do this very early. if '--deprecations-are-errors' in sys.argv: set_deprecations_are_errors(True) # Get the contents of the pyproject.toml file. pyproject = PyProject() # Get the name of the project factory. project_factory_name = None sip_section_name = 'tool.sip' value_name = 'project-factory' sip_section = pyproject.get_section(sip_section_name) if sip_section is not None: f_name = sip_section.get(value_name) if f_name is not None: if not isinstance(f_name, str): raise PyProjectOptionException(value_name, "should be a 'str' and not '{0}'".format( type(f_name).__name__), section_name=sip_section_name) project_factory_name = f_name # See if there is a corresponding callable. if project_factory_name is None: default_factory_py = 'project.py' if os.path.isfile(default_factory_py): project_factory = cls.import_callable(default_factory_py, cls) else: # The default project factory. from .project import Project as project_factory else: project_factory = cls.import_callable(project_factory_name, cls) project = project_factory() if not isinstance(project, cls): raise UserException( "The project factory did not return an AbstractProject " "object") # We set this as an attribute rather than change the API of the ctor or # setup(). project.arguments = arguments # Complete the configuration of the project. project.setup(pyproject, tool, tool_description) return project @abstractmethod def build(self): """ Build the project in-situ. """ @abstractmethod def build_sdist(self, sdist_directory): """ Build an sdist for the project and return the name of the sdist file. """ @abstractmethod def build_wheel(self, wheel_directory): """ Build a wheel for the project and return the name of the wheel file. """ @staticmethod def import_callable(name, base_type): """ Import a callable from either a .py file or specified as module[:object]. """ # See if the name refers to a .py file. if name.endswith('.py'): name = name.replace('/', os.sep) module_name = name[:-3] object_name = None # Try and import the .py file. spec = importlib.util.spec_from_file_location(module_name, name) module = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(module) except Exception as e: raise UserException("Unable to import '{0}'".format(name), detail=str(e)) else: # Extract the module and any object name. parts = name.split(':') if len(parts) > 2: raise UserException( "The callable '{0}' must be specified as " "'module[:name]'".format(name)) module_name = parts[0] object_name = parts[1] if len(parts) == 2 else None # Try and import the module. try: module = importlib.import_module(module_name) except ImportError as e: raise UserException( "Unable to import '{0}'".format(module_name), detail=str(e)) # Get the callable object from the module. if object_name is None: # Look for a class that is a sub-class of the base type. for obj in module.__dict__.values(): if isinstance(obj, type): if issubclass(obj, base_type): # Make sure the type is defined in the module and not # imported by it. if obj.__module__ == module_name: break else: raise UserException( "'{0}' does not define a {1} sub-class".format(name, base_type.__name__)) else: # We have the name of the callable so just get it. obj = getattr(module, object_name) if obj is None: raise UserException( "'{0}' module has no callable '{1}'".format( module_name, object_name)) return obj @abstractmethod def install(self): """ Install the project. """ @abstractmethod def setup(self, pyproject, tool, tool_description): """ Complete the configuration of the project. """