dev-tools-loader 0.1.0__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.
- dev_tools_loader-0.1.0/LICENSE +21 -0
- dev_tools_loader-0.1.0/PKG-INFO +143 -0
- dev_tools_loader-0.1.0/README.md +131 -0
- dev_tools_loader-0.1.0/dev_tools_loader/__init__.py +5 -0
- dev_tools_loader-0.1.0/dev_tools_loader/__main__.py +4 -0
- dev_tools_loader-0.1.0/dev_tools_loader/cli.py +31 -0
- dev_tools_loader-0.1.0/dev_tools_loader/dev_tools_loader.py +397 -0
- dev_tools_loader-0.1.0/dev_tools_loader.egg-info/PKG-INFO +143 -0
- dev_tools_loader-0.1.0/dev_tools_loader.egg-info/SOURCES.txt +13 -0
- dev_tools_loader-0.1.0/dev_tools_loader.egg-info/dependency_links.txt +1 -0
- dev_tools_loader-0.1.0/dev_tools_loader.egg-info/entry_points.txt +2 -0
- dev_tools_loader-0.1.0/dev_tools_loader.egg-info/top_level.txt +1 -0
- dev_tools_loader-0.1.0/pyproject.toml +23 -0
- dev_tools_loader-0.1.0/setup.cfg +4 -0
- dev_tools_loader-0.1.0/tests/test_dev_tools_loader.py +127 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KARMA-Electronics
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dev_tools_loader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Development tools loader
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/karma_electronics/desktop/dev_tools_loader
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/karma_electronics/desktop/dev_tools_loader.git
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# Development Tools Loader
|
|
14
|
+
|
|
15
|
+
A CLI tool for automated downloading of development tools:
|
|
16
|
+
|
|
17
|
+
- VS Code installers and extensions (`.vsix` files).
|
|
18
|
+
- Python installers and pip packages;
|
|
19
|
+
|
|
20
|
+
The tool ensures version compatibility, handles dependencies, and supports repeatable configurations via JSON.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Key Features
|
|
24
|
+
|
|
25
|
+
- **Version control**: Ensures VS Code extension versions match the target VS Code engine version.
|
|
26
|
+
- **Package bundles**: Supports downloading multiple packages/extensions in a single config.
|
|
27
|
+
- **Dependency handling**: Automatically resolves and downloads dependencies for VS Code extensions.
|
|
28
|
+
- **Resilient downloads**: Automatically retries on connection loss.
|
|
29
|
+
- **Repeatable setups**: JSON-based configuration enables reproducible environment setups.
|
|
30
|
+
- **Flexible versioning**: Supports `"latest"` for extensions and packages.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install dev-tools-loader
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Running
|
|
41
|
+
|
|
42
|
+
Once installed, you can run the tool from the command line using the JSON configuration file.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
dev_tools_loader -j path/to/config.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Command‑Line Options
|
|
50
|
+
|
|
51
|
+
- **`-j`, `--json-path` *`<json_config_path>`*** **(required)** Specifies the path to the JSON configuration file that defines download targets.
|
|
52
|
+
- **`-o`, `--output-path` *`<output_dir>`*** Sets the output directory where downloaded files will be saved.
|
|
53
|
+
- **`-c`, `--clean`** If specified, deletes files in the target output directory before starting the download process.
|
|
54
|
+
- **`-h`, `--help`** Displays the help message with a summary of all available options and exits.
|
|
55
|
+
- **`--version`** Prints the current version of the `dev-tools-loader` package and exits.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Example Config
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"version": "0.1.0",
|
|
63
|
+
"targets": [
|
|
64
|
+
{
|
|
65
|
+
"type": "python",
|
|
66
|
+
"platform": "win_amd64",
|
|
67
|
+
"version": "3.12.0",
|
|
68
|
+
"installer": "load",
|
|
69
|
+
"packages": [
|
|
70
|
+
{
|
|
71
|
+
"name": "compiledb",
|
|
72
|
+
"version": "0.10.6"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "requests",
|
|
76
|
+
"version": "latest"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "vscode",
|
|
82
|
+
"platform": "win32-x64",
|
|
83
|
+
"version": "1.96.0",
|
|
84
|
+
"installer": "load",
|
|
85
|
+
"extensions": [
|
|
86
|
+
{
|
|
87
|
+
"uid": "ms-vscode.cpptools",
|
|
88
|
+
"version": "1.28.0"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"uid": "ms-python.python",
|
|
92
|
+
"version": "latest"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Configuration Fields
|
|
102
|
+
|
|
103
|
+
- `version` (str): Schema version.
|
|
104
|
+
- `targets` (list): List of download targets. Each target has:
|
|
105
|
+
- `type` (str): `"python"` or `"vscode"`.
|
|
106
|
+
- `platform` (str): Target platform (see supported platforms below).
|
|
107
|
+
- `version` (str): Version of the tool.
|
|
108
|
+
- `installer` (str): `"load"` to download installer or `"skip"`.
|
|
109
|
+
- `packages` (list, Python-only): List of pip packages to download.
|
|
110
|
+
- `name` (str): Package name.
|
|
111
|
+
- `version` (str): Package version (`"latest"` supported).
|
|
112
|
+
- `extensions` (list, VS Code-only): List of VS Code extensions to download.
|
|
113
|
+
- `uid` (str): Extension ID (e.g., `"ms-vscode.cpptools"`).
|
|
114
|
+
- `version` (str): Extension version (`"latest"` supported).
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Supported Platforms Python
|
|
118
|
+
|
|
119
|
+
- `win32`
|
|
120
|
+
- `win_amd64`
|
|
121
|
+
- `win_arm64`
|
|
122
|
+
- `manylinux1_x86_64`
|
|
123
|
+
- `manylinux2010_x86_64`
|
|
124
|
+
- `manylinux2014_x86_64`
|
|
125
|
+
- `manylinux1_i686`
|
|
126
|
+
- `manylinux2010_i686`
|
|
127
|
+
- `manylinux2014_i686`
|
|
128
|
+
- `manylinux2014_aarch64`
|
|
129
|
+
- `manylinux2014_armv7l`
|
|
130
|
+
- `macosx_10_9_x86_64`
|
|
131
|
+
- `macosx_11_0_arm64`
|
|
132
|
+
|
|
133
|
+
## Supported Platforms VS Code
|
|
134
|
+
|
|
135
|
+
- `win32-x64`
|
|
136
|
+
- `win32-arm64`
|
|
137
|
+
- `linux-x64`
|
|
138
|
+
- `linux-arm64`
|
|
139
|
+
- `linux-armhf`
|
|
140
|
+
- `alpine-x64`
|
|
141
|
+
- `alpine-arm64`
|
|
142
|
+
- `darwin-x64`
|
|
143
|
+
- `darwin-arm64`
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Development Tools Loader
|
|
2
|
+
|
|
3
|
+
A CLI tool for automated downloading of development tools:
|
|
4
|
+
|
|
5
|
+
- VS Code installers and extensions (`.vsix` files).
|
|
6
|
+
- Python installers and pip packages;
|
|
7
|
+
|
|
8
|
+
The tool ensures version compatibility, handles dependencies, and supports repeatable configurations via JSON.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Key Features
|
|
12
|
+
|
|
13
|
+
- **Version control**: Ensures VS Code extension versions match the target VS Code engine version.
|
|
14
|
+
- **Package bundles**: Supports downloading multiple packages/extensions in a single config.
|
|
15
|
+
- **Dependency handling**: Automatically resolves and downloads dependencies for VS Code extensions.
|
|
16
|
+
- **Resilient downloads**: Automatically retries on connection loss.
|
|
17
|
+
- **Repeatable setups**: JSON-based configuration enables reproducible environment setups.
|
|
18
|
+
- **Flexible versioning**: Supports `"latest"` for extensions and packages.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install dev-tools-loader
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Running
|
|
29
|
+
|
|
30
|
+
Once installed, you can run the tool from the command line using the JSON configuration file.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dev_tools_loader -j path/to/config.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Command‑Line Options
|
|
38
|
+
|
|
39
|
+
- **`-j`, `--json-path` *`<json_config_path>`*** **(required)** Specifies the path to the JSON configuration file that defines download targets.
|
|
40
|
+
- **`-o`, `--output-path` *`<output_dir>`*** Sets the output directory where downloaded files will be saved.
|
|
41
|
+
- **`-c`, `--clean`** If specified, deletes files in the target output directory before starting the download process.
|
|
42
|
+
- **`-h`, `--help`** Displays the help message with a summary of all available options and exits.
|
|
43
|
+
- **`--version`** Prints the current version of the `dev-tools-loader` package and exits.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Example Config
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"version": "0.1.0",
|
|
51
|
+
"targets": [
|
|
52
|
+
{
|
|
53
|
+
"type": "python",
|
|
54
|
+
"platform": "win_amd64",
|
|
55
|
+
"version": "3.12.0",
|
|
56
|
+
"installer": "load",
|
|
57
|
+
"packages": [
|
|
58
|
+
{
|
|
59
|
+
"name": "compiledb",
|
|
60
|
+
"version": "0.10.6"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "requests",
|
|
64
|
+
"version": "latest"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "vscode",
|
|
70
|
+
"platform": "win32-x64",
|
|
71
|
+
"version": "1.96.0",
|
|
72
|
+
"installer": "load",
|
|
73
|
+
"extensions": [
|
|
74
|
+
{
|
|
75
|
+
"uid": "ms-vscode.cpptools",
|
|
76
|
+
"version": "1.28.0"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"uid": "ms-python.python",
|
|
80
|
+
"version": "latest"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Configuration Fields
|
|
90
|
+
|
|
91
|
+
- `version` (str): Schema version.
|
|
92
|
+
- `targets` (list): List of download targets. Each target has:
|
|
93
|
+
- `type` (str): `"python"` or `"vscode"`.
|
|
94
|
+
- `platform` (str): Target platform (see supported platforms below).
|
|
95
|
+
- `version` (str): Version of the tool.
|
|
96
|
+
- `installer` (str): `"load"` to download installer or `"skip"`.
|
|
97
|
+
- `packages` (list, Python-only): List of pip packages to download.
|
|
98
|
+
- `name` (str): Package name.
|
|
99
|
+
- `version` (str): Package version (`"latest"` supported).
|
|
100
|
+
- `extensions` (list, VS Code-only): List of VS Code extensions to download.
|
|
101
|
+
- `uid` (str): Extension ID (e.g., `"ms-vscode.cpptools"`).
|
|
102
|
+
- `version` (str): Extension version (`"latest"` supported).
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## Supported Platforms Python
|
|
106
|
+
|
|
107
|
+
- `win32`
|
|
108
|
+
- `win_amd64`
|
|
109
|
+
- `win_arm64`
|
|
110
|
+
- `manylinux1_x86_64`
|
|
111
|
+
- `manylinux2010_x86_64`
|
|
112
|
+
- `manylinux2014_x86_64`
|
|
113
|
+
- `manylinux1_i686`
|
|
114
|
+
- `manylinux2010_i686`
|
|
115
|
+
- `manylinux2014_i686`
|
|
116
|
+
- `manylinux2014_aarch64`
|
|
117
|
+
- `manylinux2014_armv7l`
|
|
118
|
+
- `macosx_10_9_x86_64`
|
|
119
|
+
- `macosx_11_0_arm64`
|
|
120
|
+
|
|
121
|
+
## Supported Platforms VS Code
|
|
122
|
+
|
|
123
|
+
- `win32-x64`
|
|
124
|
+
- `win32-arm64`
|
|
125
|
+
- `linux-x64`
|
|
126
|
+
- `linux-arm64`
|
|
127
|
+
- `linux-armhf`
|
|
128
|
+
- `alpine-x64`
|
|
129
|
+
- `alpine-arm64`
|
|
130
|
+
- `darwin-x64`
|
|
131
|
+
- `darwin-arm64`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .dev_tools_loader import DebugLog, DevToolsLoader
|
|
6
|
+
from dev_tools_loader import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
parser = argparse.ArgumentParser(description='Development tools loader CLI')
|
|
11
|
+
parser.add_argument('-j', '--json-path', help='Path to JSON config file', action='store', required=True)
|
|
12
|
+
parser.add_argument('-o', '--output-path', help='Path to output dir (default ./output)', action='store', default=Path(os.getcwd()) / Path('output'))
|
|
13
|
+
parser.add_argument('-c', '--clean', help='Clean output before load (default False)', action='store_true')
|
|
14
|
+
parser.add_argument('--version', help='Show version', action='version', version=f'%(prog)s {__version__}')
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
dtl = DevToolsLoader(args.json_path, args.output_path, args.clean)
|
|
19
|
+
dtl.run()
|
|
20
|
+
|
|
21
|
+
except KeyboardInterrupt:
|
|
22
|
+
DebugLog.log(f'\n>>> Exit by user', color='yellow')
|
|
23
|
+
|
|
24
|
+
except ValueError as e:
|
|
25
|
+
DebugLog.log(f'\n>>> Value error: {e}', color='red')
|
|
26
|
+
|
|
27
|
+
except RuntimeError as e:
|
|
28
|
+
DebugLog.log(f'\n>>> Runtime error: {e}', color='red')
|
|
29
|
+
|
|
30
|
+
finally:
|
|
31
|
+
pass
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import time
|
|
6
|
+
import json
|
|
7
|
+
import runpy
|
|
8
|
+
import urllib.request
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
#----------------------------------------------------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
class DebugLog:
|
|
16
|
+
__colors = {
|
|
17
|
+
'black': '\033[30m',
|
|
18
|
+
'red': '\033[31m',
|
|
19
|
+
'green': '\033[32m',
|
|
20
|
+
'yellow': '\033[33m',
|
|
21
|
+
'blue': '\033[34m',
|
|
22
|
+
'magenta': '\033[35m',
|
|
23
|
+
'cyan': '\033[36m',
|
|
24
|
+
'white': '\033[37m',
|
|
25
|
+
'orange': '\033[38;5;214m',
|
|
26
|
+
'bright_black': '\033[90m',
|
|
27
|
+
'bright_red': '\033[91m',
|
|
28
|
+
'bright_green': '\033[92m',
|
|
29
|
+
'bright_yellow': '\033[93m',
|
|
30
|
+
'bright_blue': '\033[94m',
|
|
31
|
+
'bright_magenta': '\033[95m',
|
|
32
|
+
'bright_cyan': '\033[96m',
|
|
33
|
+
'bright_white': '\033[97m',
|
|
34
|
+
'gray_yellow': '\033[38;5;186m',
|
|
35
|
+
'reset': '\033[0m',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __supports_color() -> bool:
|
|
40
|
+
if not sys.stdout.isatty():
|
|
41
|
+
return False
|
|
42
|
+
if not sys.platform.startswith('win'):
|
|
43
|
+
return True
|
|
44
|
+
term = os.environ.get('TERM', '')
|
|
45
|
+
if 'xterm' in term or 'ansi' in term or 'cygwin' in term:
|
|
46
|
+
return True
|
|
47
|
+
if os.environ.get('ANSICON') or os.environ.get('WT_SESSION') or os.environ.get('TERM_PROGRAM', '') == 'vscode':
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__enable_colors = __supports_color()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def log(msg: str, color=None, end='\n', flush=False) -> None:
|
|
57
|
+
if color and DebugLog.__enable_colors:
|
|
58
|
+
print(DebugLog.__colors[color], msg, DebugLog.__colors['reset'], sep='', end=end, flush=flush)
|
|
59
|
+
else:
|
|
60
|
+
print(msg, end=end, flush=flush)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def new_line() -> None:
|
|
65
|
+
print()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def sep_by_sides(left_side: str, right_side: str = '', color=None, end='\n', flush=False) -> None:
|
|
70
|
+
width = shutil.get_terminal_size().columns
|
|
71
|
+
space = width - len(left_side) - len(right_side) - 1
|
|
72
|
+
if space > 0:
|
|
73
|
+
msg = f'\r{left_side:{len(left_side) + space}}{right_side}'
|
|
74
|
+
else:
|
|
75
|
+
short_left_side = left_side[:14] + '...' + left_side[14 + 3 + 2 - space:] + ' '
|
|
76
|
+
msg = f'\r{short_left_side}{right_side}'
|
|
77
|
+
DebugLog.log(msg, color=color, end=end, flush=flush)
|
|
78
|
+
|
|
79
|
+
#----------------------------------------------------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
class DevToolsLoader:
|
|
82
|
+
EXPECTED_JSON_VERSION = '0.1.0'
|
|
83
|
+
LOAD_RETRY_QTY = 5
|
|
84
|
+
|
|
85
|
+
PYTHON_PLATFORMS = [
|
|
86
|
+
'win32', # Windows 32-bit
|
|
87
|
+
'win_amd64', # Windows 64-bit
|
|
88
|
+
'win_arm64', # Windows ARM64
|
|
89
|
+
'manylinux1_x86_64', # Linux 64-bit (old)
|
|
90
|
+
'manylinux2010_x86_64', # Linux 64-bit
|
|
91
|
+
'manylinux2014_x86_64', # Linux 64-bit
|
|
92
|
+
'manylinux1_i686', # Linux 32-bit
|
|
93
|
+
'manylinux2010_i686', # Linux 32-bit
|
|
94
|
+
'manylinux2014_i686', # Linux 32-bit
|
|
95
|
+
'manylinux2014_aarch64', # Linux ARM64
|
|
96
|
+
'manylinux2014_armv7l', # Linux ARM32
|
|
97
|
+
'macosx_10_9_x86_64', # macOS Intel
|
|
98
|
+
'macosx_11_0_arm64', # macOS Apple Silicon
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
VSCODE_PLATFORMS = [
|
|
102
|
+
'win32-x64',
|
|
103
|
+
'win32-arm64',
|
|
104
|
+
'linux-x64',
|
|
105
|
+
'linux-arm64',
|
|
106
|
+
'linux-armhf',
|
|
107
|
+
'alpine-x64',
|
|
108
|
+
'alpine-arm64',
|
|
109
|
+
'darwin-x64',
|
|
110
|
+
'darwin-arm64',
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def __init__(self, json_path: Path, output_path: Path = None, clean: bool = False):
|
|
115
|
+
if json_path:
|
|
116
|
+
json_path = Path(json_path)
|
|
117
|
+
if json_path.exists():
|
|
118
|
+
with open(json_path, 'r', encoding='utf-8') as f:
|
|
119
|
+
raw_json = f.read()
|
|
120
|
+
raw_json = re.sub(r'.*//.*', '', raw_json)
|
|
121
|
+
raw_json = re.sub(r'/\*[\s\S]*?\*/', '', raw_json)
|
|
122
|
+
self.__config = json.loads(raw_json)
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError(f'JSON file \'{json_path}\' doesn\'t exist')
|
|
125
|
+
|
|
126
|
+
if self.__config['version'] != DevToolsLoader.EXPECTED_JSON_VERSION:
|
|
127
|
+
raise ValueError(f'incompatible JSON version - \'{self.__config["version"]}\', expected - \'{DevToolsLoader.EXPECTED_JSON_VERSION}\'')
|
|
128
|
+
else:
|
|
129
|
+
raise ValueError(f'json path required parameter')
|
|
130
|
+
|
|
131
|
+
if output_path:
|
|
132
|
+
self.__output_path = Path(output_path)
|
|
133
|
+
else:
|
|
134
|
+
self.__output_path = Path(os.getcwd()) / Path('output')
|
|
135
|
+
self.__clean = clean
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def __ver_2_tuple(ver_mmpb: str) -> Tuple[int]:
|
|
140
|
+
ver_mmp = ver_mmpb.split('-')[0]
|
|
141
|
+
return tuple(int(x) for x in ver_mmp.split('.'))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def __load_file(url: str, dest_path: Path, deep: int = 0) -> None:
|
|
146
|
+
dest_path = Path(dest_path)
|
|
147
|
+
temp_path = Path(str(dest_path) + '.part')
|
|
148
|
+
tree_str = ''
|
|
149
|
+
if deep > 0:
|
|
150
|
+
tree_str = '└' + (deep * '────')[1:-1] + ' '
|
|
151
|
+
left_side = f'{tree_str}{dest_path}'
|
|
152
|
+
|
|
153
|
+
if dest_path.exists():
|
|
154
|
+
DebugLog.sep_by_sides(left_side, 'EXISTS', color='bright_black', end='', flush=True)
|
|
155
|
+
else:
|
|
156
|
+
DebugLog.sep_by_sides(left_side, color='gray_yellow', end='', flush=True)
|
|
157
|
+
start_time = time.time()
|
|
158
|
+
for attempt in range(DevToolsLoader.LOAD_RETRY_QTY):
|
|
159
|
+
try:
|
|
160
|
+
with urllib.request.urlopen(url) as response, open(temp_path, 'wb') as out_file:
|
|
161
|
+
total_size = int(response.headers['Content-Length'], 0)
|
|
162
|
+
block_size = 128 * 1024
|
|
163
|
+
download_size = 0
|
|
164
|
+
while download_size < total_size:
|
|
165
|
+
chunk = response.read(block_size)
|
|
166
|
+
out_file.write(chunk)
|
|
167
|
+
download_size += len(chunk)
|
|
168
|
+
percent = download_size * 100 // total_size
|
|
169
|
+
minutes, seconds = divmod(int(time.time() - start_time), 60)
|
|
170
|
+
left_side = f'{tree_str}{dest_path}'
|
|
171
|
+
right_side = f'{percent}% {download_size // 1024:8}KB {minutes:02d}:{seconds:02d}'
|
|
172
|
+
DebugLog.sep_by_sides(left_side, right_side, color='gray_yellow' if percent < 100 else 'bright_green', end='', flush=True)
|
|
173
|
+
temp_path.rename(dest_path)
|
|
174
|
+
break
|
|
175
|
+
except (ConnectionResetError, urllib.error.URLError):
|
|
176
|
+
DebugLog.sep_by_sides(left_side, 'FAILED', color='red')
|
|
177
|
+
time.sleep(2)
|
|
178
|
+
else:
|
|
179
|
+
raise RuntimeError(f'failed to load \'{url}\'')
|
|
180
|
+
|
|
181
|
+
DebugLog.new_line()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def __load_python(self, cfg: dict):
|
|
185
|
+
# make dir
|
|
186
|
+
target_path = self.__output_path / Path(f'python-{cfg["platform"]}-{cfg["version"]}')
|
|
187
|
+
if self.__clean:
|
|
188
|
+
DebugLog.log(f'>>> Cleaning output \'{target_path}\'', color='bright_cyan')
|
|
189
|
+
shutil.rmtree(target_path, ignore_errors=True)
|
|
190
|
+
if not target_path.exists():
|
|
191
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
DebugLog.log(f'>>> Create dir \'{target_path}\'', color='bright_cyan')
|
|
193
|
+
|
|
194
|
+
# load installer
|
|
195
|
+
if cfg['installer'] == 'load':
|
|
196
|
+
try:
|
|
197
|
+
plat, arch = cfg['platform'].split('_')
|
|
198
|
+
arch = '-' + arch
|
|
199
|
+
except ValueError:
|
|
200
|
+
plat = cfg['platform']
|
|
201
|
+
arch = ''
|
|
202
|
+
if plat.startswith('win'):
|
|
203
|
+
ver = cfg['version']
|
|
204
|
+
file_name = f'python-{ver}{arch}.exe'
|
|
205
|
+
dest = target_path / Path(file_name)
|
|
206
|
+
url = f'https://www.python.org/ftp/python/{ver}/{file_name}'
|
|
207
|
+
DebugLog.log(f'>>> Loading python {ver} for platform {cfg["platform"]}', color='bright_cyan')
|
|
208
|
+
DevToolsLoader.__load_file(url, dest)
|
|
209
|
+
else:
|
|
210
|
+
DebugLog.log(f'>>> Load python for Linux is not supported yet', color='red')
|
|
211
|
+
# ver_major = ver.split('.')[0] + '.' + ver.split('.')[1]
|
|
212
|
+
# file_name = f'python{ver_major}_{ver}_{arch}.deb'
|
|
213
|
+
# dest = VSCODE_PATH / Path(file_name)
|
|
214
|
+
# url = f'https://deb.debian.org/debian/pool/main/p/python{ver_major}/{file_name}'
|
|
215
|
+
|
|
216
|
+
# load packages
|
|
217
|
+
if cfg.get('packages'):
|
|
218
|
+
for pkg in cfg['packages']:
|
|
219
|
+
pkg_name = pkg['name']
|
|
220
|
+
if pkg['version'] != 'latest':
|
|
221
|
+
pkg_name += f'=={pkg["version"]}'
|
|
222
|
+
|
|
223
|
+
pip_args = [
|
|
224
|
+
'pip', 'download', pkg_name,
|
|
225
|
+
'--only-binary=:all:', '-q', '--disable-pip-version-check',
|
|
226
|
+
'--python-version', cfg['version'],
|
|
227
|
+
'--platform', cfg['platform'],
|
|
228
|
+
'-d', str(target_path)
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
DebugLog.log(f'>>> Loading python package \'{pkg_name}\'', color='bright_cyan')
|
|
232
|
+
old_argv = sys.argv
|
|
233
|
+
sys.argv = pip_args
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
runpy.run_module('pip', run_name='__main__')
|
|
237
|
+
sys.argv
|
|
238
|
+
except SystemExit as e:
|
|
239
|
+
if e.code != 0:
|
|
240
|
+
raise RuntimeWarning(f'failed to install {pkg_name} for platform {cfg["platform"]}')
|
|
241
|
+
finally:
|
|
242
|
+
sys.argv = old_argv
|
|
243
|
+
|
|
244
|
+
#save setup script
|
|
245
|
+
setup = ''
|
|
246
|
+
setup_path = target_path / Path('setup' + ('.bat' if cfg["platform"].startswith('win') else '.sh'))
|
|
247
|
+
for root, dirs, files in os.walk(target_path):
|
|
248
|
+
for file in files:
|
|
249
|
+
if Path(file).suffix == '.whl':
|
|
250
|
+
setup += f'pip install --no-index --find-links=./ {file}\n'
|
|
251
|
+
DebugLog.log(f'>>> Save Python setup script to \'{setup_path}\'', color='bright_cyan')
|
|
252
|
+
with open(setup_path, 'w', encoding='utf-8') as f:
|
|
253
|
+
f.write(setup)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def __get_extension_info(uid: str, version: str, engine: str, platform: str) -> dict:
|
|
258
|
+
api_url = 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery'
|
|
259
|
+
|
|
260
|
+
query = {
|
|
261
|
+
'filters': [{ 'criteria': [{'filterType': 7, 'value': f'{uid}'}] }],
|
|
262
|
+
'flags': 0x1 | 0x2 | 0x10 | 0x80
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
headers = {
|
|
266
|
+
'Content-Type': 'application/json',
|
|
267
|
+
'Accept': 'application/json;api-version=3.0-preview.1',
|
|
268
|
+
'User-Agent': 'Offline VSIX/1.0'
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
data = json.dumps(query).encode('utf-8')
|
|
272
|
+
req = urllib.request.Request(api_url, data, headers)
|
|
273
|
+
|
|
274
|
+
for attempt in range(DevToolsLoader.LOAD_RETRY_QTY):
|
|
275
|
+
try:
|
|
276
|
+
with urllib.request.urlopen(req) as resp:
|
|
277
|
+
info_list: list[dict] = json.load(resp)['results'][0]['extensions'][0]['versions']
|
|
278
|
+
# response_path = DevToolsLoader.TEMP_PATH / Path('response')
|
|
279
|
+
# if not response_path.exists():
|
|
280
|
+
# response_path.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
# with open(response_path / Path(f'{uid}.json'), 'w', encoding='utf-8') as f:
|
|
282
|
+
# json.dump(info_list, f, indent=4)
|
|
283
|
+
# f.write('\n')
|
|
284
|
+
|
|
285
|
+
for info in info_list:
|
|
286
|
+
if info.get('targetPlatform') and platform is None:
|
|
287
|
+
raise RuntimeError(f'platform must be specified for {uid} platform')
|
|
288
|
+
|
|
289
|
+
if info.get('targetPlatform') is None or info['targetPlatform'] == platform:
|
|
290
|
+
for prop in info['properties']:
|
|
291
|
+
if prop['key'] == 'Microsoft.VisualStudio.Code.Engine':
|
|
292
|
+
ext_engine = prop['value'].replace('^', '')
|
|
293
|
+
break
|
|
294
|
+
else:
|
|
295
|
+
raise RuntimeError('failed to find Microsoft.VisualStudio.Code.Engine')
|
|
296
|
+
|
|
297
|
+
if engine == 'latest' or DevToolsLoader.__ver_2_tuple(ext_engine) <= DevToolsLoader.__ver_2_tuple(engine):
|
|
298
|
+
if version == 'latest' or version == info['version']:
|
|
299
|
+
return info
|
|
300
|
+
else:
|
|
301
|
+
raise RuntimeError(f'failed to find \'{uid}\' info')
|
|
302
|
+
except (ConnectionResetError, urllib.error.URLError):
|
|
303
|
+
time.sleep(2)
|
|
304
|
+
else:
|
|
305
|
+
raise RuntimeError(f'failed to connect \'{api_url}\'')
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def __load_vscode_extension_recursive(target_path: Path, uid: str, version: str, engine: str, platform: str, deep: int = 0, seen = None) -> None:
|
|
310
|
+
if seen is None:
|
|
311
|
+
seen = set()
|
|
312
|
+
|
|
313
|
+
if uid in seen:
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
seen.add(uid)
|
|
317
|
+
|
|
318
|
+
info = DevToolsLoader.__get_extension_info(uid, version, engine, platform)
|
|
319
|
+
for prop in info['files']:
|
|
320
|
+
if prop['assetType'] == 'Microsoft.VisualStudio.Services.VSIXPackage':
|
|
321
|
+
url = prop['source']
|
|
322
|
+
break
|
|
323
|
+
else:
|
|
324
|
+
raise RuntimeError(f'failed to find url')
|
|
325
|
+
|
|
326
|
+
#url = f'https://{uid.split('.')[0]}.gallery.vsassets.io/_apis/public/gallery/publisher/{uid.split('.')[0]}/extension/{uid.split('.')[1]}/{ver}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage'
|
|
327
|
+
#url = f'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/{publisher}/vsextensions/{name}/{ver}/vspackage'
|
|
328
|
+
|
|
329
|
+
ver = info['version']
|
|
330
|
+
plat = info.get('targetPlatform')
|
|
331
|
+
plat = f'-{plat}' if plat else ''
|
|
332
|
+
file_name = f'{uid}-{ver}{plat}.vsix'
|
|
333
|
+
dest = target_path / Path(file_name)
|
|
334
|
+
|
|
335
|
+
DevToolsLoader.__load_file(url, dest, deep)
|
|
336
|
+
|
|
337
|
+
for prop in info['properties']:
|
|
338
|
+
if prop['key'] == 'Microsoft.VisualStudio.Code.ExtensionDependencies' or prop['key'] == 'Microsoft.VisualStudio.Code.ExtensionPack':
|
|
339
|
+
for dep_uid in prop['value'].split(','):
|
|
340
|
+
if dep_uid != '':
|
|
341
|
+
DevToolsLoader.__load_vscode_extension_recursive(target_path, dep_uid, 'latest', engine, platform, deep + 1, seen)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def __load_vscode(self, cfg: dict) -> None:
|
|
345
|
+
# make dir
|
|
346
|
+
target_path = self.__output_path / Path(f'vscode-{cfg["platform"]}-{cfg["version"]}')
|
|
347
|
+
if self.__clean:
|
|
348
|
+
DebugLog.log(f'>>> Cleaning output \'{target_path}\'', color='bright_cyan')
|
|
349
|
+
shutil.rmtree(target_path, ignore_errors=True)
|
|
350
|
+
if not target_path.exists():
|
|
351
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
352
|
+
DebugLog.log(f'>>> Create dir \'{target_path}\'', color='bright_cyan')
|
|
353
|
+
|
|
354
|
+
# load installer
|
|
355
|
+
if cfg['installer'] == 'load':
|
|
356
|
+
plat = cfg['platform']
|
|
357
|
+
engine = cfg['version']
|
|
358
|
+
if plat.startswith('win'):
|
|
359
|
+
suffix = '.exe'
|
|
360
|
+
elif plat.startswith('darwin'):
|
|
361
|
+
suffix = '.zip'
|
|
362
|
+
elif plat.startswith('linux'):
|
|
363
|
+
suffix = '.tar.gz'
|
|
364
|
+
else:
|
|
365
|
+
suffix = None
|
|
366
|
+
DebugLog.log(f'>>> Load vscode for {plat} is not supported yet', color='red')
|
|
367
|
+
if suffix:
|
|
368
|
+
url_plat = plat if plat != 'darwin-x64' else 'darwin'
|
|
369
|
+
url = f'https://update.code.visualstudio.com/{engine}/{url_plat}/stable'
|
|
370
|
+
dest = target_path / Path(f'vscode-{engine}-{plat}{suffix}')
|
|
371
|
+
DebugLog.log(f'>>> Loading vscode {engine} for platform {plat}', color='bright_cyan')
|
|
372
|
+
DevToolsLoader.__load_file(url, dest)
|
|
373
|
+
|
|
374
|
+
# load extensions
|
|
375
|
+
if cfg.get('extensions'):
|
|
376
|
+
for ext in cfg['extensions']:
|
|
377
|
+
DevToolsLoader.__load_vscode_extension_recursive(target_path, ext['uid'], ext['version'], cfg['version'], cfg['platform'])
|
|
378
|
+
|
|
379
|
+
setup = ''
|
|
380
|
+
setup_path = target_path / Path('setup' + ('.bat' if cfg["platform"].startswith('win') else '.sh'))
|
|
381
|
+
for root, dirs, files in os.walk(target_path):
|
|
382
|
+
for file in files:
|
|
383
|
+
if Path(file).suffix == '.vsix':
|
|
384
|
+
setup += f'code --force --install-extension {file}\n'
|
|
385
|
+
DebugLog.log(f'>>> Save VSCode setup script to \'{setup_path}\'', color='bright_cyan')
|
|
386
|
+
with open(setup_path, 'w', encoding='utf-8') as f:
|
|
387
|
+
f.write(setup)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def run(self) -> None:
|
|
391
|
+
for cfg in self.__config['targets']:
|
|
392
|
+
if cfg['type'] == 'python':
|
|
393
|
+
self.__load_python(cfg)
|
|
394
|
+
elif cfg['type'] == 'vscode':
|
|
395
|
+
self.__load_vscode(cfg)
|
|
396
|
+
else:
|
|
397
|
+
raise ValueError(f'invalid target type \'{cfg["type"]}\'')
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dev_tools_loader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Development tools loader
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/karma_electronics/desktop/dev_tools_loader
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/karma_electronics/desktop/dev_tools_loader.git
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# Development Tools Loader
|
|
14
|
+
|
|
15
|
+
A CLI tool for automated downloading of development tools:
|
|
16
|
+
|
|
17
|
+
- VS Code installers and extensions (`.vsix` files).
|
|
18
|
+
- Python installers and pip packages;
|
|
19
|
+
|
|
20
|
+
The tool ensures version compatibility, handles dependencies, and supports repeatable configurations via JSON.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Key Features
|
|
24
|
+
|
|
25
|
+
- **Version control**: Ensures VS Code extension versions match the target VS Code engine version.
|
|
26
|
+
- **Package bundles**: Supports downloading multiple packages/extensions in a single config.
|
|
27
|
+
- **Dependency handling**: Automatically resolves and downloads dependencies for VS Code extensions.
|
|
28
|
+
- **Resilient downloads**: Automatically retries on connection loss.
|
|
29
|
+
- **Repeatable setups**: JSON-based configuration enables reproducible environment setups.
|
|
30
|
+
- **Flexible versioning**: Supports `"latest"` for extensions and packages.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install dev-tools-loader
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Running
|
|
41
|
+
|
|
42
|
+
Once installed, you can run the tool from the command line using the JSON configuration file.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
dev_tools_loader -j path/to/config.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Command‑Line Options
|
|
50
|
+
|
|
51
|
+
- **`-j`, `--json-path` *`<json_config_path>`*** **(required)** Specifies the path to the JSON configuration file that defines download targets.
|
|
52
|
+
- **`-o`, `--output-path` *`<output_dir>`*** Sets the output directory where downloaded files will be saved.
|
|
53
|
+
- **`-c`, `--clean`** If specified, deletes files in the target output directory before starting the download process.
|
|
54
|
+
- **`-h`, `--help`** Displays the help message with a summary of all available options and exits.
|
|
55
|
+
- **`--version`** Prints the current version of the `dev-tools-loader` package and exits.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Example Config
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"version": "0.1.0",
|
|
63
|
+
"targets": [
|
|
64
|
+
{
|
|
65
|
+
"type": "python",
|
|
66
|
+
"platform": "win_amd64",
|
|
67
|
+
"version": "3.12.0",
|
|
68
|
+
"installer": "load",
|
|
69
|
+
"packages": [
|
|
70
|
+
{
|
|
71
|
+
"name": "compiledb",
|
|
72
|
+
"version": "0.10.6"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "requests",
|
|
76
|
+
"version": "latest"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "vscode",
|
|
82
|
+
"platform": "win32-x64",
|
|
83
|
+
"version": "1.96.0",
|
|
84
|
+
"installer": "load",
|
|
85
|
+
"extensions": [
|
|
86
|
+
{
|
|
87
|
+
"uid": "ms-vscode.cpptools",
|
|
88
|
+
"version": "1.28.0"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"uid": "ms-python.python",
|
|
92
|
+
"version": "latest"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Configuration Fields
|
|
102
|
+
|
|
103
|
+
- `version` (str): Schema version.
|
|
104
|
+
- `targets` (list): List of download targets. Each target has:
|
|
105
|
+
- `type` (str): `"python"` or `"vscode"`.
|
|
106
|
+
- `platform` (str): Target platform (see supported platforms below).
|
|
107
|
+
- `version` (str): Version of the tool.
|
|
108
|
+
- `installer` (str): `"load"` to download installer or `"skip"`.
|
|
109
|
+
- `packages` (list, Python-only): List of pip packages to download.
|
|
110
|
+
- `name` (str): Package name.
|
|
111
|
+
- `version` (str): Package version (`"latest"` supported).
|
|
112
|
+
- `extensions` (list, VS Code-only): List of VS Code extensions to download.
|
|
113
|
+
- `uid` (str): Extension ID (e.g., `"ms-vscode.cpptools"`).
|
|
114
|
+
- `version` (str): Extension version (`"latest"` supported).
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Supported Platforms Python
|
|
118
|
+
|
|
119
|
+
- `win32`
|
|
120
|
+
- `win_amd64`
|
|
121
|
+
- `win_arm64`
|
|
122
|
+
- `manylinux1_x86_64`
|
|
123
|
+
- `manylinux2010_x86_64`
|
|
124
|
+
- `manylinux2014_x86_64`
|
|
125
|
+
- `manylinux1_i686`
|
|
126
|
+
- `manylinux2010_i686`
|
|
127
|
+
- `manylinux2014_i686`
|
|
128
|
+
- `manylinux2014_aarch64`
|
|
129
|
+
- `manylinux2014_armv7l`
|
|
130
|
+
- `macosx_10_9_x86_64`
|
|
131
|
+
- `macosx_11_0_arm64`
|
|
132
|
+
|
|
133
|
+
## Supported Platforms VS Code
|
|
134
|
+
|
|
135
|
+
- `win32-x64`
|
|
136
|
+
- `win32-arm64`
|
|
137
|
+
- `linux-x64`
|
|
138
|
+
- `linux-arm64`
|
|
139
|
+
- `linux-armhf`
|
|
140
|
+
- `alpine-x64`
|
|
141
|
+
- `alpine-arm64`
|
|
142
|
+
- `darwin-x64`
|
|
143
|
+
- `darwin-arm64`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
dev_tools_loader/__init__.py
|
|
5
|
+
dev_tools_loader/__main__.py
|
|
6
|
+
dev_tools_loader/cli.py
|
|
7
|
+
dev_tools_loader/dev_tools_loader.py
|
|
8
|
+
dev_tools_loader.egg-info/PKG-INFO
|
|
9
|
+
dev_tools_loader.egg-info/SOURCES.txt
|
|
10
|
+
dev_tools_loader.egg-info/dependency_links.txt
|
|
11
|
+
dev_tools_loader.egg-info/entry_points.txt
|
|
12
|
+
dev_tools_loader.egg-info/top_level.txt
|
|
13
|
+
tests/test_dev_tools_loader.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dev_tools_loader
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dev_tools_loader"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Development tools loader"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.6"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
dev_tools_loader = "dev_tools_loader.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
packages = ["dev_tools_loader"]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://gitlab.com/karma_electronics/desktop/dev_tools_loader"
|
|
23
|
+
Repository = "https://gitlab.com/karma_electronics/desktop/dev_tools_loader.git"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pytest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
from dev_tools_loader.dev_tools_loader import DevToolsLoader
|
|
7
|
+
from dev_tools_loader.cli import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def config_template():
|
|
12
|
+
return {
|
|
13
|
+
'version': '0.1.0',
|
|
14
|
+
'targets': [
|
|
15
|
+
{
|
|
16
|
+
'type': 'python',
|
|
17
|
+
'platform': 'none',
|
|
18
|
+
'version': '3.12.0',
|
|
19
|
+
'installer': "load",
|
|
20
|
+
'packages': [
|
|
21
|
+
{
|
|
22
|
+
'name': 'compiledb',
|
|
23
|
+
'version': '0.10.6'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
'name': 'pyserial',
|
|
27
|
+
'version': '3.2'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
'name': 'requests',
|
|
31
|
+
'version': 'latest'
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
'type': 'vscode',
|
|
37
|
+
'platform': 'none',
|
|
38
|
+
'version': '1.96.0',
|
|
39
|
+
'installer': "load",
|
|
40
|
+
'extensions': [
|
|
41
|
+
{
|
|
42
|
+
'uid': 'ms-vscode.cpptools',
|
|
43
|
+
'version': '1.28.0'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
'uid': 'ms-python.python',
|
|
47
|
+
'version': 'latest'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
'uid': 'ms-python.debugpy',
|
|
51
|
+
'version': 'latest'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
'uid': 'marus25.cortex-debug',
|
|
55
|
+
'version': '1.12.0'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
'uid': 'streetsidesoftware.code-spell-checker-russian',
|
|
59
|
+
'version': 'latest'
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def save_json_config(json_path: Path, config: dict) -> None:
|
|
68
|
+
json_path = Path(json_path)
|
|
69
|
+
with open(json_path, 'w', encoding='utf-8') as f:
|
|
70
|
+
json.dump(config, f, indent=4)
|
|
71
|
+
f.write('\n')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.fast
|
|
75
|
+
def test_cli_help():
|
|
76
|
+
print()
|
|
77
|
+
with patch('sys.argv', ['dev_tools_loader', '-h']):
|
|
78
|
+
with pytest.raises(SystemExit):
|
|
79
|
+
main()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.fast
|
|
83
|
+
def test_cli_version():
|
|
84
|
+
print()
|
|
85
|
+
with patch('sys.argv', ['dev_tools_loader', '--version']):
|
|
86
|
+
with pytest.raises(SystemExit):
|
|
87
|
+
main()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.fast
|
|
91
|
+
def test_cli_invalid_config():
|
|
92
|
+
print()
|
|
93
|
+
with patch('sys.argv', ['dev_tools_loader']):
|
|
94
|
+
with pytest.raises(SystemExit):
|
|
95
|
+
main()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.fast
|
|
99
|
+
def test_config_example(tmp_path):
|
|
100
|
+
print()
|
|
101
|
+
config_path = Path(__file__).parent / Path('data/config_example.json')
|
|
102
|
+
with patch('sys.argv', ['dev_tools_loader', '-j', str(config_path), '-o', str(tmp_path)]):
|
|
103
|
+
main()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize('platform', DevToolsLoader.PYTHON_PLATFORMS)
|
|
107
|
+
def test_python_load(config_template, platform, tmp_path):
|
|
108
|
+
config = config_template
|
|
109
|
+
config['targets'][0]['platform'] = platform
|
|
110
|
+
config['targets'].pop(1)
|
|
111
|
+
json_path = tmp_path / Path(f'config_python_{platform}.json')
|
|
112
|
+
save_json_config(json_path, config)
|
|
113
|
+
print()
|
|
114
|
+
dtl = DevToolsLoader(json_path, tmp_path)
|
|
115
|
+
dtl.run()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize('platform', DevToolsLoader.VSCODE_PLATFORMS)
|
|
119
|
+
def test_vscode_load(config_template, platform, tmp_path):
|
|
120
|
+
config = config_template
|
|
121
|
+
config['targets'][1]['platform'] = platform
|
|
122
|
+
config['targets'].pop(0)
|
|
123
|
+
json_path = tmp_path / Path(f'config_vscode_{platform}.json')
|
|
124
|
+
save_json_config(json_path, config)
|
|
125
|
+
print()
|
|
126
|
+
dtl = DevToolsLoader(json_path, tmp_path)
|
|
127
|
+
dtl.run()
|