Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 131 additions & 70 deletions cppython/builder.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Everything needed to build a CPPython project
"""

import logging
from importlib import metadata
from inspect import getmodule
from logging import Logger
from pathlib import Path
from typing import Any

from cppython_core.exceptions import ConfigError, PluginError
from cppython_core.exceptions import PluginError
from cppython_core.plugin_schema.generator import Generator
from cppython_core.plugin_schema.provider import Provider
from cppython_core.plugin_schema.scm import SCM
Expand All @@ -20,15 +20,17 @@
resolve_pep621,
resolve_project_configuration,
resolve_provider,
resolve_scm,
)
from cppython_core.schema import (
CoreData,
CorePluginData,
CPPythonGlobalConfiguration,
CPPythonLocalConfiguration,
DataPluginT,
PEP621Configuration,
PEP621Data,
ProjectConfiguration,
ProjectData,
PyProject,
)


Expand All @@ -38,90 +40,104 @@ class Builder:
def __init__(self, logger: Logger) -> None:
self.logger = logger

def generate_core_data(
self,
configuration: ProjectConfiguration,
pep621_configuration: PEP621Configuration,
cppython_configuration: CPPythonLocalConfiguration,
plugin_build_date: PluginBuildData,
) -> CoreData:
"""Parses and returns resolved data from all configuration sources
def setup_logger(self, project_configuration: ProjectConfiguration) -> None:
"""_summary_

Args:
configuration: Input configuration
pep621_configuration: Project table configuration
cppython_configuration: Tool configuration
plugin_build_date: Build data
project_configuration: _description_
"""
# Default logging levels
levels = [logging.WARNING, logging.INFO, logging.DEBUG]

Raises:
ConfigError: Raised if data cannot be parsed
# Add default output stream
self.logger.addHandler(logging.StreamHandler())
self.logger.setLevel(levels[project_configuration.verbosity])

self.logger.info("Logging setup complete")

def generate_project_data(self, project_configuration: ProjectConfiguration) -> ProjectData:
"""_summary_

Args:
project_configuration: _description_

Returns:
The resolved core object
_description_
"""

global_configuration = CPPythonGlobalConfiguration()
return resolve_project_configuration(project_configuration)

project_data = resolve_project_configuration(configuration)
def generate_data_plugins(self, pyproject: PyProject) -> PluginBuildData:
"""_summary_

try:
pep621_data = resolve_pep621(pep621_configuration, configuration)
Args:
pyproject: _description_

except ConfigError:
configuration.version = self.extract_scm_version(configuration.pyproject_file.parent)
pep621_data = resolve_pep621(pep621_configuration, configuration)
Returns:
_description_
"""

cppython_data = resolve_cppython(cppython_configuration, global_configuration, project_data, plugin_build_date)
raw_generator_plugins = self.find_generators()
generator_plugins = self.filter_plugins(
raw_generator_plugins,
pyproject.tool.cppython.generator_name,
"Generator",
)

return CoreData(project_data=project_data, pep621_data=pep621_data, cppython_data=cppython_data)
raw_provider_plugins = self.find_providers()
provider_plugins = self.filter_plugins(
raw_provider_plugins,
pyproject.tool.cppython.provider_name,
"Provider",
)

def extract_scm_version(self, path: Path) -> str:
"""Locates an available SCM plugin that can report version information about the given path
# Solve the messy interactions between plugins
generator_type, provider_type = self.solve(generator_plugins, provider_plugins)

Args:
path: The directory to query
return PluginBuildData(generator_type=generator_type, provider_type=provider_type)

Raises:
PluginError: If no SCM plugin can be found
def generate_pep621_data(
self, pyproject: PyProject, project_configuration: ProjectConfiguration, scm: SCM | None
) -> PEP621Data:
"""_summary_

Args:
pyproject: _description_
project_configuration: _description_
scm: _description_

Returns:
A version token
_description_
"""
return resolve_pep621(pyproject.project, project_configuration, scm)

group = "SCM"
group_lower = group.lower()

scm_types: list[type[SCM]] = []
def generate_core_data(
self,
project_data: ProjectData,
pyproject: PyProject,
pep621_data: PEP621Data,
plugin_build_date: PluginBuildData,
) -> CoreData:
"""Parses and returns resolved data from all configuration sources

if not (entries := list(metadata.entry_points(group=f"cppython.{group_lower}"))):
raise PluginError("No SCM plugin found")
Args:
project_data: Project data
pyproject: TODO
pep621_data: TODO
plugin_build_date: TODO

# Filter entries
for entry_point in entries:
plugin_type = entry_point.load()
if not issubclass(plugin_type, SCM):
self.logger.warning(
f"Found incompatible plugin. The '{resolve_name(plugin_type)}' plugin must be an instance of"
f" '{group_lower}'"
)
else:
scm_types.append(plugin_type)
Raises:
ConfigError: Raised if data cannot be parsed

# Deduce the SCM repository
plugin = None
for scm_type in scm_types:
scm = scm_type()
if scm.supported(path):
plugin = scm
break
Returns:
The resolved core object
"""

if not plugin:
raise PluginError("No applicable SCM plugin found for the given path")
global_configuration = CPPythonGlobalConfiguration()

if (version := plugin.version(path)) is None:
raise PluginError("Project has no version information")
cppython_data = resolve_cppython(pyproject.tool.cppython, global_configuration, project_data, plugin_build_date)

return version
return CoreData(project_data=project_data, pep621_data=pep621_data, cppython_data=cppython_data)

def find_generators(self) -> list[type[Generator]]:
"""_summary_
Expand Down Expand Up @@ -188,13 +204,12 @@ def find_providers(self) -> list[type[Provider]]:
return plugin_types

def filter_plugins(
self, plugin_types: list[type[DataPluginT]], directory: Path, pinned_name: str | None, group_name: str
self, plugin_types: list[type[DataPluginT]], pinned_name: str | None, group_name: str
) -> list[type[DataPluginT]]:
"""Finds and filters data plugins

Args:
plugin_types: The plugin type to lookup
directory: The data to query support for the filtered plugins
pinned_name: The configuration name
group_name: The group name

Expand All @@ -220,11 +235,10 @@ def filter_plugins(

# Deduce types
for loaded_type in plugin_types:
if loaded_type.supported(directory):
self.logger.warning(
f"A {group_name} plugin is supported: {resolve_name(loaded_type)} from {getmodule(loaded_type)}"
)
supported_types.append(loaded_type)
self.logger.warning(
f"A {group_name} plugin is supported: {resolve_name(loaded_type)} from {getmodule(loaded_type)}"
)
supported_types.append(loaded_type)

# Fail
if supported_types is None:
Expand Down Expand Up @@ -263,6 +277,53 @@ def solve(

return combos[0]

def create_scm(
self,
project_data: ProjectData,
) -> SCM | None:
"""_summary_

Args:
project_data: _description_

Raises:
PluginError: Ya

Returns:
_description_
"""
group = "scm"
path = project_data.pyproject_file.parent

scm_types: list[type[SCM]] = []

if not (entries := list(metadata.entry_points(group=f"cppython.{group}"))):
raise PluginError("No SCM plugin found")

# Filter entries
for entry_point in entries:
plugin_type = entry_point.load()
if not issubclass(plugin_type, SCM):
self.logger.warning(
f"Found incompatible plugin. The '{resolve_name(plugin_type)}' plugin must be an instance of"
f" '{group}'"
)
else:
scm_types.append(plugin_type)

# Deduce the SCM repository
plugin = None
for scm_type in scm_types:
if scm_type.features(path).repository:
scm_data = resolve_scm(project_data)
plugin = scm_type(scm_data)
break

if not plugin:
self.logger.error("No applicable SCM plugin found for the given path")

return plugin

def create_generator(
self, core_data: CoreData, generator_configuration: dict[str, Any], generator_type: type[Generator]
) -> Generator:
Expand Down
28 changes: 18 additions & 10 deletions cppython/plugins/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from pathlib import Path

from cppython_core.plugin_schema.scm import SCM
from cppython_core.plugin_schema.scm import (
SCM,
SCMPluginGroupData,
SupportedSCMFeatures,
)
from cppython_core.schema import Information
from dulwich.errors import NotGitRepository
from dulwich.repo import Repo
Expand All @@ -11,23 +15,27 @@
class GitSCM(SCM):
"""Git implementation hooks"""

def __init__(self, group_data: SCMPluginGroupData) -> None:
self.group_data = group_data

@staticmethod
def supported(directory: Path) -> bool:
"""Queries repository status of a path
def features(directory: Path) -> SupportedSCMFeatures:
"""Broadcasts the shared features of the SCM plugin to CPPython

Args:
directory: The input path to query
directory: The root directory where features are evaluated

Returns:
Whether the given path is a repository root
The supported features
"""

is_repository = True
try:
Repo(str(directory))
return True

except NotGitRepository:
return False
is_repository = False

return SupportedSCMFeatures(repository=is_repository)

@staticmethod
def information() -> Information:
Expand All @@ -38,11 +46,11 @@ def information() -> Information:
"""
return Information()

def version(self, path: Path) -> str:
def version(self, directory: Path) -> str:
"""Extracts the system's version metadata

Args:
path: The repository path
directory: The repository path

Returns:
The git version
Expand Down
Loading