projectdevsetup 1.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.
- projectdevsetup/__init__.py +11 -0
- projectdevsetup/__main__.py +31 -0
- projectdevsetup/detector.py +102 -0
- projectdevsetup/installer.py +447 -0
- projectdevsetup/network.py +80 -0
- projectdevsetup/path_manager.py +149 -0
- projectdevsetup/permissions.py +95 -0
- projectdevsetup/templates/Hello.java +8 -0
- projectdevsetup/templates/__init__.py +5 -0
- projectdevsetup/templates/hello.c +9 -0
- projectdevsetup/templates/hello.cpp +9 -0
- projectdevsetup/templates/hello.go +10 -0
- projectdevsetup/templates/hello.html +25 -0
- projectdevsetup/templates/hello.js +4 -0
- projectdevsetup/templates/hello.py +4 -0
- projectdevsetup/templates/hello.rs +6 -0
- projectdevsetup/utils/__init__.py +5 -0
- projectdevsetup/utils/logger.py +69 -0
- projectdevsetup/utils/os_detect.py +142 -0
- projectdevsetup/utils/venv_helper.py +95 -0
- projectdevsetup/vscode.py +386 -0
- projectdevsetup/wizard.py +262 -0
- projectdevsetup-1.0.0.dist-info/METADATA +83 -0
- projectdevsetup-1.0.0.dist-info/RECORD +27 -0
- projectdevsetup-1.0.0.dist-info/WHEEL +5 -0
- projectdevsetup-1.0.0.dist-info/licenses/LICENSE +22 -0
- projectdevsetup-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from projectdevsetup.wizard import run
|
|
9
|
+
from projectdevsetup.utils.logger import error
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
try:
|
|
14
|
+
run()
|
|
15
|
+
except KeyboardInterrupt:
|
|
16
|
+
print("\n")
|
|
17
|
+
error(
|
|
18
|
+
"Setup cancelled by user. Run 'python -m projectdevsetup' to start again."
|
|
19
|
+
)
|
|
20
|
+
sys.exit(0)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
error(
|
|
23
|
+
"Something unexpected went wrong. "
|
|
24
|
+
"Please report this issue at:\n"
|
|
25
|
+
" https://github.com/zenith-open-source/projectdevsetup/issues"
|
|
26
|
+
)
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
from projectdevsetup.utils.logger import already_installed, info
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_installed(command: str, version_flag: str = "--version") -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Check if a command-line tool is installed and working.
|
|
16
|
+
Returns True if tool exists and responds to version flag.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
if not shutil.which(command):
|
|
20
|
+
return False
|
|
21
|
+
result = subprocess.run(
|
|
22
|
+
[command, version_flag], capture_output=True, text=True, timeout=10
|
|
23
|
+
)
|
|
24
|
+
return result.returncode == 0
|
|
25
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_python() -> bool:
|
|
30
|
+
cmds = ["python3", "python"]
|
|
31
|
+
for cmd in cmds:
|
|
32
|
+
if is_installed(cmd):
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_gcc() -> bool:
|
|
38
|
+
return is_installed("gcc")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_gpp() -> bool:
|
|
42
|
+
return is_installed("g++")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_java() -> bool:
|
|
46
|
+
return is_installed("java", "-version")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def check_node() -> bool:
|
|
50
|
+
return is_installed("node")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_npm() -> bool:
|
|
54
|
+
return is_installed("npm")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_rust() -> bool:
|
|
58
|
+
return is_installed("rustc")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_cargo() -> bool:
|
|
62
|
+
return is_installed("cargo")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def check_go() -> bool:
|
|
66
|
+
return is_installed("go", "version")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def check_vscode() -> bool:
|
|
70
|
+
"""Check if VSCode (code command) is available."""
|
|
71
|
+
return bool(shutil.which("code"))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def check_winget() -> bool:
|
|
75
|
+
return is_installed("winget", "--version")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_brew() -> bool:
|
|
79
|
+
return is_installed("brew", "--version")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def check_snap() -> bool:
|
|
83
|
+
return is_installed("snap", "--version")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_installed_summary() -> dict:
|
|
87
|
+
"""Return a full summary of what is installed."""
|
|
88
|
+
return {
|
|
89
|
+
"python": check_python(),
|
|
90
|
+
"gcc": check_gcc(),
|
|
91
|
+
"gpp": check_gpp(),
|
|
92
|
+
"java": check_java(),
|
|
93
|
+
"node": check_node(),
|
|
94
|
+
"npm": check_npm(),
|
|
95
|
+
"rust": check_rust(),
|
|
96
|
+
"cargo": check_cargo(),
|
|
97
|
+
"go": check_go(),
|
|
98
|
+
"vscode": check_vscode(),
|
|
99
|
+
"winget": check_winget(),
|
|
100
|
+
"brew": check_brew(),
|
|
101
|
+
"snap": check_snap(),
|
|
102
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from projectdevsetup.detector import (
|
|
12
|
+
check_gcc,
|
|
13
|
+
check_gpp,
|
|
14
|
+
check_java,
|
|
15
|
+
check_node,
|
|
16
|
+
check_npm,
|
|
17
|
+
check_rust,
|
|
18
|
+
check_cargo,
|
|
19
|
+
check_go,
|
|
20
|
+
check_python,
|
|
21
|
+
)
|
|
22
|
+
from projectdevsetup.network import download_file, get_temp_dir
|
|
23
|
+
from projectdevsetup.path_manager import add_to_path, verify_in_path
|
|
24
|
+
from projectdevsetup.utils.logger import (
|
|
25
|
+
info,
|
|
26
|
+
success,
|
|
27
|
+
error,
|
|
28
|
+
warning,
|
|
29
|
+
already_installed,
|
|
30
|
+
step,
|
|
31
|
+
)
|
|
32
|
+
from projectdevsetup.utils.os_detect import detect_system, SystemInfo
|
|
33
|
+
|
|
34
|
+
URLS = {
|
|
35
|
+
"jdk_windows": "https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe",
|
|
36
|
+
"jdk_mac_arm": "https://download.oracle.com/java/21/latest/jdk-21_macos-aarch64_bin.dmg",
|
|
37
|
+
"jdk_mac_x64": "https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.dmg",
|
|
38
|
+
"node_windows": "https://nodejs.org/dist/latest/node-latest-x86.msi",
|
|
39
|
+
"go_windows": "https://go.dev/dl/go1.22.0.windows-amd64.msi",
|
|
40
|
+
"go_mac": "https://go.dev/dl/go1.22.0.darwin-amd64.pkg",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Installer:
|
|
45
|
+
def __init__(self):
|
|
46
|
+
self.sys_info: SystemInfo = detect_system()
|
|
47
|
+
|
|
48
|
+
def install_for_language(self, language: str) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
Install all required tools for a given language.
|
|
51
|
+
Returns True if installation succeeded.
|
|
52
|
+
"""
|
|
53
|
+
lang = language.lower()
|
|
54
|
+
handlers = {
|
|
55
|
+
"python": self._install_python,
|
|
56
|
+
"c": self._install_gcc,
|
|
57
|
+
"cpp": self._install_gcc,
|
|
58
|
+
"java": self._install_java,
|
|
59
|
+
"html": self._install_html,
|
|
60
|
+
"javascript": self._install_node,
|
|
61
|
+
"rust": self._install_rust,
|
|
62
|
+
"go": self._install_go,
|
|
63
|
+
}
|
|
64
|
+
handler = handlers.get(lang)
|
|
65
|
+
if handler:
|
|
66
|
+
return handler()
|
|
67
|
+
error(f"Unknown language: {language}")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def _install_python(self) -> bool:
|
|
71
|
+
if check_python():
|
|
72
|
+
already_installed("Python")
|
|
73
|
+
return True
|
|
74
|
+
info("Installing Python...")
|
|
75
|
+
os_name = self.sys_info.os_name
|
|
76
|
+
if os_name == "windows":
|
|
77
|
+
return self._install_python_windows()
|
|
78
|
+
elif os_name == "linux":
|
|
79
|
+
return self._install_via_package_manager(
|
|
80
|
+
["python3", "python3-pip", "python3-venv"], "Python"
|
|
81
|
+
)
|
|
82
|
+
elif os_name == "mac":
|
|
83
|
+
return self._install_via_brew("python3", "Python")
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def _install_python_windows(self) -> bool:
|
|
87
|
+
tmp = get_temp_dir()
|
|
88
|
+
arch = "amd64" if self.sys_info.is_64bit else "win32"
|
|
89
|
+
url = f"https://www.python.org/ftp/python/3.12.0/python-3.12.0-{arch}.exe"
|
|
90
|
+
installer = tmp / "python_installer.exe"
|
|
91
|
+
if not download_file(url, installer, "Python"):
|
|
92
|
+
return False
|
|
93
|
+
try:
|
|
94
|
+
subprocess.run(
|
|
95
|
+
[
|
|
96
|
+
str(installer),
|
|
97
|
+
"/quiet",
|
|
98
|
+
"InstallAllUsers=1",
|
|
99
|
+
"PrependPath=1",
|
|
100
|
+
"Include_test=0",
|
|
101
|
+
],
|
|
102
|
+
timeout=300,
|
|
103
|
+
)
|
|
104
|
+
if check_python():
|
|
105
|
+
success("Python installed successfully!")
|
|
106
|
+
return True
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
self._show_manual_install("Python", "https://www.python.org/downloads/")
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def _install_gcc(self) -> bool:
|
|
113
|
+
if check_gcc() and check_gpp():
|
|
114
|
+
already_installed("GCC / G++")
|
|
115
|
+
return True
|
|
116
|
+
info("Installing GCC and G++...")
|
|
117
|
+
os_name = self.sys_info.os_name
|
|
118
|
+
if os_name == "windows":
|
|
119
|
+
return self._install_gcc_windows()
|
|
120
|
+
elif os_name == "linux":
|
|
121
|
+
return self._install_via_package_manager(
|
|
122
|
+
["gcc", "g++", "build-essential"]
|
|
123
|
+
if self.sys_info.package_manager == "apt"
|
|
124
|
+
else ["gcc", "g++"],
|
|
125
|
+
"GCC",
|
|
126
|
+
)
|
|
127
|
+
elif os_name == "mac":
|
|
128
|
+
return self._install_xcode_cli_tools()
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def _install_gcc_windows(self) -> bool:
|
|
132
|
+
"""Install MSYS2 + GCC on Windows."""
|
|
133
|
+
info("Installing MSYS2 (includes GCC for Windows)...")
|
|
134
|
+
tmp = get_temp_dir()
|
|
135
|
+
arch = "x86_64" if self.sys_info.is_64bit else "i686"
|
|
136
|
+
url = (
|
|
137
|
+
f"https://github.com/msys2/msys2-installer/releases/download/"
|
|
138
|
+
f"2024-01-13/msys2-{arch}-20240113.exe"
|
|
139
|
+
)
|
|
140
|
+
installer = tmp / "msys2_installer.exe"
|
|
141
|
+
if not download_file(url, installer, "MSYS2 (GCC)"):
|
|
142
|
+
self._show_manual_install(
|
|
143
|
+
"GCC for Windows (MSYS2)", "https://www.msys2.org/"
|
|
144
|
+
)
|
|
145
|
+
return False
|
|
146
|
+
try:
|
|
147
|
+
subprocess.run(
|
|
148
|
+
[
|
|
149
|
+
str(installer),
|
|
150
|
+
"install",
|
|
151
|
+
"--root",
|
|
152
|
+
"C:\\msys64",
|
|
153
|
+
"--confirm-command",
|
|
154
|
+
],
|
|
155
|
+
timeout=600,
|
|
156
|
+
)
|
|
157
|
+
subprocess.run(
|
|
158
|
+
[
|
|
159
|
+
"C:\\msys64\\usr\\bin\\pacman.exe",
|
|
160
|
+
"-S",
|
|
161
|
+
"--noconfirm",
|
|
162
|
+
"mingw-w64-x86_64-gcc",
|
|
163
|
+
],
|
|
164
|
+
timeout=300,
|
|
165
|
+
)
|
|
166
|
+
add_to_path("C:\\msys64\\mingw64\\bin", "GCC")
|
|
167
|
+
if verify_in_path("gcc"):
|
|
168
|
+
success("GCC installed successfully!")
|
|
169
|
+
return True
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
self._show_manual_install("GCC (MSYS2)", "https://www.msys2.org/")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def _install_xcode_cli_tools(self) -> bool:
|
|
176
|
+
"""Install Xcode Command Line Tools on Mac (includes GCC)."""
|
|
177
|
+
info("Installing Xcode Command Line Tools (includes GCC)...")
|
|
178
|
+
try:
|
|
179
|
+
subprocess.run(["xcode-select", "--install"], timeout=30)
|
|
180
|
+
warning(
|
|
181
|
+
"A popup appeared asking to install developer tools. "
|
|
182
|
+
"Please click 'Install' and wait for it to finish. "
|
|
183
|
+
"Then run this tool again."
|
|
184
|
+
)
|
|
185
|
+
return False
|
|
186
|
+
except Exception:
|
|
187
|
+
self._show_manual_install(
|
|
188
|
+
"Xcode Command Line Tools", "https://developer.apple.com/xcode/"
|
|
189
|
+
)
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def _install_java(self) -> bool:
|
|
193
|
+
if check_java():
|
|
194
|
+
already_installed("Java JDK")
|
|
195
|
+
return True
|
|
196
|
+
info("Installing Java JDK 21 LTS...")
|
|
197
|
+
os_name = self.sys_info.os_name
|
|
198
|
+
if os_name == "windows":
|
|
199
|
+
return self._install_java_windows()
|
|
200
|
+
elif os_name == "linux":
|
|
201
|
+
return self._install_via_package_manager(
|
|
202
|
+
["default-jdk"]
|
|
203
|
+
if self.sys_info.package_manager == "apt"
|
|
204
|
+
else ["java-21-openjdk"],
|
|
205
|
+
"Java JDK",
|
|
206
|
+
)
|
|
207
|
+
elif os_name == "mac":
|
|
208
|
+
return self._install_via_brew("openjdk@21", "Java JDK 21")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
def _install_java_windows(self) -> bool:
|
|
212
|
+
tmp = get_temp_dir()
|
|
213
|
+
url = URLS["jdk_windows"]
|
|
214
|
+
installer = tmp / "jdk_installer.exe"
|
|
215
|
+
if not download_file(url, installer, "Java JDK 21"):
|
|
216
|
+
self._show_manual_install("Java JDK", "https://adoptium.net/")
|
|
217
|
+
return False
|
|
218
|
+
try:
|
|
219
|
+
subprocess.run([str(installer), "/s"], timeout=300)
|
|
220
|
+
warning(
|
|
221
|
+
"Java JDK installed. "
|
|
222
|
+
"Please restart your terminal for it to be available."
|
|
223
|
+
)
|
|
224
|
+
return True
|
|
225
|
+
except Exception:
|
|
226
|
+
self._show_manual_install("Java JDK", "https://adoptium.net/")
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def _install_html(self) -> bool:
|
|
230
|
+
success(
|
|
231
|
+
"HTML and CSS do not need any installation! "
|
|
232
|
+
"Just VSCode with Live Server extension is enough."
|
|
233
|
+
)
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
def _install_node(self) -> bool:
|
|
237
|
+
if check_node() and check_npm():
|
|
238
|
+
already_installed("Node.js and npm")
|
|
239
|
+
return True
|
|
240
|
+
info("Installing Node.js (includes npm)...")
|
|
241
|
+
os_name = self.sys_info.os_name
|
|
242
|
+
if os_name == "windows":
|
|
243
|
+
return self._install_node_windows()
|
|
244
|
+
elif os_name == "linux":
|
|
245
|
+
return self._install_node_linux()
|
|
246
|
+
elif os_name == "mac":
|
|
247
|
+
return self._install_via_brew("node", "Node.js")
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
def _install_node_windows(self) -> bool:
|
|
251
|
+
tmp = get_temp_dir()
|
|
252
|
+
arch = "x64" if self.sys_info.is_64bit else "x86"
|
|
253
|
+
url = f"https://nodejs.org/dist/latest/node-latest-{arch}.msi"
|
|
254
|
+
installer = tmp / "node_installer.msi"
|
|
255
|
+
if not download_file(url, installer, "Node.js"):
|
|
256
|
+
self._show_manual_install("Node.js", "https://nodejs.org/")
|
|
257
|
+
return False
|
|
258
|
+
try:
|
|
259
|
+
subprocess.run(["msiexec", "/i", str(installer), "/quiet"], timeout=300)
|
|
260
|
+
if check_node():
|
|
261
|
+
success("Node.js installed successfully!")
|
|
262
|
+
return True
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
self._show_manual_install("Node.js", "https://nodejs.org/")
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
def _install_node_linux(self) -> bool:
|
|
269
|
+
try:
|
|
270
|
+
setup = subprocess.run(
|
|
271
|
+
[
|
|
272
|
+
"bash",
|
|
273
|
+
"-c",
|
|
274
|
+
"curl -fsSL https://deb.nodesource.com/setup_lts.x"
|
|
275
|
+
" | sudo -E bash -",
|
|
276
|
+
],
|
|
277
|
+
timeout=120,
|
|
278
|
+
)
|
|
279
|
+
return self._install_via_package_manager(["nodejs"], "Node.js")
|
|
280
|
+
except Exception:
|
|
281
|
+
return self._install_via_package_manager(["nodejs", "npm"], "Node.js")
|
|
282
|
+
|
|
283
|
+
def _install_rust(self) -> bool:
|
|
284
|
+
if check_rust() and check_cargo():
|
|
285
|
+
already_installed("Rust and Cargo")
|
|
286
|
+
return True
|
|
287
|
+
info("Installing Rust via rustup...")
|
|
288
|
+
os_name = self.sys_info.os_name
|
|
289
|
+
if os_name == "windows":
|
|
290
|
+
return self._install_rust_windows()
|
|
291
|
+
else:
|
|
292
|
+
return self._install_rust_unix()
|
|
293
|
+
|
|
294
|
+
def _install_rust_windows(self) -> bool:
|
|
295
|
+
tmp = get_temp_dir()
|
|
296
|
+
url = "https://win.rustup.rs/x86_64"
|
|
297
|
+
installer = tmp / "rustup-init.exe"
|
|
298
|
+
if not download_file(url, installer, "Rust (rustup)"):
|
|
299
|
+
self._show_manual_install("Rust", "https://rustup.rs/")
|
|
300
|
+
return False
|
|
301
|
+
try:
|
|
302
|
+
subprocess.run([str(installer), "-y"], timeout=600)
|
|
303
|
+
cargo_bin = Path.home() / ".cargo" / "bin"
|
|
304
|
+
add_to_path(str(cargo_bin), "Rust/Cargo")
|
|
305
|
+
if check_rust():
|
|
306
|
+
success("Rust installed successfully!")
|
|
307
|
+
return True
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
self._show_manual_install("Rust", "https://rustup.rs/")
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
def _install_rust_unix(self) -> bool:
|
|
314
|
+
try:
|
|
315
|
+
subprocess.run(
|
|
316
|
+
[
|
|
317
|
+
"bash",
|
|
318
|
+
"-c",
|
|
319
|
+
"curl --proto '=https' --tlsv1.2 "
|
|
320
|
+
"-sSf https://sh.rustup.rs | sh -s -- -y",
|
|
321
|
+
],
|
|
322
|
+
timeout=600,
|
|
323
|
+
)
|
|
324
|
+
cargo_bin = Path.home() / ".cargo" / "bin"
|
|
325
|
+
add_to_path(str(cargo_bin), "Rust/Cargo")
|
|
326
|
+
if check_rust():
|
|
327
|
+
success("Rust installed successfully!")
|
|
328
|
+
return True
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
331
|
+
self._show_manual_install("Rust", "https://rustup.rs/")
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def _install_go(self) -> bool:
|
|
335
|
+
if check_go():
|
|
336
|
+
already_installed("Go")
|
|
337
|
+
return True
|
|
338
|
+
info("Installing Go...")
|
|
339
|
+
os_name = self.sys_info.os_name
|
|
340
|
+
if os_name == "windows":
|
|
341
|
+
return self._install_go_windows()
|
|
342
|
+
elif os_name == "linux":
|
|
343
|
+
return self._install_via_package_manager(["golang-go"], "Go")
|
|
344
|
+
elif os_name == "mac":
|
|
345
|
+
return self._install_via_brew("go", "Go")
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
def _install_go_windows(self) -> bool:
|
|
349
|
+
tmp = get_temp_dir()
|
|
350
|
+
arch = "amd64" if self.sys_info.is_64bit else "386"
|
|
351
|
+
url = f"https://go.dev/dl/go1.22.0.windows-{arch}.msi"
|
|
352
|
+
installer = tmp / "go_installer.msi"
|
|
353
|
+
if not download_file(url, installer, "Go"):
|
|
354
|
+
self._show_manual_install("Go", "https://go.dev/dl/")
|
|
355
|
+
return False
|
|
356
|
+
try:
|
|
357
|
+
subprocess.run(["msiexec", "/i", str(installer), "/quiet"], timeout=300)
|
|
358
|
+
add_to_path("C:\\Go\\bin", "Go")
|
|
359
|
+
if check_go():
|
|
360
|
+
success("Go installed successfully!")
|
|
361
|
+
return True
|
|
362
|
+
except Exception:
|
|
363
|
+
pass
|
|
364
|
+
self._show_manual_install("Go", "https://go.dev/dl/")
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
def _install_via_package_manager(self, packages: list, tool_name: str) -> bool:
|
|
368
|
+
"""Install packages using the system package manager."""
|
|
369
|
+
pm = self.sys_info.package_manager
|
|
370
|
+
pkg_commands = {
|
|
371
|
+
"apt": ["sudo", "apt-get", "install", "-y"],
|
|
372
|
+
"pacman": ["sudo", "pacman", "-S", "--noconfirm"],
|
|
373
|
+
"dnf": ["sudo", "dnf", "install", "-y"],
|
|
374
|
+
"yum": ["sudo", "yum", "install", "-y"],
|
|
375
|
+
"zypper": ["sudo", "zypper", "install", "-y"],
|
|
376
|
+
"apk": ["sudo", "apk", "add"],
|
|
377
|
+
}
|
|
378
|
+
cmd_prefix = pkg_commands.get(pm)
|
|
379
|
+
if not cmd_prefix:
|
|
380
|
+
warning(
|
|
381
|
+
f"No supported package manager found to install {tool_name}. "
|
|
382
|
+
"Please install it manually."
|
|
383
|
+
)
|
|
384
|
+
return False
|
|
385
|
+
try:
|
|
386
|
+
if pm == "apt":
|
|
387
|
+
subprocess.run(
|
|
388
|
+
["sudo", "apt-get", "update"], capture_output=True, timeout=60
|
|
389
|
+
)
|
|
390
|
+
result = subprocess.run(cmd_prefix + packages, timeout=300)
|
|
391
|
+
if result.returncode == 0:
|
|
392
|
+
success(f"{tool_name} installed successfully!")
|
|
393
|
+
return True
|
|
394
|
+
except subprocess.TimeoutExpired:
|
|
395
|
+
error(
|
|
396
|
+
f"Installation of {tool_name} took too long. "
|
|
397
|
+
"Please try again or install manually."
|
|
398
|
+
)
|
|
399
|
+
except Exception:
|
|
400
|
+
pass
|
|
401
|
+
self._show_manual_install(tool_name, "")
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
def _install_via_brew(self, formula: str, tool_name: str) -> bool:
|
|
405
|
+
"""Install via Homebrew on Mac."""
|
|
406
|
+
from projectdevsetup.detector import check_brew
|
|
407
|
+
|
|
408
|
+
if not check_brew():
|
|
409
|
+
warning("Homebrew is not installed. Installing Homebrew first...")
|
|
410
|
+
try:
|
|
411
|
+
subprocess.run(
|
|
412
|
+
[
|
|
413
|
+
"/bin/bash",
|
|
414
|
+
"-c",
|
|
415
|
+
"$(curl -fsSL https://raw.githubusercontent.com/"
|
|
416
|
+
"Homebrew/install/HEAD/install.sh)",
|
|
417
|
+
],
|
|
418
|
+
timeout=600,
|
|
419
|
+
)
|
|
420
|
+
except Exception:
|
|
421
|
+
self._show_manual_install("Homebrew", "https://brew.sh")
|
|
422
|
+
return False
|
|
423
|
+
try:
|
|
424
|
+
result = subprocess.run(["brew", "install", formula], timeout=300)
|
|
425
|
+
if result.returncode == 0:
|
|
426
|
+
success(f"{tool_name} installed successfully!")
|
|
427
|
+
return True
|
|
428
|
+
except Exception:
|
|
429
|
+
pass
|
|
430
|
+
self._show_manual_install(tool_name, "")
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
def _show_manual_install(self, tool_name: str, url: str):
|
|
434
|
+
"""Show a beginner-friendly message for manual install."""
|
|
435
|
+
warning(f"Could not install {tool_name} automatically.")
|
|
436
|
+
if url:
|
|
437
|
+
print(
|
|
438
|
+
f"\n Please download and install {tool_name} manually:\n"
|
|
439
|
+
f" {url}\n"
|
|
440
|
+
f"\n After installing, run this tool again.\n"
|
|
441
|
+
)
|
|
442
|
+
try:
|
|
443
|
+
import webbrowser
|
|
444
|
+
|
|
445
|
+
webbrowser.open(url)
|
|
446
|
+
except Exception:
|
|
447
|
+
pass
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import urllib.request
|
|
8
|
+
import urllib.error
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from projectdevsetup.utils.logger import info, error, warning
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_internet() -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Check if internet is available.
|
|
18
|
+
Tries to reach multiple hosts in case one is down.
|
|
19
|
+
"""
|
|
20
|
+
hosts = [
|
|
21
|
+
"https://www.google.com",
|
|
22
|
+
"https://www.cloudflare.com",
|
|
23
|
+
"https://www.microsoft.com",
|
|
24
|
+
]
|
|
25
|
+
for host in hosts:
|
|
26
|
+
try:
|
|
27
|
+
urllib.request.urlopen(host, timeout=5)
|
|
28
|
+
return True
|
|
29
|
+
except Exception:
|
|
30
|
+
continue
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def download_file(
|
|
35
|
+
url: str, destination: Path, description: str, retries: int = 3
|
|
36
|
+
) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Download a file with retry logic and progress indication.
|
|
39
|
+
Returns True on success, False on failure.
|
|
40
|
+
Shows beginner-friendly messages throughout.
|
|
41
|
+
"""
|
|
42
|
+
for attempt in range(1, retries + 1):
|
|
43
|
+
try:
|
|
44
|
+
info(f"Downloading {description}... (attempt {attempt}/{retries})")
|
|
45
|
+
|
|
46
|
+
def progress_hook(count, block_size, total_size):
|
|
47
|
+
if total_size > 0:
|
|
48
|
+
percent = min(int(count * block_size * 100 / total_size), 100)
|
|
49
|
+
print(f"\r Progress: {percent}% ", end="", flush=True)
|
|
50
|
+
|
|
51
|
+
urllib.request.urlretrieve(url, str(destination), reporthook=progress_hook)
|
|
52
|
+
print()
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
except urllib.error.URLError:
|
|
56
|
+
warning(
|
|
57
|
+
f"Download attempt {attempt} failed. "
|
|
58
|
+
f"Checking your internet connection..."
|
|
59
|
+
)
|
|
60
|
+
if not check_internet():
|
|
61
|
+
error(
|
|
62
|
+
"No internet connection detected. "
|
|
63
|
+
"Please connect to the internet and try again."
|
|
64
|
+
)
|
|
65
|
+
return False
|
|
66
|
+
except Exception as e:
|
|
67
|
+
warning(f"Download attempt {attempt} failed. Retrying...")
|
|
68
|
+
|
|
69
|
+
error(
|
|
70
|
+
f"Could not download {description} after {retries} attempts. "
|
|
71
|
+
"Please check your internet connection and try again."
|
|
72
|
+
)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_temp_dir() -> Path:
|
|
77
|
+
"""Return a safe temp directory for downloads."""
|
|
78
|
+
tmp = Path(tempfile.gettempdir()) / "projectdevsetup"
|
|
79
|
+
tmp.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
return tmp
|