forked from synodic/cppython
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolution.py
More file actions
327 lines (253 loc) · 11.5 KB
/
resolution.py
File metadata and controls
327 lines (253 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"""Data conversion routines"""
import logging
from pathlib import Path
from typing import Any, cast
from packaging.requirements import InvalidRequirement, Requirement
from pydantic import BaseModel, DirectoryPath, ValidationError
from cppython.core.exception import ConfigException
from cppython.core.plugin_schema.generator import Generator, GeneratorPluginGroupData
from cppython.core.plugin_schema.provider import Provider, ProviderPluginGroupData
from cppython.core.plugin_schema.scm import SCM, SCMPluginGroupData
from cppython.core.schema import (
CPPythonData,
CPPythonGlobalConfiguration,
CPPythonLocalConfiguration,
CPPythonModel,
CPPythonPluginData,
PEP621Configuration,
PEP621Data,
Plugin,
PluginGroupData,
ProjectConfiguration,
ProjectData,
)
from cppython.utility.utility import TypeName
def resolve_project_configuration(project_configuration: ProjectConfiguration) -> ProjectData:
"""Creates a resolved type
Args:
project_configuration: Input configuration
Returns:
The resolved data
"""
return ProjectData(project_root=project_configuration.project_root, verbosity=project_configuration.verbosity)
def resolve_pep621(
pep621_configuration: PEP621Configuration, project_configuration: ProjectConfiguration, scm: SCM | None
) -> PEP621Data:
"""Creates a resolved type
Args:
pep621_configuration: Input PEP621 configuration
project_configuration: The input configuration used to aid the resolve
scm: SCM
Raises:
ConfigError: Raised when the tooling did not satisfy the configuration request
ValueError: Raised if there is a broken schema
Returns:
The resolved type
"""
# Update the dynamic version
if 'version' in pep621_configuration.dynamic:
if project_configuration.version is not None:
modified_version = project_configuration.version
elif scm is not None:
modified_version = scm.version(project_configuration.project_root)
else:
raise ValueError("Version can't be resolved. No SCM")
elif pep621_configuration.version is not None:
modified_version = pep621_configuration.version
else:
raise ValueError("Version can't be resolved. Schema error")
pep621_data = PEP621Data(
name=pep621_configuration.name, version=modified_version, description=pep621_configuration.description
)
return pep621_data
def _resolve_absolute_path(path: Path, root_directory: Path) -> Path:
"""Convert a path to absolute, using root_directory as base for relative paths.
Args:
path: The path to resolve
root_directory: The base directory for relative paths
Returns:
The absolute path
"""
if path.is_absolute():
return path
return root_directory / path
class PluginBuildData(CPPythonModel):
"""Data needed to construct CoreData"""
generator_type: type[Generator]
provider_type: type[Provider]
scm_type: type[SCM]
class PluginCPPythonData(CPPythonModel):
"""Plugin data needed to construct CPPythonData"""
generator_name: TypeName
provider_name: TypeName
scm_name: TypeName
def resolve_cppython(
local_configuration: CPPythonLocalConfiguration,
global_configuration: CPPythonGlobalConfiguration,
project_data: ProjectData,
plugin_build_data: PluginCPPythonData,
) -> CPPythonData:
"""Creates a copy and resolves dynamic attributes
Args:
local_configuration: Local project configuration
global_configuration: Shared project configuration
project_data: Project information to aid in the resolution
plugin_build_data: Plugin build data
Raises:
ConfigError: Raised when the tooling did not satisfy the configuration request
Returns:
An instance of the resolved type
"""
root_directory = project_data.project_root.absolute()
# Resolve configuration path
modified_configuration_path = local_configuration.configuration_path
if modified_configuration_path is None:
modified_configuration_path = root_directory / 'cppython.json'
else:
modified_configuration_path = _resolve_absolute_path(modified_configuration_path, root_directory)
# Resolve other paths
modified_install_path = _resolve_absolute_path(local_configuration.install_path, root_directory)
modified_tool_path = _resolve_absolute_path(local_configuration.tool_path, root_directory)
modified_build_path = _resolve_absolute_path(local_configuration.build_path, root_directory)
modified_provider_name = plugin_build_data.provider_name
modified_generator_name = plugin_build_data.generator_name
modified_scm_name = plugin_build_data.scm_name
# Extract provider and generator configuration data
provider_type_name = TypeName(modified_provider_name)
generator_type_name = TypeName(modified_generator_name)
provider_data = {}
if local_configuration.providers and provider_type_name in local_configuration.providers:
provider_data = local_configuration.providers[provider_type_name]
generator_data = {}
if local_configuration.generators and generator_type_name in local_configuration.generators:
generator_data = local_configuration.generators[generator_type_name]
# Construct dependencies from the local configuration only
dependencies: list[Requirement] = []
invalid_requirements: list[str] = []
if local_configuration.dependencies:
for dependency in local_configuration.dependencies:
try:
dependencies.append(Requirement(dependency))
except InvalidRequirement as error:
invalid_requirements.append(f"Invalid requirement '{dependency}': {error}")
# Construct dependency groups from the local configuration
dependency_groups: dict[str, list[Requirement]] = {}
if local_configuration.dependency_groups:
for group_name, group_dependencies in local_configuration.dependency_groups.items():
resolved_group: list[Requirement] = []
for dependency in group_dependencies:
try:
resolved_group.append(Requirement(dependency))
except InvalidRequirement as error:
invalid_requirements.append(f"Invalid requirement '{dependency}' in group '{group_name}': {error}")
dependency_groups[group_name] = resolved_group
if invalid_requirements:
raise ConfigException('\n'.join(invalid_requirements))
cppython_data = CPPythonData(
configuration_path=modified_configuration_path,
install_path=modified_install_path,
tool_path=modified_tool_path,
build_path=modified_build_path,
current_check=global_configuration.current_check,
provider_name=modified_provider_name,
generator_name=modified_generator_name,
scm_name=modified_scm_name,
dependencies=dependencies,
dependency_groups=dependency_groups,
provider_data=provider_data,
generator_data=generator_data,
)
return cppython_data
def resolve_cppython_plugin(cppython_data: CPPythonData, plugin_type: type[Plugin]) -> CPPythonPluginData:
"""Resolve project configuration for plugins
Args:
cppython_data: The CPPython data
plugin_type: The plugin type
Returns:
The resolved type with plugin specific modifications
"""
# Add plugin specific paths to the base path
modified_install_path = cppython_data.install_path / plugin_type.name()
plugin_data = cppython_data.model_copy(update={'install_path': modified_install_path})
return cast(CPPythonPluginData, plugin_data)
def _write_tool_directory(cppython_data: CPPythonData, directory: Path) -> DirectoryPath:
"""Creates directories following a certain format
Args:
cppython_data: The cppython data
directory: The directory to create
Returns:
The written path
"""
plugin_directory = cppython_data.tool_path / 'cppython' / directory
return plugin_directory
def _resolve_plugin_group[T: PluginGroupData](
project_data: ProjectData, cppython_data: CPPythonPluginData, category: str, plugin_name: str, group_type: type[T]
) -> T:
"""Generic helper to resolve plugin group data.
Args:
project_data: The input project data
cppython_data: The input cppython data
category: The subfolder category (e.g. 'generators', 'providers', 'managers')
plugin_name: The name of the specific plugin
group_type: The PluginGroupData subclass to construct
Returns:
The plugin specific configuration
"""
root_directory = project_data.project_root
tool_directory = _write_tool_directory(cppython_data, Path(category) / plugin_name)
return group_type(root_directory=root_directory, tool_directory=tool_directory)
def resolve_generator(project_data: ProjectData, cppython_data: CPPythonPluginData) -> GeneratorPluginGroupData:
"""Creates generator plugin group data from the given project.
Args:
project_data: The input project data
cppython_data: The input cppython data
Returns:
The plugin specific configuration
"""
return _resolve_plugin_group(
project_data, cppython_data, 'generators', cppython_data.generator_name, GeneratorPluginGroupData
)
def resolve_provider(project_data: ProjectData, cppython_data: CPPythonPluginData) -> ProviderPluginGroupData:
"""Creates provider plugin group data from the given project.
Args:
project_data: The input project data
cppython_data: The input cppython data
Returns:
The plugin specific configuration
"""
return _resolve_plugin_group(
project_data, cppython_data, 'providers', cppython_data.provider_name, ProviderPluginGroupData
)
def resolve_scm(project_data: ProjectData, cppython_data: CPPythonPluginData) -> SCMPluginGroupData:
"""Creates SCM plugin group data from the given project.
Args:
project_data: The input project data
cppython_data: The input cppython data
Returns:
The plugin specific configuration
"""
return _resolve_plugin_group(project_data, cppython_data, 'managers', cppython_data.scm_name, SCMPluginGroupData)
def resolve_model[T: BaseModel](model: type[T], data: dict[str, Any]) -> T:
"""Wraps the model validation and conversion
Args:
model: The model to create
data: The input data to create the model from
Raises:
ConfigException: Raised when the input does not satisfy the given schema
Returns:
The instance of the model
"""
try:
# BaseModel is setup to ignore extra fields
return model(**data)
except ValidationError as e:
# Log the raw ValidationError for debugging
logging.getLogger('cppython').debug('ValidationError details: %s', e.errors())
if e.errors():
formatted_errors = '\n'.join(
f"Field '{'.'.join(map(str, error['loc']))}': {error['msg']}"
for error in e.errors(include_input=True, include_context=True)
)
else:
formatted_errors = 'An unknown validation error occurred.'
raise ConfigException(f'The input project failed validation:\n{formatted_errors}') from e