lockss-turtles 0.6.0.dev21__py3-none-any.whl → 0.6.0.dev22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lockss/turtles/__init__.py +1 -1
- lockss/turtles/cli.py +12 -16
- lockss/turtles/plugin_registry.py +468 -44
- lockss/turtles/plugin_set.py +325 -79
- lockss/turtles/util.py +44 -3
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/METADATA +2 -2
- lockss_turtles-0.6.0.dev22.dist-info/RECORD +15 -0
- lockss_turtles-0.6.0.dev21.dist-info/RECORD +0 -15
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/LICENSE +0 -0
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/WHEEL +0 -0
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/entry_points.txt +0 -0
lockss/turtles/plugin_set.py
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
30
|
|
|
31
31
|
"""
|
|
32
|
-
|
|
32
|
+
Library to represent plugin sets and plugin set catalogs.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
# Remove in Python 3.14
|
|
@@ -49,6 +49,7 @@ from pydantic import BaseModel, Field
|
|
|
49
49
|
from .plugin import Plugin, PluginIdentifier
|
|
50
50
|
from .util import BaseModelWithRoot
|
|
51
51
|
|
|
52
|
+
|
|
52
53
|
#: A type alias for the plugin set catalog kind.
|
|
53
54
|
PluginSetCatalogKind = Literal['PluginSetCatalog']
|
|
54
55
|
|
|
@@ -58,10 +59,16 @@ class PluginSetCatalog(BaseModelWithRoot):
|
|
|
58
59
|
A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent a
|
|
59
60
|
plugin set catalog.
|
|
60
61
|
"""
|
|
62
|
+
|
|
61
63
|
#: This object's kind.
|
|
62
|
-
kind: PluginSetCatalogKind = Field(title='Kind',
|
|
64
|
+
kind: PluginSetCatalogKind = Field(title='Kind',
|
|
65
|
+
description="This object's kind")
|
|
66
|
+
|
|
63
67
|
#: A non-empty list of plugin set files.
|
|
64
|
-
plugin_set_files: list[str] = Field(alias='plugin-set-files',
|
|
68
|
+
plugin_set_files: list[str] = Field(alias='plugin-set-files',
|
|
69
|
+
min_length=1,
|
|
70
|
+
title='Plugin Set Files',
|
|
71
|
+
description="A non-empty list of plugin set files")
|
|
65
72
|
|
|
66
73
|
def get_plugin_set_files(self) -> list[Path]:
|
|
67
74
|
"""
|
|
@@ -82,18 +89,27 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
|
|
|
82
89
|
"""
|
|
83
90
|
An abstract Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to
|
|
84
91
|
represent a plugin set builder, with concrete implementations
|
|
85
|
-
``
|
|
92
|
+
``MavenPluginSetBuilder`` and ``AntPluginSetBuilder``.
|
|
86
93
|
"""
|
|
87
94
|
|
|
88
95
|
#: Pydantic definition of the ``type`` field.
|
|
89
|
-
TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Builder Type',
|
|
96
|
+
TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Builder Type',
|
|
97
|
+
description='A plugin builder type')
|
|
98
|
+
|
|
90
99
|
#: Pydantic definition of the ``main`` field.
|
|
91
|
-
MAIN_FIELD: ClassVar[dict[str, str]] = dict(title='Main Code Path',
|
|
100
|
+
MAIN_FIELD: ClassVar[dict[str, str]] = dict(title='Main Code Path',
|
|
101
|
+
description="The path to the plugins' source code, relative to the root of the project")
|
|
102
|
+
|
|
92
103
|
#: Pydantic definition of the ``test`` field.
|
|
93
|
-
TEST_FIELD: ClassVar[dict[str, str]] = dict(title='Test Code Path',
|
|
104
|
+
TEST_FIELD: ClassVar[dict[str, str]] = dict(title='Test Code Path',
|
|
105
|
+
description="The path to the plugins' unit tests, relative to the root of the project")
|
|
94
106
|
|
|
95
107
|
@abstractmethod
|
|
96
|
-
def build_plugin(self,
|
|
108
|
+
def build_plugin(self,
|
|
109
|
+
plugin_id: PluginIdentifier,
|
|
110
|
+
keystore_path: Path,
|
|
111
|
+
keystore_alias: str,
|
|
112
|
+
keystore_password=None) -> tuple[Path, Plugin]:
|
|
97
113
|
"""
|
|
98
114
|
Builds the given plugin, using the given plugin signing credentials.
|
|
99
115
|
|
|
@@ -143,7 +159,8 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
|
|
|
143
159
|
"""
|
|
144
160
|
return getattr(self, 'type')
|
|
145
161
|
|
|
146
|
-
def has_plugin(self,
|
|
162
|
+
def has_plugin(self,
|
|
163
|
+
plugin_id: PluginIdentifier) -> bool:
|
|
147
164
|
"""
|
|
148
165
|
Determines if the given plugin identifier represents a plugin that is
|
|
149
166
|
present in the plugin set.
|
|
@@ -155,7 +172,8 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
|
|
|
155
172
|
"""
|
|
156
173
|
return self._plugin_path(plugin_id).is_file()
|
|
157
174
|
|
|
158
|
-
def make_plugin(self,
|
|
175
|
+
def make_plugin(self,
|
|
176
|
+
plugin_id: PluginIdentifier) -> Plugin:
|
|
159
177
|
"""
|
|
160
178
|
Makes a ``Plugin`` object from the given plugin identifier.
|
|
161
179
|
|
|
@@ -184,7 +202,8 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
|
|
|
184
202
|
"""
|
|
185
203
|
return getattr(self, 'test')
|
|
186
204
|
|
|
187
|
-
def _plugin_path(self,
|
|
205
|
+
def _plugin_path(self,
|
|
206
|
+
plugin_id: PluginIdentifier) -> Path:
|
|
188
207
|
"""
|
|
189
208
|
Returns the path of the plugin file for the given plugin identifier
|
|
190
209
|
relative to the plugin set's main code path.
|
|
@@ -197,23 +216,188 @@ class BasePluginSetBuilder(BaseModelWithRoot, ABC):
|
|
|
197
216
|
return self.get_main().joinpath(Plugin.id_to_file(plugin_id))
|
|
198
217
|
|
|
199
218
|
|
|
219
|
+
class MavenPluginSetBuilder(BasePluginSetBuilder):
|
|
220
|
+
"""
|
|
221
|
+
A plugin set builder that uses `Java <https://www.oracle.com/java/>`_
|
|
222
|
+
Development Kit (JDK) 17 and `Apache Maven <https://maven.apache.org/>`_,
|
|
223
|
+
with the parent POM
|
|
224
|
+
`org.lockss:lockss-plugins-parent-pom <https://central.sonatype.com/artifact/org.lockss/lockss-plugins-parent-pom>`_.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
#: The default value for ``main``.
|
|
228
|
+
DEFAULT_MAIN: ClassVar[str] = 'src/main/java'
|
|
229
|
+
|
|
230
|
+
#: The default value for ``test``.
|
|
231
|
+
DEFAULT_TEST: ClassVar[str] = 'src/test/java'
|
|
232
|
+
|
|
233
|
+
#: This Plugin set builder's type.
|
|
234
|
+
type: Literal['maven'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
|
|
235
|
+
|
|
236
|
+
#: This plugin set builder's main code path.
|
|
237
|
+
main: Optional[str] = Field(DEFAULT_MAIN,
|
|
238
|
+
**BasePluginSetBuilder.MAIN_FIELD)
|
|
239
|
+
|
|
240
|
+
#: This plugin set builder's unit test path.
|
|
241
|
+
test: Optional[str] = Field(DEFAULT_TEST,
|
|
242
|
+
**BasePluginSetBuilder.TEST_FIELD)
|
|
243
|
+
|
|
244
|
+
#: An internal flag to remember if a build has occurred.
|
|
245
|
+
_built: bool
|
|
246
|
+
|
|
247
|
+
def build_plugin(self,
|
|
248
|
+
plugin_id: PluginIdentifier,
|
|
249
|
+
keystore_path: Path,
|
|
250
|
+
keystore_alias: str,
|
|
251
|
+
keystore_password=None) -> tuple[Path, Plugin]:
|
|
252
|
+
"""
|
|
253
|
+
Builds the given plugin with the supplied plugin signing credentials.
|
|
254
|
+
|
|
255
|
+
:param plugin_id: A plugin identifier.
|
|
256
|
+
:type plugin_id: PluginIdentifier
|
|
257
|
+
:param keystore_path: The path to the plugin signing keystore.
|
|
258
|
+
:type keystore_path: Path
|
|
259
|
+
:param keystore_alias: The alias to use in the plugin signing keystore.
|
|
260
|
+
:type keystore_alias: str
|
|
261
|
+
:param keystore_password: The plugin signing password.
|
|
262
|
+
:type keystore_password:
|
|
263
|
+
:return: A tuple of the built and signed JAR file and a Plugin object
|
|
264
|
+
instantiated from it.
|
|
265
|
+
:rtype: tuple[Path, Plugin]
|
|
266
|
+
:raises subprocess.CalledProcessError: If a subprocess fails.
|
|
267
|
+
:raises FileNotFoundError: If the expected built and signed plugin JAR
|
|
268
|
+
path is unexpectedly not found despite the
|
|
269
|
+
build.
|
|
270
|
+
"""
|
|
271
|
+
self._big_build(keystore_path, keystore_alias, keystore_password=keystore_password)
|
|
272
|
+
return self._little_build(plugin_id)
|
|
273
|
+
|
|
274
|
+
def model_post_init(self,
|
|
275
|
+
context: Any) -> None:
|
|
276
|
+
"""
|
|
277
|
+
Pydantic post-initialization method to initialize the ``_built`` flag.
|
|
278
|
+
|
|
279
|
+
:param context: The Pydantic context.
|
|
280
|
+
:type context: Any
|
|
281
|
+
"""
|
|
282
|
+
super().model_post_init(context)
|
|
283
|
+
self._built = False
|
|
284
|
+
|
|
285
|
+
def _big_build(self,
|
|
286
|
+
keystore_path: Path,
|
|
287
|
+
keystore_alias: str,
|
|
288
|
+
keystore_password: str=None) -> None:
|
|
289
|
+
"""
|
|
290
|
+
Runs ``mvn package`` on the project if the ``_built`` flag is False.
|
|
291
|
+
|
|
292
|
+
:param keystore_path: The path to the plugin signing keystore.
|
|
293
|
+
:type keystore_path: Path
|
|
294
|
+
:param keystore_alias: The alias to use in the plugin signing keystore.
|
|
295
|
+
:type keystore_alias: str
|
|
296
|
+
:param keystore_password: The plugin signing password.
|
|
297
|
+
:type keystore_password:
|
|
298
|
+
:raises subprocess.CalledProcessError: If a subprocess fails.
|
|
299
|
+
"""
|
|
300
|
+
if not self._built:
|
|
301
|
+
# Do build
|
|
302
|
+
cmd = ['mvn', 'package',
|
|
303
|
+
f'-Dkeystore.file={keystore_path!s}',
|
|
304
|
+
f'-Dkeystore.alias={keystore_alias}',
|
|
305
|
+
f'-Dkeystore.password={keystore_password}']
|
|
306
|
+
try:
|
|
307
|
+
subprocess.run(cmd, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
308
|
+
except subprocess.CalledProcessError as cpe:
|
|
309
|
+
raise self._sanitize(cpe)
|
|
310
|
+
self._built = True
|
|
311
|
+
|
|
312
|
+
def _little_build(self,
|
|
313
|
+
plugin_id: PluginIdentifier) -> tuple[Path, Plugin]:
|
|
314
|
+
"""
|
|
315
|
+
In the Maven implementation, essentially a no-op (keeping for parallel
|
|
316
|
+
structure with the legacy Ant plugin set builder).
|
|
317
|
+
|
|
318
|
+
:param plugin_id: A plugin identifier.
|
|
319
|
+
:type plugin_id: PluginIdentifier
|
|
320
|
+
:return: A tuple of the built and signed JAR file and a Plugin object
|
|
321
|
+
instantiated from it.
|
|
322
|
+
:rtype: tuple[Path, Plugin]
|
|
323
|
+
:raises FileNotFoundError: If the expected built and signed plugin JAR
|
|
324
|
+
path is unexpectedly not found despite the
|
|
325
|
+
build.
|
|
326
|
+
"""
|
|
327
|
+
jar_path = self.get_root().joinpath('target', 'pluginjars', f'{plugin_id}.jar')
|
|
328
|
+
if not jar_path.is_file():
|
|
329
|
+
raise FileNotFoundError(str(jar_path))
|
|
330
|
+
return jar_path, Plugin.from_jar(jar_path)
|
|
331
|
+
|
|
332
|
+
def _sanitize(self,
|
|
333
|
+
called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError:
|
|
334
|
+
"""
|
|
335
|
+
Alters the ``-Dkeystore.password=`` portion of a called process error.
|
|
336
|
+
|
|
337
|
+
:param called_process_error: A called process error.
|
|
338
|
+
:return: The same called process error, with ``-Dkeystore.password=``
|
|
339
|
+
altered in ``cmd``.
|
|
340
|
+
"""
|
|
341
|
+
cmd = called_process_error.cmd[:]
|
|
342
|
+
for i in range(len(cmd)):
|
|
343
|
+
if cmd[i].startswith('-Dkeystore.password='):
|
|
344
|
+
cmd[i] = '-Dkeystore.password=<password>'
|
|
345
|
+
called_process_error.cmd = ' '.join([shlex.quote(c) for c in cmd])
|
|
346
|
+
return called_process_error
|
|
347
|
+
|
|
348
|
+
|
|
200
349
|
class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
350
|
+
"""
|
|
351
|
+
A plugin set builder that uses `Java <https://www.oracle.com/java/>`_
|
|
352
|
+
Development Kit (JDK) 8 and `Apache Ant <https://ant.apache.org/>`_,
|
|
353
|
+
with the legacy LOCKSS 1.x build system.
|
|
354
|
+
"""
|
|
355
|
+
|
|
201
356
|
#: Default value for the ``main`` field.
|
|
202
357
|
DEFAULT_MAIN: ClassVar[str] = 'plugins/src'
|
|
358
|
+
|
|
203
359
|
#: Default value for the ``test`` field.
|
|
204
360
|
DEFAULT_TEST: ClassVar[str] = 'plugins/test/src'
|
|
205
361
|
|
|
206
362
|
#: This plugin set builder's type.
|
|
207
363
|
type: Literal['ant'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
|
|
364
|
+
|
|
208
365
|
#: This plugin set builder's main code path.
|
|
209
|
-
main: Optional[str] = Field(DEFAULT_MAIN,
|
|
366
|
+
main: Optional[str] = Field(DEFAULT_MAIN,
|
|
367
|
+
**BasePluginSetBuilder.MAIN_FIELD)
|
|
368
|
+
|
|
210
369
|
#: This plugin set builder's unit test path.
|
|
211
|
-
test: Optional[str] = Field(DEFAULT_TEST,
|
|
370
|
+
test: Optional[str] = Field(DEFAULT_TEST,
|
|
371
|
+
**BasePluginSetBuilder.TEST_FIELD)
|
|
212
372
|
|
|
213
373
|
#: An internal flag to remember if a build has occurred.
|
|
214
374
|
_built: bool
|
|
215
375
|
|
|
216
|
-
def build_plugin(self,
|
|
376
|
+
def build_plugin(self,
|
|
377
|
+
plugin_id: PluginIdentifier,
|
|
378
|
+
keystore_path: Path,
|
|
379
|
+
keystore_alias: str,
|
|
380
|
+
keystore_password=None) -> tuple[Path, Plugin]:
|
|
381
|
+
"""
|
|
382
|
+
Builds the given plugin with the supplied plugin signing credentials.
|
|
383
|
+
|
|
384
|
+
:param plugin_id: A plugin identifier.
|
|
385
|
+
:type plugin_id: PluginIdentifier
|
|
386
|
+
:param keystore_path: The path to the plugin signing keystore.
|
|
387
|
+
:type keystore_path: Path
|
|
388
|
+
:param keystore_alias: The alias to use in the plugin signing keystore.
|
|
389
|
+
:type keystore_alias: str
|
|
390
|
+
:param keystore_password: The plugin signing password.
|
|
391
|
+
:type keystore_password:
|
|
392
|
+
:return: A tuple of the built and signed JAR file and a Plugin object
|
|
393
|
+
instantiated from it.
|
|
394
|
+
:rtype: tuple[Path, Plugin]
|
|
395
|
+
:raises Exception: If ``JAVA_HOME`` is not set in the environment.
|
|
396
|
+
:raises subprocess.CalledProcessError: If a subprocess fails.
|
|
397
|
+
:raises FileNotFoundError: If the expected built and signed plugin JAR
|
|
398
|
+
path is unexpectedly not found despite the
|
|
399
|
+
build.
|
|
400
|
+
"""
|
|
217
401
|
# Prerequisites
|
|
218
402
|
if 'JAVA_HOME' not in os.environ:
|
|
219
403
|
raise Exception('error: JAVA_HOME must be set in your environment')
|
|
@@ -222,13 +406,22 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
|
222
406
|
# Little build
|
|
223
407
|
return self._little_build(plugin_id, keystore_path, keystore_alias, keystore_password=keystore_password)
|
|
224
408
|
|
|
225
|
-
def model_post_init(self,
|
|
409
|
+
def model_post_init(self,
|
|
410
|
+
context: Any) -> None:
|
|
411
|
+
"""
|
|
412
|
+
Pydantic post-initialization method to initialize the ``_built`` flag.
|
|
413
|
+
|
|
414
|
+
:param context: The Pydantic context.
|
|
415
|
+
:type context: Any
|
|
416
|
+
"""
|
|
226
417
|
super().model_post_init(context)
|
|
227
418
|
self._built = False
|
|
228
419
|
|
|
229
420
|
def _big_build(self) -> None:
|
|
230
421
|
"""
|
|
231
|
-
|
|
422
|
+
Runs ``ant load-plugins`` if the ``_built`` flag is False.
|
|
423
|
+
|
|
424
|
+
:raises subprocess.CalledProcessError: If a subprocess fails.
|
|
232
425
|
"""
|
|
233
426
|
if not self._built:
|
|
234
427
|
# Do build
|
|
@@ -236,7 +429,11 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
|
236
429
|
shell=True, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
237
430
|
self._built = True
|
|
238
431
|
|
|
239
|
-
def _little_build(self,
|
|
432
|
+
def _little_build(self,
|
|
433
|
+
plugin_id: PluginIdentifier,
|
|
434
|
+
keystore_path: Path,
|
|
435
|
+
keystore_alias: str,
|
|
436
|
+
keystore_password: str=None) -> tuple[Path, Plugin]:
|
|
240
437
|
"""
|
|
241
438
|
Performs the "little build" of the given plugin.
|
|
242
439
|
|
|
@@ -244,14 +441,17 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
|
244
441
|
:type plugin_id: PluginIdentifier
|
|
245
442
|
:param keystore_path: The path to the plugin signing keystore.
|
|
246
443
|
:type keystore_path: Path
|
|
247
|
-
:param keystore_alias: The
|
|
248
|
-
keystore.
|
|
444
|
+
:param keystore_alias: The alias to use in the plugin signing keystore.
|
|
249
445
|
:type keystore_alias: str
|
|
250
|
-
:param keystore_password: The signing password.
|
|
251
|
-
:type keystore_password:
|
|
252
|
-
:return: A tuple of the
|
|
253
|
-
|
|
446
|
+
:param keystore_password: The plugin signing password.
|
|
447
|
+
:type keystore_password:
|
|
448
|
+
:return: A tuple of the built and signed JAR file and a Plugin object
|
|
449
|
+
instantiated from it.
|
|
254
450
|
:rtype: tuple[Path, Plugin]
|
|
451
|
+
:raises subprocess.CalledProcessError: If a subprocess fails.
|
|
452
|
+
:raises FileNotFoundError: If the expected built and signed plugin JAR
|
|
453
|
+
path is unexpectedly not found despite the
|
|
454
|
+
build.
|
|
255
455
|
"""
|
|
256
456
|
orig_plugin = None
|
|
257
457
|
cur_id = plugin_id
|
|
@@ -296,7 +496,15 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
|
296
496
|
# def _plugin_path(self, plugin_id: PluginIdentifier) -> Path:
|
|
297
497
|
# return self.get_main().joinpath(Plugin.id_to_file(plugin_id))
|
|
298
498
|
|
|
299
|
-
def _sanitize(self,
|
|
499
|
+
def _sanitize(self,
|
|
500
|
+
called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError:
|
|
501
|
+
"""
|
|
502
|
+
Alters the value of ``--password`` in a called process error.
|
|
503
|
+
|
|
504
|
+
:param called_process_error: A called process error.
|
|
505
|
+
:return: The same called process error, with the value of ``--password``
|
|
506
|
+
altered in ``cmd``.
|
|
507
|
+
"""
|
|
300
508
|
cmd = called_process_error.cmd[:]
|
|
301
509
|
for i in range(1, len(cmd)):
|
|
302
510
|
if cmd[i - 1] == '--password':
|
|
@@ -305,85 +513,123 @@ class AntPluginSetBuilder(BasePluginSetBuilder):
|
|
|
305
513
|
return called_process_error
|
|
306
514
|
|
|
307
515
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
type: Literal['maven'] = Field(**BasePluginSetBuilder.TYPE_FIELD)
|
|
313
|
-
main: Optional[str] = Field(DEFAULT_MAIN, **BasePluginSetBuilder.MAIN_FIELD)
|
|
314
|
-
test: Optional[str] = Field(DEFAULT_TEST, **BasePluginSetBuilder.TEST_FIELD)
|
|
315
|
-
|
|
316
|
-
_built: bool
|
|
317
|
-
|
|
318
|
-
def build_plugin(self, plugin_id: PluginIdentifier, keystore_path: Path, keystore_alias: str, keystore_password=None) -> tuple[Path, Plugin]:
|
|
319
|
-
self._big_build(keystore_path, keystore_alias, keystore_password=keystore_password)
|
|
320
|
-
return self._little_build(plugin_id)
|
|
321
|
-
|
|
322
|
-
def model_post_init(self, context: Any) -> None:
|
|
323
|
-
super().model_post_init(context)
|
|
324
|
-
self._built = False
|
|
325
|
-
|
|
326
|
-
def _big_build(self, keystore_path: Path, keystore_alias: str, keystore_password: str=None) -> None:
|
|
327
|
-
if not self._built:
|
|
328
|
-
# Do build
|
|
329
|
-
cmd = ['mvn', 'package',
|
|
330
|
-
f'-Dkeystore.file={keystore_path!s}',
|
|
331
|
-
f'-Dkeystore.alias={keystore_alias}',
|
|
332
|
-
f'-Dkeystore.password={keystore_password}']
|
|
333
|
-
try:
|
|
334
|
-
subprocess.run(cmd, cwd=self.get_root(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
335
|
-
except subprocess.CalledProcessError as cpe:
|
|
336
|
-
raise self._sanitize(cpe)
|
|
337
|
-
self._built = True
|
|
338
|
-
|
|
339
|
-
def _little_build(self, plugin_id: PluginIdentifier) -> tuple[Path, Plugin]:
|
|
340
|
-
jar_path = self.get_root().joinpath('target', 'pluginjars', f'{plugin_id}.jar')
|
|
341
|
-
if not jar_path.is_file():
|
|
342
|
-
raise FileNotFoundError(str(jar_path))
|
|
343
|
-
return jar_path, Plugin.from_jar(jar_path)
|
|
344
|
-
|
|
345
|
-
def _sanitize(self, called_process_error: subprocess.CalledProcessError) -> subprocess.CalledProcessError:
|
|
346
|
-
cmd = called_process_error.cmd[:]
|
|
347
|
-
for i in range(len(cmd)):
|
|
348
|
-
if cmd[i].startswith('-Dkeystore.password='):
|
|
349
|
-
cmd[i] = '-Dkeystore.password=<password>'
|
|
350
|
-
called_process_error.cmd = ' '.join([shlex.quote(c) for c in cmd])
|
|
351
|
-
return called_process_error
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
PluginSetBuilder = Annotated[Union[AntPluginSetBuilder, MavenPluginSetBuilder], Field(discriminator='type')]
|
|
516
|
+
#: A type alias for plugin set builders, which is the union of
|
|
517
|
+
#: ``MavenPluginSetBuilder`` and ``AntPluginSetBuilder`` using ``type`` as the
|
|
518
|
+
#: discriminator field.
|
|
519
|
+
PluginSetBuilder = Annotated[Union[MavenPluginSetBuilder, AntPluginSetBuilder], Field(discriminator='type')]
|
|
355
520
|
|
|
356
521
|
|
|
522
|
+
#: A type alias for the plugin set kind.
|
|
357
523
|
PluginSetKind = Literal['PluginSet']
|
|
358
524
|
|
|
359
525
|
|
|
526
|
+
#: A type alias for plugin set identifiers.
|
|
360
527
|
PluginSetIdentifier = str
|
|
361
528
|
|
|
362
529
|
|
|
363
530
|
class PluginSet(BaseModel):
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
531
|
+
"""
|
|
532
|
+
A Pydantic model for a plugin set.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
#: This object's kind.
|
|
536
|
+
kind: PluginSetKind = Field(title='Kind',
|
|
537
|
+
description="This object's kind")
|
|
538
|
+
|
|
539
|
+
#: This plugin set's identifier.
|
|
540
|
+
id: PluginSetIdentifier = Field(title='Plugin Set Identifier',
|
|
541
|
+
description='An identifier for the plugin set')
|
|
542
|
+
|
|
543
|
+
#: This plugin set's name.
|
|
544
|
+
name: str = Field(title='Plugin Set Name',
|
|
545
|
+
description='A name for the plugin set')
|
|
546
|
+
|
|
547
|
+
#: This plugin set's builder.
|
|
548
|
+
builder: PluginSetBuilder = Field(title='Plugin Set Builder',
|
|
549
|
+
description='A builder for the plugin set')
|
|
550
|
+
|
|
551
|
+
def build_plugin(self,
|
|
552
|
+
plugin_id: PluginIdentifier,
|
|
553
|
+
keystore_path: Path,
|
|
554
|
+
keystore_alias: str,
|
|
555
|
+
keystore_password=None) -> tuple[Path, Plugin]:
|
|
556
|
+
"""
|
|
557
|
+
Builds the given plugin with the supplied plugin signing credentials.
|
|
368
558
|
|
|
369
|
-
|
|
559
|
+
:param plugin_id: A plugin identifier.
|
|
560
|
+
:type plugin_id: PluginIdentifier
|
|
561
|
+
:param keystore_path: The path to the plugin signing keystore.
|
|
562
|
+
:type keystore_path: Path
|
|
563
|
+
:param keystore_alias: The alias to use in the plugin signing keystore.
|
|
564
|
+
:type keystore_alias: str
|
|
565
|
+
:param keystore_password: The plugin signing password.
|
|
566
|
+
:type keystore_password:
|
|
567
|
+
:return: A tuple of the built and signed JAR file and a Plugin object
|
|
568
|
+
instantiated from it.
|
|
569
|
+
"""
|
|
370
570
|
return self.builder.build_plugin(plugin_id, keystore_path, keystore_alias, keystore_password)
|
|
371
571
|
|
|
372
572
|
def get_builder(self) -> PluginSetBuilder:
|
|
573
|
+
"""
|
|
574
|
+
Returns this plugin set's builder.
|
|
575
|
+
|
|
576
|
+
:return: This plugin set's builder.
|
|
577
|
+
:rtype: PluginSetBuilder
|
|
578
|
+
"""
|
|
373
579
|
return self.builder
|
|
374
580
|
|
|
375
581
|
def get_id(self) -> PluginSetIdentifier:
|
|
582
|
+
"""
|
|
583
|
+
Returns this plugin set's identifier.
|
|
584
|
+
|
|
585
|
+
:return: This plugin set's identifier.
|
|
586
|
+
:rtype: PluginSetIdentifier
|
|
587
|
+
"""
|
|
376
588
|
return self.id
|
|
377
589
|
|
|
378
590
|
def get_name(self) -> str:
|
|
591
|
+
"""
|
|
592
|
+
Returns this plugin set's name.
|
|
593
|
+
|
|
594
|
+
:return: This plugin set's name.
|
|
595
|
+
:rtype: str
|
|
596
|
+
"""
|
|
379
597
|
return self.name
|
|
380
598
|
|
|
381
|
-
def has_plugin(self,
|
|
599
|
+
def has_plugin(self,
|
|
600
|
+
plugin_id: PluginIdentifier) -> bool:
|
|
601
|
+
"""
|
|
602
|
+
Determines if the given plugin identifier represents a plugin that is
|
|
603
|
+
present in the plugin set.
|
|
604
|
+
|
|
605
|
+
:param plugin_id: A plugin identifier.
|
|
606
|
+
:type plugin_id: PluginIdentifier
|
|
607
|
+
:return: Whether the plugin is present in the plugin set.
|
|
608
|
+
:rtype: bool
|
|
609
|
+
"""
|
|
382
610
|
return self.get_builder().has_plugin(plugin_id)
|
|
383
611
|
|
|
384
|
-
def initialize(self,
|
|
612
|
+
def initialize(self,
|
|
613
|
+
root: Path) -> PluginSet:
|
|
614
|
+
"""
|
|
615
|
+
Mandatory initialization of the builder.
|
|
616
|
+
|
|
617
|
+
:param root: This plugin set's root path.
|
|
618
|
+
:type root: Path
|
|
619
|
+
:return: This plugin set, for chaining.
|
|
620
|
+
:rtype: PluginSet
|
|
621
|
+
"""
|
|
385
622
|
self.get_builder().initialize(root)
|
|
386
623
|
return self
|
|
387
624
|
|
|
388
|
-
def make_plugin(self,
|
|
625
|
+
def make_plugin(self,
|
|
626
|
+
plugin_id: PluginIdentifier) -> Plugin:
|
|
627
|
+
"""
|
|
628
|
+
Makes a ``Plugin`` object from the given plugin identifier.
|
|
629
|
+
|
|
630
|
+
:param plugin_id: A plugin identifier.
|
|
631
|
+
:type plugin_id: PluginIdentifier
|
|
632
|
+
:return: The corresponding ``Plugin`` object.
|
|
633
|
+
:rtype: Plugin
|
|
634
|
+
"""
|
|
389
635
|
return self.get_builder().make_plugin(plugin_id)
|
lockss/turtles/util.py
CHANGED
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
29
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
30
|
|
|
31
|
+
"""
|
|
32
|
+
Utilities for the ``lockss.turtles`` package.
|
|
33
|
+
"""
|
|
34
|
+
|
|
31
35
|
# Remove in Python 3.14
|
|
32
36
|
# See https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514
|
|
33
37
|
from __future__ import annotations
|
|
@@ -40,25 +44,62 @@ from lockss.pybasic.fileutil import path
|
|
|
40
44
|
from pydantic import BaseModel
|
|
41
45
|
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
#: Type alias for the union of ``Path`` and ``str``.
|
|
44
48
|
PathOrStr = Union[Path, str]
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
class BaseModelWithRoot(BaseModel):
|
|
52
|
+
"""
|
|
53
|
+
A Pydantic model with a root path which can be used to resolve relative
|
|
54
|
+
paths.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
#: An internal root path.
|
|
48
58
|
_root: Optional[Path]
|
|
49
59
|
|
|
50
60
|
def model_post_init(self, context: Any) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Pydantic post-initialization method to create the ``_root`` field.
|
|
63
|
+
"""
|
|
51
64
|
self._root = None
|
|
52
65
|
|
|
53
66
|
def get_root(self) -> Path:
|
|
67
|
+
"""
|
|
68
|
+
Returns this object's root path.
|
|
69
|
+
|
|
70
|
+
See ``initialize``.
|
|
71
|
+
|
|
72
|
+
:return: This object's root path.
|
|
73
|
+
:rtype: Path
|
|
74
|
+
:raises ValueError: If this object's ``initialize`` method was not
|
|
75
|
+
called.
|
|
76
|
+
"""
|
|
54
77
|
if self._root is None:
|
|
55
78
|
raise ValueError('Uninitialized root')
|
|
56
79
|
return self._root
|
|
57
80
|
|
|
58
|
-
def initialize(self,
|
|
59
|
-
|
|
81
|
+
def initialize(self,
|
|
82
|
+
root_path_or_str: PathOrStr) -> BaseModelWithRoot:
|
|
83
|
+
"""
|
|
84
|
+
Mandatory initialization of the root path.
|
|
85
|
+
|
|
86
|
+
:param root_path_or_str: This object's root path.
|
|
87
|
+
:type root_path_or_str: PathOrStr
|
|
88
|
+
:return: This object, for chaining.
|
|
89
|
+
:rtype: BaseModelWithRoot
|
|
90
|
+
"""
|
|
91
|
+
self._root = path(root_path_or_str)
|
|
60
92
|
return self
|
|
61
93
|
|
|
62
94
|
|
|
63
95
|
def file_or(paths: Iterable[Path]) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Turns an iterable of file paths and returns them as a ``" or "``-separated
|
|
98
|
+
string.
|
|
99
|
+
|
|
100
|
+
:param paths: A non-null list of file paths.
|
|
101
|
+
:type paths: Iterable[Path]
|
|
102
|
+
:return: A ``" or "``-separated string of the given file paths.
|
|
103
|
+
:rtype: str
|
|
104
|
+
"""
|
|
64
105
|
return ' or '.join(map(str, paths))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: lockss-turtles
|
|
3
|
-
Version: 0.6.0.
|
|
3
|
+
Version: 0.6.0.dev22
|
|
4
4
|
Summary: Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Author: Thib Guicherd-Callin
|
|
@@ -34,7 +34,7 @@ Description-Content-Type: text/x-rst
|
|
|
34
34
|
Turtles
|
|
35
35
|
=======
|
|
36
36
|
|
|
37
|
-
.. |RELEASE| replace:: 0.6.0-
|
|
37
|
+
.. |RELEASE| replace:: 0.6.0-dev22
|
|
38
38
|
.. |RELEASE_DATE| replace:: ?
|
|
39
39
|
|
|
40
40
|
.. |HELP| replace:: ``--help/-h``
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
lockss/turtles/__init__.py,sha256=v-2mPurnlbDsfTo_37mD7fnqclC3pS8DScu46ZawpcQ,1827
|
|
2
|
+
lockss/turtles/__main__.py,sha256=BqRTG3dejCgLh87UJ1Os9DD9unxnQkOdUQM6eqWS3Sg,1680
|
|
3
|
+
lockss/turtles/app.py,sha256=Rxbsq10A830D1r99a5tqLVYWM3bJZ2Org-JKjXD-2Mk,16689
|
|
4
|
+
lockss/turtles/cli.py,sha256=Pgd0GSThxDfnPWKwwujo6ZocfKePoe-aiHJ9qYpm9BQ,21019
|
|
5
|
+
lockss/turtles/plugin.py,sha256=HT01Er9-odvJ4lF8DNC7TV4uxSqTw44UBe02Jgj5xMs,11065
|
|
6
|
+
lockss/turtles/plugin_registry.py,sha256=UlB9faNH8fEh9mfdj6byMcRcZ7axi0DzUP2qdyj9ANk,26935
|
|
7
|
+
lockss/turtles/plugin_set.py,sha256=PuzSWl2k7ikbkKtxStQRJhcXi1JXuSBl9jXz4dzp7Ts,24550
|
|
8
|
+
lockss/turtles/util.py,sha256=kETdjOSDaECs2xSm6ah8UoPVuoo42eFUJEEJn5wqYXE,3647
|
|
9
|
+
unittest/lockss/turtles/__init__.py,sha256=hrgWx4GaP-hhPBuRlbLndJnOQmZicKCLaP-dQTCu1sQ,3162
|
|
10
|
+
unittest/lockss/turtles/test_plugin_set.py,sha256=q6JmooDR-wijAQ8x9DWKTNB6Of4EOr3Dgq8oaX0LTpQ,4179
|
|
11
|
+
lockss_turtles-0.6.0.dev22.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
|
|
12
|
+
lockss_turtles-0.6.0.dev22.dist-info/METADATA,sha256=jgCvdNmmsDAzEDTGldk4MfLGRlq8tHfhvh-Bpilr4JQ,39692
|
|
13
|
+
lockss_turtles-0.6.0.dev22.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
14
|
+
lockss_turtles-0.6.0.dev22.dist-info/entry_points.txt,sha256=25BAVFSBRKWAWiXIGZgcr1ypt2mV7nj31Jl8WcNZZOk,51
|
|
15
|
+
lockss_turtles-0.6.0.dev22.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
lockss/turtles/__init__.py,sha256=9arhPOwCemb5EemdFE4gisbpLEE6bPqOBGi1yOXJmIA,1827
|
|
2
|
-
lockss/turtles/__main__.py,sha256=BqRTG3dejCgLh87UJ1Os9DD9unxnQkOdUQM6eqWS3Sg,1680
|
|
3
|
-
lockss/turtles/app.py,sha256=Rxbsq10A830D1r99a5tqLVYWM3bJZ2Org-JKjXD-2Mk,16689
|
|
4
|
-
lockss/turtles/cli.py,sha256=ZijrjwW1VXtmRvTDpfLnLvq9aL0CfK-F6mglk04RihI,20887
|
|
5
|
-
lockss/turtles/plugin.py,sha256=HT01Er9-odvJ4lF8DNC7TV4uxSqTw44UBe02Jgj5xMs,11065
|
|
6
|
-
lockss/turtles/plugin_registry.py,sha256=ZGlVnhP831hmqLQ9cJueUAXlZWQ67rm0cs6U91F7STc,11943
|
|
7
|
-
lockss/turtles/plugin_set.py,sha256=Q-d-UfZM8I6f-z4TeYe8jdBbhPv0r4ro2H24oNd8Syc,15898
|
|
8
|
-
lockss/turtles/util.py,sha256=8EyWvzTLNycQM_ZKCtWm8FpKJSrwaCSWh8006UYq6vU,2498
|
|
9
|
-
unittest/lockss/turtles/__init__.py,sha256=hrgWx4GaP-hhPBuRlbLndJnOQmZicKCLaP-dQTCu1sQ,3162
|
|
10
|
-
unittest/lockss/turtles/test_plugin_set.py,sha256=q6JmooDR-wijAQ8x9DWKTNB6Of4EOr3Dgq8oaX0LTpQ,4179
|
|
11
|
-
lockss_turtles-0.6.0.dev21.dist-info/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
|
|
12
|
-
lockss_turtles-0.6.0.dev21.dist-info/METADATA,sha256=2HRA5jwOYJaTyU4wXqaIBzQg4aKlxdjTdl9lqL3aT9w,39692
|
|
13
|
-
lockss_turtles-0.6.0.dev21.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
14
|
-
lockss_turtles-0.6.0.dev21.dist-info/entry_points.txt,sha256=25BAVFSBRKWAWiXIGZgcr1ypt2mV7nj31Jl8WcNZZOk,51
|
|
15
|
-
lockss_turtles-0.6.0.dev21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|