torizon-templates-utils 0.0.1__tar.gz → 0.0.3__tar.gz
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.
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/LICENSE +1 -1
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/PKG-INFO +6 -2
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/pyproject.toml +7 -1
- torizon_templates_utils-0.0.3/torizon_templates_utils/animations.py +62 -0
- torizon_templates_utils-0.0.3/torizon_templates_utils/args.py +83 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/colors.py +7 -0
- torizon_templates_utils-0.0.3/torizon_templates_utils/debug.py +22 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/errors.py +18 -2
- torizon_templates_utils-0.0.3/torizon_templates_utils/network.py +17 -0
- torizon_templates_utils-0.0.3/torizon_templates_utils/tasks.py +927 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils.egg-info/PKG-INFO +6 -2
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils.egg-info/SOURCES.txt +6 -0
- torizon_templates_utils-0.0.3/torizon_templates_utils.egg-info/requires.txt +4 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/README.md +0 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/setup.cfg +0 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/__init__.py +0 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils.egg-info/dependency_links.txt +0 -0
- {torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: torizon_templates_utils
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Package with utilities for Torizon Templates scripts
|
|
5
5
|
Author-email: Matheus Castello <matheus.castello@toradex.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/torizon/vscode-torizon-templates
|
|
@@ -11,6 +11,10 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
11
11
|
Requires-Python: >=3.8
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: torizon-io-api
|
|
15
|
+
Requires-Dist: requests
|
|
16
|
+
Requires-Dist: pyyaml
|
|
17
|
+
Requires-Dist: debugpy
|
|
14
18
|
|
|
15
19
|
# Torizon Templates Utils
|
|
16
20
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "torizon_templates_utils"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Matheus Castello", email="matheus.castello@toradex.com" },
|
|
10
10
|
]
|
|
@@ -16,6 +16,12 @@ classifiers = [
|
|
|
16
16
|
"License :: OSI Approved :: MIT License",
|
|
17
17
|
"Operating System :: POSIX :: Linux",
|
|
18
18
|
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"torizon-io-api",
|
|
21
|
+
"requests",
|
|
22
|
+
"pyyaml",
|
|
23
|
+
"debugpy"
|
|
24
|
+
]
|
|
19
25
|
|
|
20
26
|
[project.urls]
|
|
21
27
|
Homepage = "https://github.com/torizon/vscode-torizon-templates"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
import threading
|
|
4
|
+
from itertools import cycle
|
|
5
|
+
|
|
6
|
+
def run_command_with_wait_animation(call, *args):
|
|
7
|
+
anima_frames = ["🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛"]
|
|
8
|
+
|
|
9
|
+
def animate():
|
|
10
|
+
for frame in cycle(anima_frames):
|
|
11
|
+
if not running[0]:
|
|
12
|
+
break
|
|
13
|
+
sys.stdout.write(f"\r{frame} :: RUNNING PLEASE WAIT :: {frame}")
|
|
14
|
+
sys.stdout.flush()
|
|
15
|
+
time.sleep(0.1)
|
|
16
|
+
|
|
17
|
+
# Clear the line
|
|
18
|
+
sys.stdout.write("\r ")
|
|
19
|
+
|
|
20
|
+
if running[1]:
|
|
21
|
+
sys.stdout.write("\r❌ :: TASK FAILED :: ❌\n")
|
|
22
|
+
else:
|
|
23
|
+
sys.stdout.write("\r✅ :: TASK COMPLETED :: ✅\n")
|
|
24
|
+
|
|
25
|
+
def target():
|
|
26
|
+
nonlocal output
|
|
27
|
+
try:
|
|
28
|
+
output = call(*args)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
output = e
|
|
31
|
+
running[1] = True
|
|
32
|
+
finally:
|
|
33
|
+
running[0] = False
|
|
34
|
+
|
|
35
|
+
# [0] is if it's running [1] if it has failed
|
|
36
|
+
running = [True, False]
|
|
37
|
+
output = None
|
|
38
|
+
|
|
39
|
+
animation_thread = threading.Thread(target=animate)
|
|
40
|
+
command_thread = threading.Thread(target=target)
|
|
41
|
+
|
|
42
|
+
animation_thread.start()
|
|
43
|
+
command_thread.start()
|
|
44
|
+
|
|
45
|
+
command_thread.join()
|
|
46
|
+
animation_thread.join()
|
|
47
|
+
|
|
48
|
+
if isinstance(output, Exception):
|
|
49
|
+
raise output
|
|
50
|
+
|
|
51
|
+
return output
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# # Example usage
|
|
55
|
+
# def example_script(duration):
|
|
56
|
+
# time.sleep(duration)
|
|
57
|
+
# return "Task finished"
|
|
58
|
+
|
|
59
|
+
# if __name__ == "__main__":
|
|
60
|
+
# print("LET'S RUN A SCRIPT THAT TAKES 5 SECONDS TO FINISH")
|
|
61
|
+
# result = run_command_in_background_with_wait_animation(example_script, 5)
|
|
62
|
+
# print(result)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
import sys
|
|
3
|
+
from typing import TypeVar, Type
|
|
4
|
+
from torizon_templates_utils.errors import Error, Error_Out
|
|
5
|
+
|
|
6
|
+
T = TypeVar('T')
|
|
7
|
+
|
|
8
|
+
def get_arg_not_empty(index: int) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Get an argument from the command line.
|
|
11
|
+
If the argument is an empty string, an error is raised.
|
|
12
|
+
"""
|
|
13
|
+
_arg = sys.argv[index]
|
|
14
|
+
|
|
15
|
+
if _arg == "":
|
|
16
|
+
Error_Out(
|
|
17
|
+
"Error: Argument cannot be empty",
|
|
18
|
+
Error.EUSER
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return _arg
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_optional_arg(index: int, default: T) -> T | bool | str:
|
|
25
|
+
"""
|
|
26
|
+
Get an optional argument from the command line.
|
|
27
|
+
If the argument is not provided, the default value is returned.
|
|
28
|
+
"""
|
|
29
|
+
if len(sys.argv) > index:
|
|
30
|
+
# sys.argv return string
|
|
31
|
+
# we need to return T
|
|
32
|
+
# FIXME: this only convert string to bool for now
|
|
33
|
+
if type(default) is bool:
|
|
34
|
+
if sys.argv[index] == "True" or sys.argv[index] == "true" or sys.argv[index] == "1":
|
|
35
|
+
return True
|
|
36
|
+
else:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
return sys.argv[index]
|
|
40
|
+
|
|
41
|
+
return default
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_arg_iterative(
|
|
45
|
+
index: int, prompt: str, default_type: Type, default: T | None = None, iterative: bool = False
|
|
46
|
+
) -> T | None | bool | str:
|
|
47
|
+
"""
|
|
48
|
+
Get an argument from the command line.
|
|
49
|
+
If the argument is not provided, an error is raised.
|
|
50
|
+
"""
|
|
51
|
+
if len(sys.argv) > index:
|
|
52
|
+
if default_type is bool:
|
|
53
|
+
if sys.argv[index] == "True":
|
|
54
|
+
return True
|
|
55
|
+
else:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
return sys.argv[index]
|
|
59
|
+
elif default != None:
|
|
60
|
+
return default
|
|
61
|
+
elif iterative:
|
|
62
|
+
_input = input(prompt)
|
|
63
|
+
if _input == "":
|
|
64
|
+
Error_Out(
|
|
65
|
+
"Error: Argument cannot be empty",
|
|
66
|
+
Error.EUSER
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
if default_type is bool:
|
|
70
|
+
if _input == "True":
|
|
71
|
+
return True
|
|
72
|
+
else:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
return _input
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
Error_Out(
|
|
79
|
+
f"Error: Argument for prompt [{prompt}] not provided",
|
|
80
|
+
Error.EUSER
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return default
|
{torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/colors.py
RENAMED
|
@@ -17,12 +17,19 @@ class BgColor(enum.IntEnum):
|
|
|
17
17
|
NONE = 0
|
|
18
18
|
BLACK = 40
|
|
19
19
|
RED = 41
|
|
20
|
+
BRIGTH_RED = 101
|
|
20
21
|
GREEN = 42
|
|
22
|
+
BRIGTH_GREEN = 102
|
|
21
23
|
YELLOW = 43
|
|
24
|
+
BRIGTH_YELLOW = 103
|
|
22
25
|
BLUE = 44
|
|
26
|
+
BRIGTH_BLUE = 104
|
|
23
27
|
MAGENTA = 45
|
|
28
|
+
BRIGTH_MAGENTA = 105
|
|
24
29
|
CYAN = 46
|
|
30
|
+
BRIGTH_CYAN = 106
|
|
25
31
|
WHITE = 47
|
|
32
|
+
BRIGTH_WHITE = 107
|
|
26
33
|
|
|
27
34
|
# override print to have color and background color
|
|
28
35
|
def print(
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# pylint: disable=missing-function-docstring
|
|
2
|
+
# pylint: disable=missing-module-docstring
|
|
3
|
+
import debugpy # type: ignore[import-untyped]
|
|
4
|
+
|
|
5
|
+
DEBUG_INITIALIZED = False
|
|
6
|
+
|
|
7
|
+
def vscode_prepare(port: int = 5679) -> None:
|
|
8
|
+
global DEBUG_INITIALIZED
|
|
9
|
+
|
|
10
|
+
if DEBUG_INITIALIZED:
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
print("__debugpy__")
|
|
14
|
+
debugpy.listen(("0.0.0.0", port))
|
|
15
|
+
print("__debugpy__ go")
|
|
16
|
+
debugpy.wait_for_client()
|
|
17
|
+
print(f"__debugpy__ is connected [{debugpy.is_client_connected()}]")
|
|
18
|
+
|
|
19
|
+
DEBUG_INITIALIZED = True
|
|
20
|
+
|
|
21
|
+
def breakpoint() -> None:
|
|
22
|
+
debugpy.breakpoint()
|
{torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/errors.py
RENAMED
|
@@ -13,18 +13,33 @@ class _error_struct:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Error(Enum):
|
|
16
|
+
ENOCONF = _error_struct(
|
|
17
|
+
1, "Not configured"
|
|
18
|
+
)
|
|
19
|
+
EINVAL = _error_struct(
|
|
20
|
+
22, "Invalid argument"
|
|
21
|
+
)
|
|
16
22
|
ENOPKG = _error_struct(
|
|
17
23
|
65, "Package not installed"
|
|
18
24
|
)
|
|
19
25
|
EUSER = _error_struct(
|
|
20
26
|
69, "User fault"
|
|
21
27
|
)
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
EABORT = _error_struct(
|
|
29
|
+
170, "Abort"
|
|
24
30
|
)
|
|
25
31
|
ENOFOUND = _error_struct(
|
|
26
32
|
404, "Not found"
|
|
27
33
|
)
|
|
34
|
+
EFAIL = _error_struct(
|
|
35
|
+
500, "Failed"
|
|
36
|
+
)
|
|
37
|
+
EUNKNOWN = _error_struct(
|
|
38
|
+
666, "Unknown error"
|
|
39
|
+
)
|
|
40
|
+
ETOMCRUISE = _error_struct(
|
|
41
|
+
999, "Impossible condition"
|
|
42
|
+
)
|
|
28
43
|
|
|
29
44
|
|
|
30
45
|
def Error_Out(msg: str, error: Error) -> None:
|
|
@@ -32,6 +47,7 @@ def Error_Out(msg: str, error: Error) -> None:
|
|
|
32
47
|
print(f"Error cause: {error.value.message}\n", color=Color.RED)
|
|
33
48
|
sys.exit(error.value.code)
|
|
34
49
|
|
|
50
|
+
|
|
35
51
|
def last_return_code() -> int:
|
|
36
52
|
# we are ignoring the type here because this will get the current
|
|
37
53
|
# xonsh shell instance
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_host_ip():
|
|
8
|
+
if 'WSL_DISTRO_NAME' in os.environ:
|
|
9
|
+
command = ["/mnt/c/Windows/System32/Wbem/WMIC.exe", "NICCONFIG", "WHERE", "IPEnabled=true", "GET", "IPAddress"]
|
|
10
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
11
|
+
ip_address = re.search(r'((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])', result.stdout)
|
|
12
|
+
if ip_address:
|
|
13
|
+
return ip_address.group(0)
|
|
14
|
+
else:
|
|
15
|
+
command = ["hostname", "-I"]
|
|
16
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
17
|
+
return result.stdout.split()[0]
|
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import yaml # type: ignore[import-untyped]
|
|
5
|
+
import json
|
|
6
|
+
import inspect
|
|
7
|
+
import mimetypes
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Dict, Type, TypeVar, Union, Tuple, Optional, Literal
|
|
11
|
+
from torizon_templates_utils.colors import print, Color
|
|
12
|
+
|
|
13
|
+
T = TypeVar('T')
|
|
14
|
+
|
|
15
|
+
def replace_tasks_input():
|
|
16
|
+
for file in Path('.').rglob('*.json'):
|
|
17
|
+
print(file)
|
|
18
|
+
mime_type, _ = mimetypes.guess_type(file)
|
|
19
|
+
|
|
20
|
+
if mime_type is None or mime_type.startswith("application/octet-stream"):
|
|
21
|
+
if "id_rsa" not in str(file):
|
|
22
|
+
with open(file, 'r') as f:
|
|
23
|
+
content = f.read()
|
|
24
|
+
|
|
25
|
+
content = content.replace("input:dockerLogin", "command:docker_login")
|
|
26
|
+
content = content.replace("input:dockerImageRegistry", "command:docker_registry")
|
|
27
|
+
content = content.replace("input:dockerPsswd", "command:docker_password")
|
|
28
|
+
|
|
29
|
+
with open(file, 'w') as f:
|
|
30
|
+
f.write(content)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _cast_from_json(json_data, cls: Type[T]) -> T:
|
|
34
|
+
# check on json_data if there is some key with . like "name.prop"
|
|
35
|
+
# if so, we need change the key to something like "name_prop"
|
|
36
|
+
keys = list(json_data.keys())
|
|
37
|
+
for key in keys:
|
|
38
|
+
if '.' in key:
|
|
39
|
+
new_key = key.replace('.', '_')
|
|
40
|
+
json_data[new_key] = json_data.pop(key)
|
|
41
|
+
|
|
42
|
+
expected_args = inspect.signature(cls.__init__).parameters
|
|
43
|
+
filtered_data = {k: v for k, v in json_data.items() if k in expected_args}
|
|
44
|
+
|
|
45
|
+
# check if the cls type has the any attribute
|
|
46
|
+
# the any attribute is a Dict[str, str]
|
|
47
|
+
# and it store the non expected args
|
|
48
|
+
if 'any' in expected_args:
|
|
49
|
+
non_expected_args = {k: v for k, v in json_data.items() if k not in expected_args}
|
|
50
|
+
filtered_data['any'] = non_expected_args
|
|
51
|
+
|
|
52
|
+
return cls(**filtered_data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# For Settings interface we are mapping only the Torizon specific settings
|
|
56
|
+
class TorizonSettings:
|
|
57
|
+
"""
|
|
58
|
+
TorizonSettings is a interface to map specific VS Code settings defined
|
|
59
|
+
by the Torizon extension.
|
|
60
|
+
"""
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
torizon_psswd: Optional[str] = None,
|
|
64
|
+
torizon_login: Optional[str] = None,
|
|
65
|
+
torizon_ip: Optional[str] = None,
|
|
66
|
+
torizon_ssh_port: Optional[str] = None,
|
|
67
|
+
host_ip: Optional[str] = None,
|
|
68
|
+
torizon_workspace: Optional[str] = None,
|
|
69
|
+
torizon_debug_ssh_port: Optional[str] = None,
|
|
70
|
+
torizon_debug_port1: Optional[str] = None,
|
|
71
|
+
torizon_debug_port2: Optional[str] = None,
|
|
72
|
+
torizon_debug_port3: Optional[str] = None,
|
|
73
|
+
torizon_gpu: Optional[str] = None,
|
|
74
|
+
torizon_arch: Optional[str] = None,
|
|
75
|
+
wait_sync: Optional[str] = None,
|
|
76
|
+
torizon_run_as: Optional[str] = None,
|
|
77
|
+
torizon_app_root: Optional[str] = None,
|
|
78
|
+
docker_tag: Optional[str] = None,
|
|
79
|
+
tcb_packageName: Optional[str] = None,
|
|
80
|
+
tcb_version: Optional[str] = None,
|
|
81
|
+
torizon_gpuPrefixRC: Optional[str] = None,
|
|
82
|
+
any: Optional[Dict[str, str]] = None
|
|
83
|
+
):
|
|
84
|
+
|
|
85
|
+
self.torizon_psswd = torizon_psswd
|
|
86
|
+
self.torizon_login = torizon_login
|
|
87
|
+
self.torizon_ip = torizon_ip
|
|
88
|
+
self.torizon_ssh_port = torizon_ssh_port
|
|
89
|
+
self.host_ip = host_ip
|
|
90
|
+
self.torizon_workspace = torizon_workspace
|
|
91
|
+
self.torizon_debug_ssh_port = torizon_debug_ssh_port
|
|
92
|
+
self.torizon_debug_port1 = torizon_debug_port1
|
|
93
|
+
self.torizon_debug_port2 = torizon_debug_port2
|
|
94
|
+
self.torizon_debug_port3 = torizon_debug_port3
|
|
95
|
+
self.torizon_gpu = torizon_gpu
|
|
96
|
+
self.torizon_arch = torizon_arch
|
|
97
|
+
self.wait_sync = wait_sync
|
|
98
|
+
self.torizon_run_as = torizon_run_as
|
|
99
|
+
self.torizon_app_root = torizon_app_root
|
|
100
|
+
self.docker_tag = docker_tag
|
|
101
|
+
self.tcb_packageName = tcb_packageName
|
|
102
|
+
self.tcb_version = tcb_version
|
|
103
|
+
self.torizon_gpuPrefixRC = torizon_gpuPrefixRC
|
|
104
|
+
self.any = any
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# These are from:
|
|
108
|
+
# https://code.visualstudio.com/docs/editor/tasks-appendix
|
|
109
|
+
|
|
110
|
+
class ShellConfiguration:
|
|
111
|
+
def __init__(self, executable: str, args: Optional[List[str]]):
|
|
112
|
+
self.executable = executable
|
|
113
|
+
self.args = args
|
|
114
|
+
|
|
115
|
+
class CommandOptions:
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
cwd: Optional[str] = None,
|
|
119
|
+
env: Optional[Dict[str, str]] = None,
|
|
120
|
+
shell: Optional[ShellConfiguration] = None
|
|
121
|
+
):
|
|
122
|
+
|
|
123
|
+
self.cwd = cwd
|
|
124
|
+
self.env = env
|
|
125
|
+
self.shell = shell
|
|
126
|
+
|
|
127
|
+
# we are getting this data from json
|
|
128
|
+
# so we need to cast the classes dependencies
|
|
129
|
+
if shell:
|
|
130
|
+
self.shell = _cast_from_json(shell, ShellConfiguration)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class PresentationOptions:
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
reveal: Optional[Literal['never', 'silent', 'always']] = None,
|
|
137
|
+
echo: Optional[bool] = None,
|
|
138
|
+
focus: Optional[bool] = None,
|
|
139
|
+
panel: Optional[Literal['shared', 'dedicated', 'new']] = None,
|
|
140
|
+
showReuseMessage: Optional[bool] = None,
|
|
141
|
+
clear: Optional[bool] = None,
|
|
142
|
+
group: Optional[str] = None
|
|
143
|
+
):
|
|
144
|
+
|
|
145
|
+
self.reveal = reveal
|
|
146
|
+
self.echo = echo
|
|
147
|
+
self.focus = focus
|
|
148
|
+
self.panel = panel
|
|
149
|
+
self.showReuseMessage = showReuseMessage
|
|
150
|
+
self.clear = clear
|
|
151
|
+
self.group = group
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ProblemPattern:
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
regexp: str,
|
|
158
|
+
kind: Optional[Literal['file', 'location']] = None,
|
|
159
|
+
file: Union[int, float] = 0,
|
|
160
|
+
location: Optional[Union[int, float]] = None,
|
|
161
|
+
line: Optional[Union[int, float]] =None,
|
|
162
|
+
column: Optional[Union[int, float]] = None,
|
|
163
|
+
endLine: Optional[Union[int, float]] = None,
|
|
164
|
+
endColumn: Optional[Union[int, float]] = None,
|
|
165
|
+
severity: Optional[Union[int, float]] = None,
|
|
166
|
+
code: Optional[Union[int, float]] = None,
|
|
167
|
+
message: Union[int, float] = 0,
|
|
168
|
+
loop: Optional[bool] = False
|
|
169
|
+
):
|
|
170
|
+
|
|
171
|
+
self.regexp = regexp
|
|
172
|
+
self.kind = kind
|
|
173
|
+
self.file = file
|
|
174
|
+
self.location = location
|
|
175
|
+
self.line = line
|
|
176
|
+
self.column = column
|
|
177
|
+
self.endLine = endLine
|
|
178
|
+
self.endColumn = endColumn
|
|
179
|
+
self.severity = severity
|
|
180
|
+
self.code = code
|
|
181
|
+
self.message = message
|
|
182
|
+
self.loop = loop
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class BackgroundMatcher:
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
activeOnStart: Optional[bool] = False,
|
|
189
|
+
beginsPattern: Optional[str] = None,
|
|
190
|
+
endsPattern: Optional[str] = None
|
|
191
|
+
):
|
|
192
|
+
|
|
193
|
+
self.activeOnStart = activeOnStart
|
|
194
|
+
self.beginsPattern = beginsPattern
|
|
195
|
+
self.endsPattern = endsPattern
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class ProblemMatcher:
|
|
199
|
+
def __init__(
|
|
200
|
+
self,
|
|
201
|
+
base: Optional[str] = None,
|
|
202
|
+
owner: Optional[str] = 'external',
|
|
203
|
+
source: Optional[str] = None,
|
|
204
|
+
severity: Optional[Literal['error', 'warning', 'info']] = 'error',
|
|
205
|
+
fileLocation: Optional[str | List[str] | List[
|
|
206
|
+
Union[
|
|
207
|
+
Literal['search'],
|
|
208
|
+
Dict[str, Optional[List[str]]]
|
|
209
|
+
]
|
|
210
|
+
]] = None,
|
|
211
|
+
pattern: Optional[str | ProblemPattern | List[ProblemPattern]] = None,
|
|
212
|
+
background: Optional[BackgroundMatcher] = None
|
|
213
|
+
):
|
|
214
|
+
|
|
215
|
+
self.base = base
|
|
216
|
+
self.owner = owner
|
|
217
|
+
self.source = source
|
|
218
|
+
self.severity = severity
|
|
219
|
+
self.fileLocation = fileLocation
|
|
220
|
+
self.pattern = pattern
|
|
221
|
+
self.background = background
|
|
222
|
+
|
|
223
|
+
# we are getting this data from json
|
|
224
|
+
# so we need to cast the classes dependencies
|
|
225
|
+
if pattern:
|
|
226
|
+
self.pattern = _cast_from_json(pattern, ProblemPattern)
|
|
227
|
+
|
|
228
|
+
if background:
|
|
229
|
+
self.background = _cast_from_json(background, BackgroundMatcher)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RunOptions:
|
|
233
|
+
def __init__(
|
|
234
|
+
self,
|
|
235
|
+
reevaluateOnRerun: Optional[bool] = True,
|
|
236
|
+
runOn: Optional[Literal['default', 'folderOpen']] = 'default'
|
|
237
|
+
):
|
|
238
|
+
|
|
239
|
+
self.reevaluateOnRerun = reevaluateOnRerun
|
|
240
|
+
self.runOn = runOn
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class IconOptions:
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
id: str,
|
|
247
|
+
color: Optional[str]
|
|
248
|
+
):
|
|
249
|
+
|
|
250
|
+
self.id = id
|
|
251
|
+
self.color = color
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class InputOptions:
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
id: str,
|
|
258
|
+
description: str,
|
|
259
|
+
default: Optional[str] = None,
|
|
260
|
+
type: Optional[Literal['promptString', 'pickString']] = 'promptString',
|
|
261
|
+
options: Optional[List[str]] = None
|
|
262
|
+
):
|
|
263
|
+
|
|
264
|
+
self.id = id
|
|
265
|
+
self.description = description
|
|
266
|
+
self.default = default
|
|
267
|
+
self.type = type
|
|
268
|
+
self.options = options
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class TaskDescription:
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
label: str,
|
|
275
|
+
type: Literal['shell', 'process'],
|
|
276
|
+
command: str,
|
|
277
|
+
hide: Optional[bool] = None,
|
|
278
|
+
isBackground: Optional[bool] = None,
|
|
279
|
+
args: Optional[List[str]] = None,
|
|
280
|
+
options: Optional[CommandOptions] = None,
|
|
281
|
+
group: Optional[Literal['build', 'test']] = None,
|
|
282
|
+
presentation: Optional[PresentationOptions] = None,
|
|
283
|
+
problemMatcher: Optional[str | ProblemMatcher | List[str] | List[ProblemMatcher]] = None,
|
|
284
|
+
runOptions: Optional[RunOptions] = None,
|
|
285
|
+
dependsOrder: Optional[Literal['sequence', 'parallel']] = None,
|
|
286
|
+
dependsOn: Optional[List[str]] = None,
|
|
287
|
+
icon: Optional[IconOptions] = None
|
|
288
|
+
):
|
|
289
|
+
|
|
290
|
+
self.label = label
|
|
291
|
+
self.type = type
|
|
292
|
+
self.command = command
|
|
293
|
+
self.hide: bool = (hide if hide is not None else False)
|
|
294
|
+
self.isBackground = isBackground
|
|
295
|
+
self.options = options
|
|
296
|
+
self.args = args
|
|
297
|
+
self.group = group
|
|
298
|
+
self.presentation = presentation
|
|
299
|
+
self.problemMatcher = problemMatcher
|
|
300
|
+
self.runOptions = runOptions
|
|
301
|
+
self.dependsOrder = dependsOrder
|
|
302
|
+
self.dependsOn = dependsOn
|
|
303
|
+
self.icon = icon
|
|
304
|
+
|
|
305
|
+
# we are getting this data from json
|
|
306
|
+
# so we need to cast the classes dependencies
|
|
307
|
+
if options:
|
|
308
|
+
self.options = _cast_from_json(options, CommandOptions)
|
|
309
|
+
|
|
310
|
+
if presentation:
|
|
311
|
+
self.presentation = _cast_from_json(presentation, PresentationOptions)
|
|
312
|
+
|
|
313
|
+
if runOptions:
|
|
314
|
+
self.runOptions = _cast_from_json(runOptions, RunOptions)
|
|
315
|
+
|
|
316
|
+
if icon:
|
|
317
|
+
self.icon = _cast_from_json(icon, IconOptions)
|
|
318
|
+
|
|
319
|
+
def to_dict(self):
|
|
320
|
+
return {
|
|
321
|
+
'label': self.label,
|
|
322
|
+
'type': self.type,
|
|
323
|
+
'command': self.command,
|
|
324
|
+
'isBackground': self.isBackground,
|
|
325
|
+
'args': self.args,
|
|
326
|
+
'options': self.options.__dict__ if self.options else None,
|
|
327
|
+
'group': self.group,
|
|
328
|
+
'presentation': self.presentation.__dict__ if self.presentation else None,
|
|
329
|
+
'problemMatcher': self.problemMatcher,
|
|
330
|
+
'runOptions': self.runOptions.__dict__ if self.runOptions else None,
|
|
331
|
+
'dependsOrder': self.dependsOrder,
|
|
332
|
+
'dependsOn': self.dependsOn,
|
|
333
|
+
'icon': self.icon.__dict__ if self.icon else None
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class BaseTaskConfiguration:
|
|
338
|
+
def __init__(
|
|
339
|
+
self,
|
|
340
|
+
type: str,
|
|
341
|
+
command: str,
|
|
342
|
+
isBackground: Optional[bool] = None,
|
|
343
|
+
options: Optional[CommandOptions] = None,
|
|
344
|
+
args: Optional[str] = None,
|
|
345
|
+
presentation: Optional[PresentationOptions] = None,
|
|
346
|
+
problemMatcher: Optional[str | ProblemMatcher | List[str] | List[ProblemMatcher]] = None,
|
|
347
|
+
tasks: Optional[List[TaskDescription]] = None
|
|
348
|
+
):
|
|
349
|
+
|
|
350
|
+
self.type = type
|
|
351
|
+
self.command = command
|
|
352
|
+
self.isBackground = isBackground
|
|
353
|
+
self.options = options
|
|
354
|
+
self.args = args
|
|
355
|
+
self.presentation = presentation
|
|
356
|
+
self.problemMatcher = problemMatcher
|
|
357
|
+
self.tasks = tasks
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class TaskConfiguration:
|
|
361
|
+
"""
|
|
362
|
+
TorizonConfiguration is a interface to map tasks.json file
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def __init__(
|
|
366
|
+
self,
|
|
367
|
+
version: Literal['2.0.0'] = '2.0.0',
|
|
368
|
+
tasks: Optional[List[TaskDescription]] = None,
|
|
369
|
+
inputs: Optional[List[InputOptions]] = None,
|
|
370
|
+
windows: Optional[BaseTaskConfiguration] = None,
|
|
371
|
+
osx: Optional[BaseTaskConfiguration] = None,
|
|
372
|
+
linux: Optional[BaseTaskConfiguration] = None
|
|
373
|
+
):
|
|
374
|
+
|
|
375
|
+
self.version = version
|
|
376
|
+
self.tasks = tasks
|
|
377
|
+
self.inputs = inputs
|
|
378
|
+
self.windows = windows
|
|
379
|
+
self.osx = osx
|
|
380
|
+
self.linux = linux
|
|
381
|
+
|
|
382
|
+
# as this could be from json dict, we need to cast the tasks
|
|
383
|
+
if tasks:
|
|
384
|
+
self.tasks = [_cast_from_json(task, TaskDescription) for task in tasks]
|
|
385
|
+
|
|
386
|
+
if inputs:
|
|
387
|
+
self.inputs = [_cast_from_json(_input, InputOptions) for _input in inputs]
|
|
388
|
+
|
|
389
|
+
# TODO:
|
|
390
|
+
# for now we are not casting the other configurations
|
|
391
|
+
# as them are not used in the templates
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def get_tasks_json(file_path: str) -> TaskConfiguration:
|
|
395
|
+
with open(f"{file_path}/.vscode/tasks.json", 'r') as file:
|
|
396
|
+
return _cast_from_json(json.load(file), TaskConfiguration)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def get_settings_json(
|
|
400
|
+
file_path: str,
|
|
401
|
+
custom_file: str | None = None
|
|
402
|
+
) -> TorizonSettings:
|
|
403
|
+
_file = custom_file if custom_file else "settings.json"
|
|
404
|
+
|
|
405
|
+
with open(f"{file_path}/.vscode/{_file}", 'r') as file:
|
|
406
|
+
_tor_settings = _cast_from_json(json.load(file), TorizonSettings)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
return _tor_settings
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class TaskRunner:
|
|
414
|
+
"""
|
|
415
|
+
TaskRunner is a class to run tasks from tasks.json file
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
def __init__(
|
|
419
|
+
self,
|
|
420
|
+
tasks: List[TaskDescription],
|
|
421
|
+
inputs: List[InputOptions],
|
|
422
|
+
settings: TorizonSettings,
|
|
423
|
+
debug: bool = False
|
|
424
|
+
):
|
|
425
|
+
|
|
426
|
+
self.__tasks = tasks
|
|
427
|
+
self.__inputs = inputs
|
|
428
|
+
self.__settings = settings
|
|
429
|
+
self.__debug = debug
|
|
430
|
+
self.__gitlab_ci = False
|
|
431
|
+
self.__override_env = True
|
|
432
|
+
self.__cli_inputs: Dict[str, str] = {}
|
|
433
|
+
self.__can_receive_interactive_input = False
|
|
434
|
+
|
|
435
|
+
# check if we have stdin
|
|
436
|
+
if os.isatty(0) and ("TASKS_DISABLE_INTERACTIVE_INPUT" not in os.environ):
|
|
437
|
+
self.__can_receive_interactive_input = True
|
|
438
|
+
|
|
439
|
+
# environment configs
|
|
440
|
+
if "DOCKER_PSSWD" in os.environ:
|
|
441
|
+
os.environ["config:docker_password"] = os.environ["DOCKER_PSSWD"]
|
|
442
|
+
|
|
443
|
+
if "GITLAB_CI" in os.environ:
|
|
444
|
+
self.__gitlab_ci = True
|
|
445
|
+
|
|
446
|
+
if "TASKS_OVERRIDE_ENV" in os.environ:
|
|
447
|
+
self.__override_env = False
|
|
448
|
+
|
|
449
|
+
if "TASKS_DEBUG" in os.environ:
|
|
450
|
+
self.__debug = True
|
|
451
|
+
|
|
452
|
+
self.__settings_to_env()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def __settings_to_env(self):
|
|
456
|
+
# for keys in settings, we are adding to env
|
|
457
|
+
for key, value in self.__settings.__dict__.items():
|
|
458
|
+
if value is not None:
|
|
459
|
+
os.environ[f"config:{key}"] = f"{value}"
|
|
460
|
+
|
|
461
|
+
# also for non Torizon ones
|
|
462
|
+
for key, value in self.__settings.any.items():
|
|
463
|
+
if isinstance(value, str) or \
|
|
464
|
+
isinstance(value, int) or \
|
|
465
|
+
isinstance(value, float):
|
|
466
|
+
|
|
467
|
+
os.environ[f"config:{key}"] = str(value)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def list_labels(self, show_hidden=False, no_index: bool = False):
|
|
471
|
+
i = 0
|
|
472
|
+
|
|
473
|
+
for task in self.__tasks:
|
|
474
|
+
if no_index:
|
|
475
|
+
if show_hidden or not task.hide:
|
|
476
|
+
print(task.label)
|
|
477
|
+
else:
|
|
478
|
+
if show_hidden or not task.hide:
|
|
479
|
+
print(f"{i}. \t{task.label}")
|
|
480
|
+
|
|
481
|
+
i += 1
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def desc_input(self, id: str):
|
|
485
|
+
for _input in self.__inputs:
|
|
486
|
+
if _input.id == id:
|
|
487
|
+
print(json.dumps(_input.__dict__, indent=4))
|
|
488
|
+
return
|
|
489
|
+
|
|
490
|
+
raise ReferenceError(f"Input with id [{id}] not found")
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def desc_task(self, label: int | str):
|
|
494
|
+
task = None
|
|
495
|
+
|
|
496
|
+
if isinstance(label, int):
|
|
497
|
+
task = self.__tasks[label]
|
|
498
|
+
else:
|
|
499
|
+
for _task in self.__tasks:
|
|
500
|
+
if _task.label == label:
|
|
501
|
+
task = _task
|
|
502
|
+
break
|
|
503
|
+
|
|
504
|
+
if task is not None:
|
|
505
|
+
task_txt = json.dumps(task.to_dict(), indent=4)
|
|
506
|
+
print(task_txt)
|
|
507
|
+
else:
|
|
508
|
+
raise ReferenceError(f"Task with index [{label}] not found")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def __replace_env_var(self, var: str, env: str):
|
|
512
|
+
if f"${{{var}}}" in env:
|
|
513
|
+
return env.replace(f"${{{var}}}", os.environ[var])
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def __check_workspace_folder(self, env: List[str]) -> List[str]:
|
|
517
|
+
ret: List[str] = []
|
|
518
|
+
|
|
519
|
+
for value in env:
|
|
520
|
+
if "workspaceFolderBasename" in value:
|
|
521
|
+
value = self.__replace_env_var("workspaceFolderBasename", value)
|
|
522
|
+
if "workspaceFolder" in value:
|
|
523
|
+
value = self.__replace_env_var("workspaceFolder", value)
|
|
524
|
+
ret.append(value)
|
|
525
|
+
|
|
526
|
+
return ret
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def __check_torizon_inputs(self, env: List[str]) -> List[str]:
|
|
530
|
+
ret: List[str] = []
|
|
531
|
+
|
|
532
|
+
for value in env:
|
|
533
|
+
if "${command:torizon_" in value:
|
|
534
|
+
value = value.replace("${command:torizon_", "${config:torizon_")
|
|
535
|
+
ret.append(value)
|
|
536
|
+
|
|
537
|
+
return ret
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def __check_docker_inputs(self, env: List[str]) -> List[str]:
|
|
541
|
+
ret: List[str] = []
|
|
542
|
+
|
|
543
|
+
for value in env:
|
|
544
|
+
if "${command:docker_" in value:
|
|
545
|
+
value = value.replace("${command:docker_", "${config:docker_")
|
|
546
|
+
ret.append(value)
|
|
547
|
+
|
|
548
|
+
return ret
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def __check_tcb_inputs(self, env: List[str]) -> List[str]:
|
|
552
|
+
ret: List[str] = []
|
|
553
|
+
|
|
554
|
+
for value in env:
|
|
555
|
+
if "${command:tcb" in value:
|
|
556
|
+
if "tcb.getNextPackageVersion" in value:
|
|
557
|
+
# call the xonsh script
|
|
558
|
+
_p_ret = subprocess.run(
|
|
559
|
+
[
|
|
560
|
+
"xonsh",
|
|
561
|
+
"./conf/torizon-io.xsh",
|
|
562
|
+
"package", "latest", "version",
|
|
563
|
+
os.environ["config:tcb_packageName"]
|
|
564
|
+
],
|
|
565
|
+
capture_output=True,
|
|
566
|
+
text=True
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
if _p_ret.returncode != 0:
|
|
570
|
+
raise RuntimeError(f"Error running torizon-io.xsh: {_p_ret.stderr}")
|
|
571
|
+
|
|
572
|
+
_next = int(_p_ret.stdout.strip()) +1
|
|
573
|
+
|
|
574
|
+
if self.__debug:
|
|
575
|
+
print(f"Next package version: {_next}")
|
|
576
|
+
|
|
577
|
+
ret.append(
|
|
578
|
+
value.replace("${{command:tcb.getNextPackageVersion}}", f"{_next}")
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
elif "tcb.outputTEZIFolder" in value:
|
|
582
|
+
# load the tcbuild.yaml
|
|
583
|
+
with open("tcbuild.yaml", 'r') as file:
|
|
584
|
+
_tcbuild = yaml.load(file, Loader=yaml.FullLoader)
|
|
585
|
+
|
|
586
|
+
_tezi_folder = None
|
|
587
|
+
try:
|
|
588
|
+
_tezi_folder = _tcbuild["output"]["easy-installer"]["local"]
|
|
589
|
+
except KeyError:
|
|
590
|
+
raise RuntimeError("Error replacing variable tcb.outputTEZIFolder, make sure the tcbuild.yaml has the output.easy-installer.local property")
|
|
591
|
+
|
|
592
|
+
value = value.replace("${{command:tcb.outputTEZIFolder}}", _tezi_folder)
|
|
593
|
+
|
|
594
|
+
# for all the items we need to replace ${command:tcb. with ${config:tcb.
|
|
595
|
+
_pattern = r"(?<=\$\{command:tcb\.).*?(?=\s*})"
|
|
596
|
+
_matches = re.findall(_pattern, value)
|
|
597
|
+
|
|
598
|
+
for match in _matches:
|
|
599
|
+
value = value.replace(f"${{command:tcb.{match}}}", f"${{config:tcb.{match}}}")
|
|
600
|
+
|
|
601
|
+
ret.append(value)
|
|
602
|
+
|
|
603
|
+
return ret
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def __contains_special_chars(self, str: str) -> bool:
|
|
607
|
+
_pattern = r"[^a-zA-Z0-9\.\-_|>\/=]"
|
|
608
|
+
return re.search(_pattern, str) is not None
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def __scape_args(self, args: List[str]) -> List[str]:
|
|
612
|
+
ret: List[str] = []
|
|
613
|
+
|
|
614
|
+
for arg in args:
|
|
615
|
+
if "\"" in arg:
|
|
616
|
+
arg = arg.replace("\"", "\\\"")
|
|
617
|
+
|
|
618
|
+
ret.append(arg)
|
|
619
|
+
|
|
620
|
+
return ret
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def __check_config(self, args: List[str]) -> List[str]:
|
|
624
|
+
"""
|
|
625
|
+
This method will make the config replacement in the args
|
|
626
|
+
"""
|
|
627
|
+
ret: List[str] = []
|
|
628
|
+
|
|
629
|
+
for arg in args:
|
|
630
|
+
if "${config:" in arg:
|
|
631
|
+
_pattern = r"(?<=\$\{config:).*?(?=\s*})"
|
|
632
|
+
_matches = re.findall(_pattern, arg)
|
|
633
|
+
|
|
634
|
+
for match in _matches:
|
|
635
|
+
if "." in match:
|
|
636
|
+
_match = match.replace(".", "_")
|
|
637
|
+
else:
|
|
638
|
+
_match = match
|
|
639
|
+
|
|
640
|
+
# first check if the config exists
|
|
641
|
+
if f"config:{_match}" not in os.environ:
|
|
642
|
+
raise ReferenceError(f"Config with id [{match}] not found. Check your settings.json")
|
|
643
|
+
|
|
644
|
+
# edge case for docker_registry
|
|
645
|
+
if _match == "docker_registry" and os.environ[f"config:{_match}"] == "":
|
|
646
|
+
os.environ[f"config:{_match}"] = "registry-1.docker.io"
|
|
647
|
+
|
|
648
|
+
arg = arg.replace(f"${{config:{match}}}", os.environ[f"config:{_match}"])
|
|
649
|
+
|
|
650
|
+
ret.append(arg)
|
|
651
|
+
|
|
652
|
+
return ret
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def __check_vscode_env(self, args: List[str]) -> List[str]:
|
|
656
|
+
"""
|
|
657
|
+
handle the VS Code ${env:VAR} replacement
|
|
658
|
+
"""
|
|
659
|
+
ret: List[str] = []
|
|
660
|
+
|
|
661
|
+
for arg in args:
|
|
662
|
+
if "${env:" in arg:
|
|
663
|
+
_pattern = r"(?<=\$\{env:).*?(?=\s*})"
|
|
664
|
+
_matches = re.findall(_pattern, arg)
|
|
665
|
+
|
|
666
|
+
for match in _matches:
|
|
667
|
+
if match not in os.environ:
|
|
668
|
+
raise ReferenceError(f"Environment variable with id [{match}] not found")
|
|
669
|
+
|
|
670
|
+
arg = arg.replace(f"${{env:{match}}}", os.environ[match])
|
|
671
|
+
|
|
672
|
+
ret.append(arg)
|
|
673
|
+
|
|
674
|
+
return ret
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def __check_long_args(self, args: List[str]) -> List[str]:
|
|
678
|
+
ret: List[str] = []
|
|
679
|
+
|
|
680
|
+
for arg in args:
|
|
681
|
+
if " " in arg:
|
|
682
|
+
arg = f"'{arg}'"
|
|
683
|
+
|
|
684
|
+
ret.append(arg)
|
|
685
|
+
|
|
686
|
+
return ret
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def __quoting_special_chars(self, args: List[str]) -> List[str]:
|
|
690
|
+
ret: List[str] = []
|
|
691
|
+
|
|
692
|
+
for arg in args:
|
|
693
|
+
_has_special_chars = self.__contains_special_chars(arg)
|
|
694
|
+
_hash_space = " " in arg
|
|
695
|
+
|
|
696
|
+
if _has_special_chars and not _hash_space:
|
|
697
|
+
arg = f"'{arg}'"
|
|
698
|
+
|
|
699
|
+
ret.append(arg)
|
|
700
|
+
|
|
701
|
+
return ret
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def __check_input(self, args: List[str]) -> List[str]:
|
|
705
|
+
ret: List[str] = []
|
|
706
|
+
|
|
707
|
+
for arg in args:
|
|
708
|
+
if "${input:" in arg:
|
|
709
|
+
_pattern = r"(?<=\$\{input:).*?(?=\s*})"
|
|
710
|
+
_matches = re.findall(_pattern, arg)
|
|
711
|
+
|
|
712
|
+
for match in _matches:
|
|
713
|
+
_input = None
|
|
714
|
+
_input_value = "None"
|
|
715
|
+
|
|
716
|
+
for inp in self.__inputs:
|
|
717
|
+
if inp.id == match:
|
|
718
|
+
_input = inp
|
|
719
|
+
break
|
|
720
|
+
|
|
721
|
+
if _input is None:
|
|
722
|
+
raise ReferenceError(f"Input with id [{match}] not found")
|
|
723
|
+
|
|
724
|
+
# first check if the input was set by cli
|
|
725
|
+
if match in self.__cli_inputs:
|
|
726
|
+
_input_value = self.__cli_inputs[match]
|
|
727
|
+
elif _input.default:
|
|
728
|
+
_input_value = _input.default
|
|
729
|
+
else:
|
|
730
|
+
if not self.__can_receive_interactive_input:
|
|
731
|
+
raise RuntimeError("CLI inputs not set and interactive input is disabled")
|
|
732
|
+
|
|
733
|
+
if _input.type == "promptString":
|
|
734
|
+
_input_value = input(f"{_input.description}: ")
|
|
735
|
+
elif _input.type == "pickString":
|
|
736
|
+
for _inp in self.__inputs:
|
|
737
|
+
if _inp.id == match:
|
|
738
|
+
# print options
|
|
739
|
+
assert _inp.options is not None, "pickString option has a valid id but options is empty. Check your tasks.json"
|
|
740
|
+
print(f"Options for [{match}]:")
|
|
741
|
+
_i = 0
|
|
742
|
+
_indexed_options = {}
|
|
743
|
+
|
|
744
|
+
for _opt in _inp.options:
|
|
745
|
+
_indexed_options[str(_i)] = _opt
|
|
746
|
+
print(f"{_i}. {_opt}")
|
|
747
|
+
_i += 1
|
|
748
|
+
|
|
749
|
+
_input_value = input(f"{_input.description} (option index): ")
|
|
750
|
+
|
|
751
|
+
# check if the input is in the options
|
|
752
|
+
if _input_value not in _indexed_options:
|
|
753
|
+
raise ValueError(f"Input value for [{match}] is not in the possible options")
|
|
754
|
+
else:
|
|
755
|
+
_input_value = _indexed_options[_input_value]
|
|
756
|
+
|
|
757
|
+
if _input_value is None:
|
|
758
|
+
raise ValueError(f"Input value for [{match}] could not be None")
|
|
759
|
+
|
|
760
|
+
arg = arg.replace(f"${{input:{match}}}", _input_value)
|
|
761
|
+
|
|
762
|
+
ret.append(arg)
|
|
763
|
+
|
|
764
|
+
return ret
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def __parse_envs(self, env: str, task: TaskDescription) -> str | None :
|
|
768
|
+
"""
|
|
769
|
+
It's christmas time 🎅
|
|
770
|
+
"""
|
|
771
|
+
if task.options:
|
|
772
|
+
value = task.options.env
|
|
773
|
+
|
|
774
|
+
# get the env from the task
|
|
775
|
+
if value:
|
|
776
|
+
_env_value = value.get(env)
|
|
777
|
+
|
|
778
|
+
if _env_value:
|
|
779
|
+
expvalue = [_env_value]
|
|
780
|
+
expvalue = self.__check_workspace_folder(expvalue)
|
|
781
|
+
expvalue = self.__check_torizon_inputs(expvalue)
|
|
782
|
+
expvalue = self.__check_docker_inputs(expvalue)
|
|
783
|
+
expvalue = self.__check_tcb_inputs(expvalue)
|
|
784
|
+
expvalue = self.__check_input(expvalue)
|
|
785
|
+
expvalue = self.__check_config(expvalue)
|
|
786
|
+
exp_value_str = " ".join(expvalue)
|
|
787
|
+
|
|
788
|
+
if self.__debug:
|
|
789
|
+
print(f"Env: {env}={_env_value}")
|
|
790
|
+
print(f"Parsed Env: {env}={exp_value_str}")
|
|
791
|
+
|
|
792
|
+
return exp_value_str
|
|
793
|
+
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def __replace_docker_host(self, arg: str) -> str:
|
|
798
|
+
if "DOCKER_HOST" in arg:
|
|
799
|
+
arg = arg.replace("DOCKER_HOST=", "DOCKER_HOST=tcp://docker:2375")
|
|
800
|
+
|
|
801
|
+
return arg
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def set_cli_inputs(self, cli_inputs: Dict[str, str]) -> None:
|
|
805
|
+
"""
|
|
806
|
+
Set the cli inputs to be used in the tasks.
|
|
807
|
+
"""
|
|
808
|
+
for key, value in cli_inputs.items():
|
|
809
|
+
# validate if the key is in the inputs
|
|
810
|
+
_input = None
|
|
811
|
+
_input = next((inp for inp in self.__inputs if inp.id == key), None)
|
|
812
|
+
|
|
813
|
+
if _input is None:
|
|
814
|
+
raise ReferenceError(f"Input with id [{key}] not found")
|
|
815
|
+
|
|
816
|
+
self.__cli_inputs[key] = value
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def run_task(self, label: str) -> None:
|
|
820
|
+
# query the task
|
|
821
|
+
_task = None
|
|
822
|
+
_task = next((task for task in self.__tasks if task.label == label), None)
|
|
823
|
+
|
|
824
|
+
if _task is None:
|
|
825
|
+
raise ReferenceError(f"Task with label [{label}] not found")
|
|
826
|
+
|
|
827
|
+
# prepare the command
|
|
828
|
+
_cmd = _task.command
|
|
829
|
+
|
|
830
|
+
# the cmd itself can use the mechanism to replace stuff
|
|
831
|
+
_cmd = self.__check_workspace_folder([_cmd])[0]
|
|
832
|
+
_cmd = self.__check_torizon_inputs([_cmd])[0]
|
|
833
|
+
_cmd = self.__check_docker_inputs([_cmd])[0]
|
|
834
|
+
_cmd = self.__check_tcb_inputs([_cmd])[0]
|
|
835
|
+
_cmd = self.__check_input([_cmd])[0]
|
|
836
|
+
_cmd = self.__check_vscode_env([_cmd])[0]
|
|
837
|
+
_cmd = self.__check_config([_cmd])[0]
|
|
838
|
+
|
|
839
|
+
_args = []
|
|
840
|
+
if _task.args is not None:
|
|
841
|
+
_args = _task.args
|
|
842
|
+
|
|
843
|
+
_env: Dict[str, str] | None = {}
|
|
844
|
+
_cwd = None
|
|
845
|
+
_last_cwd = os.getcwd()
|
|
846
|
+
if _task.options is not None:
|
|
847
|
+
_env = _task.options.env
|
|
848
|
+
_cwd = _task.options.cwd
|
|
849
|
+
|
|
850
|
+
_depends = []
|
|
851
|
+
if _task.dependsOn is not None:
|
|
852
|
+
_depends = _task.dependsOn
|
|
853
|
+
|
|
854
|
+
# first we need to run the dependencies
|
|
855
|
+
for dep in _depends:
|
|
856
|
+
self.run_task(dep)
|
|
857
|
+
|
|
858
|
+
print(f"> Executing task: {label} <", color=Color.GREEN)
|
|
859
|
+
|
|
860
|
+
_is_background = ""
|
|
861
|
+
if _task.isBackground:
|
|
862
|
+
_is_background = " &"
|
|
863
|
+
|
|
864
|
+
_shell = _task.type == "shell"
|
|
865
|
+
|
|
866
|
+
# FIXME: The scape args was in the powershell implementation
|
|
867
|
+
# but when used on Python it generates weird behavior
|
|
868
|
+
# _args = self.__scape_args(_args)
|
|
869
|
+
_args = self.__check_workspace_folder(_args)
|
|
870
|
+
_args = self.__check_torizon_inputs(_args)
|
|
871
|
+
_args = self.__check_docker_inputs(_args)
|
|
872
|
+
_args = self.__check_tcb_inputs(_args)
|
|
873
|
+
_args = self.__check_input(_args)
|
|
874
|
+
_args = self.__check_vscode_env(_args)
|
|
875
|
+
_args = self.__check_config(_args)
|
|
876
|
+
_args = self.__check_long_args(_args)
|
|
877
|
+
_args = self.__quoting_special_chars(_args)
|
|
878
|
+
|
|
879
|
+
# if in gitlab ci env we need to replace the DOCKER_HOST
|
|
880
|
+
if self.__gitlab_ci:
|
|
881
|
+
_cmd = self.__replace_docker_host(_cmd)
|
|
882
|
+
|
|
883
|
+
# inject env
|
|
884
|
+
if _env is not None:
|
|
885
|
+
for env, value in _env.items():
|
|
886
|
+
if self.__override_env:
|
|
887
|
+
__env = self.__parse_envs(env, _task)
|
|
888
|
+
if __env:
|
|
889
|
+
os.environ[env] = __env
|
|
890
|
+
else:
|
|
891
|
+
if env not in os.environ:
|
|
892
|
+
__env = self.__parse_envs(env, _task)
|
|
893
|
+
if __env:
|
|
894
|
+
os.environ[env] = __env
|
|
895
|
+
|
|
896
|
+
# we need to change the cwd if it's set
|
|
897
|
+
if _cwd is not None:
|
|
898
|
+
_cwd = self.__check_workspace_folder([_cwd])[0]
|
|
899
|
+
_cwd = self.__check_config([_cwd])[0]
|
|
900
|
+
_cwd = self.__check_vscode_env([_cwd])[0]
|
|
901
|
+
|
|
902
|
+
os.chdir(_cwd)
|
|
903
|
+
|
|
904
|
+
# execute the task
|
|
905
|
+
_cmd_join = f"{_cmd} {' '.join(_args)}{_is_background}"
|
|
906
|
+
|
|
907
|
+
if self.__debug:
|
|
908
|
+
print(f"Command: {_task.command}", color=Color.YELLOW)
|
|
909
|
+
print(f"Args: {_task.args}", color=Color.YELLOW)
|
|
910
|
+
print(f"Parsed Args: {_args}", color=Color.YELLOW)
|
|
911
|
+
print(f"Parsed Command: {_cmd_join}", color=Color.YELLOW)
|
|
912
|
+
|
|
913
|
+
_ret = subprocess.run(
|
|
914
|
+
[_cmd, *_args] if not _shell else _cmd_join,
|
|
915
|
+
stdout=None,
|
|
916
|
+
stderr=None,
|
|
917
|
+
env=os.environ,
|
|
918
|
+
shell=_shell
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
# go back to the last cwd
|
|
922
|
+
os.chdir(_last_cwd)
|
|
923
|
+
|
|
924
|
+
if _ret.returncode != 0:
|
|
925
|
+
print(f"> TASK [{label}] exited with error code [{_ret.returncode}] <", color=Color.RED)
|
|
926
|
+
raise RuntimeError(f"Error running task: {label}")
|
|
927
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: torizon_templates_utils
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Package with utilities for Torizon Templates scripts
|
|
5
5
|
Author-email: Matheus Castello <matheus.castello@toradex.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/torizon/vscode-torizon-templates
|
|
@@ -11,6 +11,10 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
11
11
|
Requires-Python: >=3.8
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: torizon-io-api
|
|
15
|
+
Requires-Dist: requests
|
|
16
|
+
Requires-Dist: pyyaml
|
|
17
|
+
Requires-Dist: debugpy
|
|
14
18
|
|
|
15
19
|
# Torizon Templates Utils
|
|
16
20
|
|
|
@@ -2,9 +2,15 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
torizon_templates_utils/__init__.py
|
|
5
|
+
torizon_templates_utils/animations.py
|
|
6
|
+
torizon_templates_utils/args.py
|
|
5
7
|
torizon_templates_utils/colors.py
|
|
8
|
+
torizon_templates_utils/debug.py
|
|
6
9
|
torizon_templates_utils/errors.py
|
|
10
|
+
torizon_templates_utils/network.py
|
|
11
|
+
torizon_templates_utils/tasks.py
|
|
7
12
|
torizon_templates_utils.egg-info/PKG-INFO
|
|
8
13
|
torizon_templates_utils.egg-info/SOURCES.txt
|
|
9
14
|
torizon_templates_utils.egg-info/dependency_links.txt
|
|
15
|
+
torizon_templates_utils.egg-info/requires.txt
|
|
10
16
|
torizon_templates_utils.egg-info/top_level.txt
|
|
File without changes
|
|
File without changes
|
{torizon_templates_utils-0.0.1 → torizon_templates_utils-0.0.3}/torizon_templates_utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|