csspin 2.0.0__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.
- csspin/__init__.py +1340 -0
- csspin/__main__.py +20 -0
- csspin/builtin/__init__.py +322 -0
- csspin/cli.py +711 -0
- csspin/schema.py +204 -0
- csspin/schema.yaml +134 -0
- csspin/tree.py +764 -0
- csspin-2.0.0.dist-info/METADATA +134 -0
- csspin-2.0.0.dist-info/RECORD +13 -0
- csspin-2.0.0.dist-info/WHEEL +5 -0
- csspin-2.0.0.dist-info/entry_points.txt +2 -0
- csspin-2.0.0.dist-info/licenses/LICENSE +176 -0
- csspin-2.0.0.dist-info/top_level.txt +1 -0
csspin/__main__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- mode: python; coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 CONTACT Software GmbH
|
|
4
|
+
# https://www.contact-software.com/
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
from csspin import _main
|
|
19
|
+
|
|
20
|
+
_main()
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# -*- mode: python; coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2020 CONTACT Software GmbH
|
|
4
|
+
# https://www.contact-software.com/
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
"""Plugins that come with spin. These don't have to be installed
|
|
19
|
+
through a plugin package and are always available.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
import distro
|
|
26
|
+
|
|
27
|
+
from csspin import (
|
|
28
|
+
argument,
|
|
29
|
+
confirm,
|
|
30
|
+
die,
|
|
31
|
+
option,
|
|
32
|
+
parse_version,
|
|
33
|
+
rmtree,
|
|
34
|
+
run_script,
|
|
35
|
+
run_spin,
|
|
36
|
+
sh,
|
|
37
|
+
task,
|
|
38
|
+
toporun,
|
|
39
|
+
warn,
|
|
40
|
+
)
|
|
41
|
+
from csspin.cli import (
|
|
42
|
+
commands,
|
|
43
|
+
finalize_cfg_tree,
|
|
44
|
+
install_plugin_packages,
|
|
45
|
+
load_plugins_into_tree,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@task("run", add_help_option=False)
|
|
50
|
+
def exec_shell(ctx: click.Context, args: list[str]) -> None:
|
|
51
|
+
"""Run a shell command in the project context."""
|
|
52
|
+
if not args:
|
|
53
|
+
die("Use of run is not possible without arguments.")
|
|
54
|
+
if "--help" == args[0]:
|
|
55
|
+
subcommand_obj = commands.get_command(ctx, "run") # type: ignore[attr-defined]
|
|
56
|
+
click.echo(subcommand_obj.get_help(ctx))
|
|
57
|
+
else:
|
|
58
|
+
sh(*args)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def pretty_descriptor(parent: str, name: str, descriptor, rst: bool) -> str: # type: ignore[no-untyped-def]
|
|
62
|
+
types = getattr(descriptor, "type", ["any"])
|
|
63
|
+
default = getattr(descriptor, "default", None)
|
|
64
|
+
if name:
|
|
65
|
+
if parent:
|
|
66
|
+
name = f"{parent}.{name}"
|
|
67
|
+
if rst:
|
|
68
|
+
joined_types = " ".join(types)
|
|
69
|
+
decl = f".. py:data:: {name}\n :type: '{joined_types}'\n"
|
|
70
|
+
if default:
|
|
71
|
+
decl += f" :value: '{default}'\n"
|
|
72
|
+
if hasattr(descriptor, "noindex") or "object" in types:
|
|
73
|
+
decl += " :noindex:\n"
|
|
74
|
+
else:
|
|
75
|
+
joined_types = ", ".join(types)
|
|
76
|
+
decl = f"{name}: [{joined_types}]"
|
|
77
|
+
if default:
|
|
78
|
+
decl += f" = '{default}'"
|
|
79
|
+
helptext = getattr(descriptor, "help", "")
|
|
80
|
+
if not helptext.endswith("\n"):
|
|
81
|
+
helptext += "\n"
|
|
82
|
+
decl += f"\n{helptext}\n"
|
|
83
|
+
else:
|
|
84
|
+
decl = "================\nSchema Reference\n================\n\n"
|
|
85
|
+
return decl
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@task()
|
|
89
|
+
def schemadoc( # type: ignore[no-untyped-def]
|
|
90
|
+
cfg,
|
|
91
|
+
outfile: option( # type: ignore[valid-type]
|
|
92
|
+
"-o",
|
|
93
|
+
"outfile",
|
|
94
|
+
default="-", # noqa: F722
|
|
95
|
+
type=click.File("w"),
|
|
96
|
+
help="Write output into FILENAME.", # noqa: F722
|
|
97
|
+
),
|
|
98
|
+
full: option( # type: ignore[valid-type]
|
|
99
|
+
"--full",
|
|
100
|
+
default=True,
|
|
101
|
+
type=click.BOOL,
|
|
102
|
+
help="Show schema documentation for the whole ConfigTree.", # noqa: F722
|
|
103
|
+
),
|
|
104
|
+
rst: option( # type: ignore[valid-type]
|
|
105
|
+
"--rst",
|
|
106
|
+
is_flag=True,
|
|
107
|
+
default=False,
|
|
108
|
+
help="Print the schema documentation in rst format.", # noqa: F722
|
|
109
|
+
),
|
|
110
|
+
select: argument( # type: ignore[valid-type]
|
|
111
|
+
type=click.STRING,
|
|
112
|
+
default="", # noqa: F722
|
|
113
|
+
callback=lambda ctx, param, value: value.split(".") if value else "",
|
|
114
|
+
),
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Print the schema definitions for spin."""
|
|
117
|
+
|
|
118
|
+
def do_docwrite(parent: str, name: str, desc, ignore=tuple()): # type: ignore[no-untyped-def]
|
|
119
|
+
fullname = f"{parent}.{name}" if parent else name
|
|
120
|
+
if fullname in ignore:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
outfile.write(pretty_descriptor(parent, name, desc, rst))
|
|
124
|
+
properties = getattr(desc, "properties", {})
|
|
125
|
+
for prop, descr in properties.items():
|
|
126
|
+
do_docwrite(fullname, prop, descr, ignore)
|
|
127
|
+
|
|
128
|
+
schema = cfg.schema
|
|
129
|
+
|
|
130
|
+
ignore = []
|
|
131
|
+
if not full:
|
|
132
|
+
for import_spec in cfg.loaded:
|
|
133
|
+
if "csspin." in import_spec:
|
|
134
|
+
continue
|
|
135
|
+
import_spec = tuple(import_spec.split("."))
|
|
136
|
+
plugin_name = import_spec[-1]
|
|
137
|
+
ignore.append(plugin_name)
|
|
138
|
+
|
|
139
|
+
arg = ""
|
|
140
|
+
for arg in select:
|
|
141
|
+
schema = schema.properties.get(arg)
|
|
142
|
+
parent = "" if len(select) < 2 else ".".join(select[:-1])
|
|
143
|
+
do_docwrite(parent, arg, schema, ignore)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TaskDefinition:
|
|
147
|
+
def __init__(self, definition: dict) -> None:
|
|
148
|
+
self._definition = definition
|
|
149
|
+
|
|
150
|
+
def __call__(self) -> None:
|
|
151
|
+
env = self._definition.get("env", None)
|
|
152
|
+
run_spin(self._definition.get("spin", []))
|
|
153
|
+
run_script(self._definition.get("script", []), env)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def configure(cfg) -> None: # type: ignore[no-untyped-def]
|
|
157
|
+
"""
|
|
158
|
+
Grab explicitly defined tasks from the configuration tree and add them as
|
|
159
|
+
subcommands.
|
|
160
|
+
"""
|
|
161
|
+
for clause_name in ("extra_tasks", "tasks"):
|
|
162
|
+
for task_name, task_definition in cfg.get(clause_name, {}).items():
|
|
163
|
+
task(task_name, help=task_definition.get("help", ""))(
|
|
164
|
+
TaskDefinition(task_definition)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def merge_dicts(a: dict, b: dict) -> None:
|
|
169
|
+
for k, v in b.items():
|
|
170
|
+
if k in a:
|
|
171
|
+
# We support lists and strings in system_requirements;
|
|
172
|
+
# this is not very robust, though.
|
|
173
|
+
if isinstance(v, list):
|
|
174
|
+
a[k].extend(v)
|
|
175
|
+
else:
|
|
176
|
+
a[k] = " ".join((a[k], v))
|
|
177
|
+
else:
|
|
178
|
+
a[k] = v
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_distro() -> dict:
|
|
182
|
+
dinfo = distro.info()
|
|
183
|
+
if sys.platform == "win32":
|
|
184
|
+
dinfo["id"] = "windows"
|
|
185
|
+
winver = sys.getwindowsversion()
|
|
186
|
+
dinfo["version"] = f"{winver.major}.{winver.minor}.{winver.build}"
|
|
187
|
+
return dinfo # type: ignore[no-any-return]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@task("system-provision", noenv=True)
|
|
191
|
+
def do_system_provisioning( # type: ignore[no-untyped-def]
|
|
192
|
+
cfg,
|
|
193
|
+
distroargs: argument(nargs=-1), # type: ignore[valid-type]
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Provision system dependencies for the host.
|
|
196
|
+
|
|
197
|
+
Usage:
|
|
198
|
+
spin system-provision [<distro> [<version>]]
|
|
199
|
+
|
|
200
|
+
This will output a script on stdout, that uses OS package managers like apt,
|
|
201
|
+
yum etc. to install system-level dependencies for the project. The output
|
|
202
|
+
can for example be piped into a sudo shell.
|
|
203
|
+
"""
|
|
204
|
+
# Install the plugins and build the full config tree
|
|
205
|
+
install_plugin_packages(cfg)
|
|
206
|
+
load_plugins_into_tree(cfg)
|
|
207
|
+
finalize_cfg_tree(cfg)
|
|
208
|
+
|
|
209
|
+
if distroargs:
|
|
210
|
+
distroname = distroargs[0]
|
|
211
|
+
else:
|
|
212
|
+
dinfo = get_distro()
|
|
213
|
+
distroname = dinfo["id"]
|
|
214
|
+
|
|
215
|
+
# Check system requirements of individual plugins
|
|
216
|
+
out: dict = {}
|
|
217
|
+
supported = True
|
|
218
|
+
for pi in cfg.spin.topo_plugins:
|
|
219
|
+
defaults = cfg.loaded[pi].defaults
|
|
220
|
+
if defaults.get("requires") and defaults.requires.get("system"):
|
|
221
|
+
system_requirements = defaults.requires.system
|
|
222
|
+
if distroname not in system_requirements.keys():
|
|
223
|
+
warn(
|
|
224
|
+
f"The '{pi}' plugin does not officially support"
|
|
225
|
+
f" {distroname}. You can see which packages to"
|
|
226
|
+
" manually install by running 'spin system-provision debian'"
|
|
227
|
+
)
|
|
228
|
+
supported = False
|
|
229
|
+
else:
|
|
230
|
+
merge_dicts(out, system_requirements.get(distroname, []))
|
|
231
|
+
|
|
232
|
+
# Check system requirements defined within the configuration tree, usually
|
|
233
|
+
# defined the projects' spinfile.yaml.
|
|
234
|
+
if cfg.system_requirements.keys():
|
|
235
|
+
if distroname not in cfg.system_requirements.keys():
|
|
236
|
+
warn(
|
|
237
|
+
"This project does not officially support"
|
|
238
|
+
f" {distroname}. You can see which packages to manually"
|
|
239
|
+
" install by running 'spin system-provision debian'"
|
|
240
|
+
)
|
|
241
|
+
supported = False
|
|
242
|
+
else:
|
|
243
|
+
merge_dicts(out, cfg.system_requirements.get(distroname, []))
|
|
244
|
+
|
|
245
|
+
for line in out.get("before", []):
|
|
246
|
+
print(line)
|
|
247
|
+
|
|
248
|
+
for syscmd in ("apt", "choco"):
|
|
249
|
+
if (package_list := out.get(syscmd, [])) and supported:
|
|
250
|
+
print(f"{syscmd} install -y {' '.join(package_list)}")
|
|
251
|
+
|
|
252
|
+
for line in out.get("after", []):
|
|
253
|
+
print(line)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@task("distro", noenv=True)
|
|
257
|
+
def distro_task(cfg) -> None: # type: ignore[no-untyped-def]
|
|
258
|
+
"""Print the distro information."""
|
|
259
|
+
dinfo = get_distro()
|
|
260
|
+
print(f"distro={repr(dinfo['id'])} version={parse_version(dinfo['version'])}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@task("provision", noenv=True)
|
|
264
|
+
def provision(cfg) -> None: # type: ignore[no-untyped-def]
|
|
265
|
+
"""
|
|
266
|
+
Create or update a development environment.
|
|
267
|
+
"""
|
|
268
|
+
# Install the plugins and build the full config tree
|
|
269
|
+
install_plugin_packages(cfg)
|
|
270
|
+
load_plugins_into_tree(cfg)
|
|
271
|
+
finalize_cfg_tree(cfg)
|
|
272
|
+
|
|
273
|
+
toporun(cfg, "provision")
|
|
274
|
+
toporun(cfg, "finalize_provision")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@task(noenv=True)
|
|
278
|
+
def cleanup( # type: ignore[no-untyped-def]
|
|
279
|
+
cfg,
|
|
280
|
+
purge: option( # type: ignore[valid-type]
|
|
281
|
+
"--purge",
|
|
282
|
+
is_flag=True,
|
|
283
|
+
help="Removes spin plugin data.", # noqa: F722
|
|
284
|
+
),
|
|
285
|
+
skip_confirmation: option( # type: ignore[valid-type]
|
|
286
|
+
"-y",
|
|
287
|
+
"--yes",
|
|
288
|
+
"skip_confirmation",
|
|
289
|
+
is_flag=True,
|
|
290
|
+
help="Skip confirmation when using --purge.", # noqa: F722
|
|
291
|
+
),
|
|
292
|
+
) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Clean up project-local resources that have been provisioned by spin, e.g.
|
|
295
|
+
virtual environments and {project_root}/.spin. Also deletes {spin.data}
|
|
296
|
+
if --purge is passed.
|
|
297
|
+
"""
|
|
298
|
+
if (
|
|
299
|
+
purge
|
|
300
|
+
and not skip_confirmation
|
|
301
|
+
and not (
|
|
302
|
+
confirm(
|
|
303
|
+
f"You are about to delete all plugin's data in {cfg.spin.data}."
|
|
304
|
+
" Continue?"
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
):
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
# Load the plugins as far as they are available.
|
|
311
|
+
load_plugins_into_tree(cfg, cleanup=True)
|
|
312
|
+
|
|
313
|
+
# Do not configure and sanitize in case of cleanup, since plugin
|
|
314
|
+
# packages may not be installed, causing AttributeErrors in case of
|
|
315
|
+
# accessing not-initialized plugins via "cfg." as well as failures due
|
|
316
|
+
# to interpolation against property tree keys that does not exist.
|
|
317
|
+
|
|
318
|
+
toporun(cfg, "cleanup", reverse=True)
|
|
319
|
+
rmtree(cfg.spin.spin_dir / "plugins")
|
|
320
|
+
|
|
321
|
+
if purge:
|
|
322
|
+
rmtree(cfg.spin.data)
|