lmms 1.1.7__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.
- lmms-1.1.7.dist-info/METADATA +13 -0
- lmms-1.1.7.dist-info/RECORD +10 -0
- lmms-1.1.7.dist-info/WHEEL +5 -0
- lmms-1.1.7.dist-info/entry_points.txt +4 -0
- lmms-1.1.7.dist-info/top_level.txt +1 -0
- lmms_builder/__init__.py +3 -0
- lmms_builder/cli.py +136 -0
- lmms_builder/hardware.py +106 -0
- lmms_builder/installer.py +341 -0
- lmms_builder/reporter.py +101 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lmms
|
|
3
|
+
Version: 1.1.7
|
|
4
|
+
Summary: Builder, hardware profiler, and toolchain manager for the LMMs Ecosystem.
|
|
5
|
+
Author: MarkanM
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Requires-Dist: psutil
|
|
8
|
+
Requires-Dist: rich
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
lmms_builder/__init__.py,sha256=s4WXUCdtZenBWJdOJj8FZE-SsdNjqm_9x6igsTb_quM,52
|
|
2
|
+
lmms_builder/cli.py,sha256=CLfuR3X_hVT6ryxMRH_oGBy9fgsV2xM1dKcHNlOVU0I,5575
|
|
3
|
+
lmms_builder/hardware.py,sha256=R6d85tn3PCOQYgj2vMzmFE8iz7nCO9s_OzO8mBZnwSo,3092
|
|
4
|
+
lmms_builder/installer.py,sha256=wqNT1BAEKx_hjlLmM05hErTLlYLmdkCF4SdUfT0bsBU,13779
|
|
5
|
+
lmms_builder/reporter.py,sha256=AqYfzThEbDio_ftJ5Q2fo9isd_YFFjTsXckC-kLug4A,3898
|
|
6
|
+
lmms-1.1.7.dist-info/METADATA,sha256=KMn5d-UaekkTc5up7nUzB9GgjU7G8acVYRou4i7x-d8,317
|
|
7
|
+
lmms-1.1.7.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
lmms-1.1.7.dist-info/entry_points.txt,sha256=BpRHTzaz0SeFuDpI_5OO1bB1IsREzlfY_mpXeHOSJok,141
|
|
9
|
+
lmms-1.1.7.dist-info/top_level.txt,sha256=CvyaNnnSTryMQZoYWbnMFuzmbwXLwjS8KWanpLg0kl0,13
|
|
10
|
+
lmms-1.1.7.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lmms_builder
|
lmms_builder/__init__.py
ADDED
lmms_builder/cli.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import platform
|
|
6
|
+
import subprocess
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import hardware
|
|
11
|
+
from . import reporter
|
|
12
|
+
from . import installer
|
|
13
|
+
|
|
14
|
+
LMMS_CONFIG_DIR = Path(os.path.expanduser("~/.lmms"))
|
|
15
|
+
LMMS_PROJECT_DIR = Path(os.path.expanduser("~/.lmms/app/src"))
|
|
16
|
+
LMMS_APP_DIR = Path(os.path.expanduser("~/.lmms/app"))
|
|
17
|
+
|
|
18
|
+
def print_help():
|
|
19
|
+
reporter.print_header()
|
|
20
|
+
print("\n[bold yellow]=== LMMs Complete Command List ===[/bold yellow]")
|
|
21
|
+
print("\n[bold cyan]1) Installer / Updater[/bold cyan]")
|
|
22
|
+
print(" LMMs -check : Automatically detects OS, hardware compatibility, and installed LMMs components.")
|
|
23
|
+
print(" LMMs install --all : Downloads and installs the entire LMMs ecosystem.")
|
|
24
|
+
print(" LMMs update --all : Forces a download of the latest code/binary version from GitHub Releases.")
|
|
25
|
+
|
|
26
|
+
print("\n[bold cyan]2) Uninstall[/bold cyan]")
|
|
27
|
+
print(" LMMs-uninstall -all : Deletes the LMMs source code but keeps Pip packages and User Data safe.")
|
|
28
|
+
print(" LMMs-uninstall -all --purge : Completely removes ALL LMMs code, pip environments, and user data (Factory Reset).")
|
|
29
|
+
|
|
30
|
+
print("\n[bold cyan]3) Launch Options[/bold cyan]")
|
|
31
|
+
print(" LMMs --gui|--cli|--engine : Directly runs the specified component overriding the default.")
|
|
32
|
+
print(" LMMs : Runs the default configured component.\n")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def builder_main():
|
|
36
|
+
args_lower = [arg.lower() for arg in sys.argv]
|
|
37
|
+
if "-check" in args_lower:
|
|
38
|
+
reporter.print_step("Running LMMs System Check...")
|
|
39
|
+
hw = hardware.detect_all()
|
|
40
|
+
reporter.print_hardware_report(hw)
|
|
41
|
+
installed = installer.get_installed_components()
|
|
42
|
+
reporter.print_step("Installed Components:")
|
|
43
|
+
for k, v in installed.items():
|
|
44
|
+
if v:
|
|
45
|
+
print(f" - {k.capitalize()}")
|
|
46
|
+
if not any(installed.values()):
|
|
47
|
+
print(" - None")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if "install" in args_lower or "update" in args_lower:
|
|
51
|
+
force_update = "update" in args_lower
|
|
52
|
+
if "--all" in args_lower:
|
|
53
|
+
installer.install_component("all", force=force_update)
|
|
54
|
+
else:
|
|
55
|
+
print("Usage: LMMs install --all")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
print_help()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def uninstall_main():
|
|
62
|
+
install_dir = Path.home() / ".lmms" / "bin" if platform.system().lower() == "windows" else Path("/usr/local/bin")
|
|
63
|
+
fallback_dir = Path.home() / ".local" / "bin"
|
|
64
|
+
|
|
65
|
+
args_lower = [arg.lower() for arg in sys.argv]
|
|
66
|
+
if "-all" not in args_lower:
|
|
67
|
+
print("Usage: LMMs-uninstall -all [--purge]")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if "--purge" in args_lower:
|
|
71
|
+
ans = input("WARNING: This will PURGE LMMs code, Python environments, and ALL user data (models, chats). Continue? [y/N]: ")
|
|
72
|
+
if ans.lower() == 'y':
|
|
73
|
+
shutil.rmtree(LMMS_APP_DIR, ignore_errors=True)
|
|
74
|
+
shutil.rmtree(LMMS_CONFIG_DIR, ignore_errors=True)
|
|
75
|
+
|
|
76
|
+
# Remove global binaries
|
|
77
|
+
bin_name = "lmms.exe" if platform.system().lower() == "windows" else "lmms"
|
|
78
|
+
(install_dir / bin_name).unlink(missing_ok=True)
|
|
79
|
+
(fallback_dir / bin_name).unlink(missing_ok=True)
|
|
80
|
+
reporter.print_success("Purge complete. All LMMs files and data have been removed.")
|
|
81
|
+
else:
|
|
82
|
+
ans = input("INFO: This will delete the LMMs source code but KEEP your Python environment and User Data safe. Continue? [y/N]: ")
|
|
83
|
+
if ans.lower() == 'y':
|
|
84
|
+
src_dir = LMMS_APP_DIR / "src"
|
|
85
|
+
shutil.rmtree(src_dir, ignore_errors=True)
|
|
86
|
+
|
|
87
|
+
# Mark uninstalled
|
|
88
|
+
installed = installer.get_installed_components()
|
|
89
|
+
installed["all"] = False
|
|
90
|
+
with open(installer.get_registry_path(), "w") as f:
|
|
91
|
+
json.dump(installed, f)
|
|
92
|
+
reporter.print_success("Code uninstalled. Pip packages and User Data remain safe.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def lmms_main():
|
|
96
|
+
args_lower = [arg.lower() for arg in sys.argv]
|
|
97
|
+
mode = None
|
|
98
|
+
if "--gui" in args_lower: mode = "gui"
|
|
99
|
+
elif "--cli" in args_lower: mode = "cli"
|
|
100
|
+
elif "--engine" in args_lower: mode = "engine"
|
|
101
|
+
else:
|
|
102
|
+
# Load default
|
|
103
|
+
config_path = LMMS_CONFIG_DIR / "config" / "config.json"
|
|
104
|
+
if config_path.exists():
|
|
105
|
+
with open(config_path, "r") as f:
|
|
106
|
+
try: mode = json.load(f).get("default_mode", "cli")
|
|
107
|
+
except: mode = "cli"
|
|
108
|
+
else:
|
|
109
|
+
mode = "cli"
|
|
110
|
+
|
|
111
|
+
# Always use the launcher script through the VENV python
|
|
112
|
+
venv_python = LMMS_APP_DIR / "venv" / "Scripts" / "python.exe" if platform.system().lower() == "windows" else LMMS_APP_DIR / "venv" / "bin" / "python"
|
|
113
|
+
launcher_script = LMMS_PROJECT_DIR / "lmms_launcher.pyc"
|
|
114
|
+
if not launcher_script.exists():
|
|
115
|
+
launcher_script = LMMS_PROJECT_DIR / "lmms_launcher.py"
|
|
116
|
+
|
|
117
|
+
if launcher_script.exists():
|
|
118
|
+
cmd = [str(venv_python), str(launcher_script)]
|
|
119
|
+
if mode and mode != "cli":
|
|
120
|
+
cmd.append(mode)
|
|
121
|
+
|
|
122
|
+
forward_args = [a for a in sys.argv[1:] if a not in ["--gui", "--cli", "--engine"]]
|
|
123
|
+
cmd.extend(forward_args)
|
|
124
|
+
|
|
125
|
+
os.execvp(str(venv_python), cmd)
|
|
126
|
+
else:
|
|
127
|
+
print("LMMs launcher not found. Please run: LMMs install --all")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def unified_main():
|
|
131
|
+
"""Entry point for the 'LMMs' command."""
|
|
132
|
+
args_lower = [arg.lower() for arg in sys.argv]
|
|
133
|
+
if len(args_lower) > 1 and args_lower[1] in ["install", "update", "-check", "-help", "--help"]:
|
|
134
|
+
builder_main()
|
|
135
|
+
else:
|
|
136
|
+
lmms_main()
|
lmms_builder/hardware.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import psutil
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
def detect_os():
|
|
7
|
+
return {
|
|
8
|
+
"system": platform.system(),
|
|
9
|
+
"release": platform.release(),
|
|
10
|
+
"architecture": platform.machine(),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def detect_cpu():
|
|
14
|
+
return {
|
|
15
|
+
"cores": psutil.cpu_count(logical=False),
|
|
16
|
+
"threads": psutil.cpu_count(logical=True),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def detect_ram():
|
|
20
|
+
mem = psutil.virtual_memory()
|
|
21
|
+
return {
|
|
22
|
+
"total_gb": round(mem.total / (1024**3), 2),
|
|
23
|
+
"available_gb": round(mem.available / (1024**3), 2)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def detect_gpu():
|
|
27
|
+
try:
|
|
28
|
+
# Check nvidia-smi
|
|
29
|
+
result = subprocess.run(
|
|
30
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"],
|
|
31
|
+
capture_output=True, text=True, check=True
|
|
32
|
+
)
|
|
33
|
+
lines = result.stdout.strip().split("\n")
|
|
34
|
+
if lines and lines[0]:
|
|
35
|
+
parts = lines[0].split(",")
|
|
36
|
+
name = parts[0].strip()
|
|
37
|
+
vram_str = parts[1].strip()
|
|
38
|
+
vram_mb = int(vram_str.replace(" MiB", ""))
|
|
39
|
+
return {
|
|
40
|
+
"detected": True,
|
|
41
|
+
"name": name,
|
|
42
|
+
"vram_gb": round(vram_mb / 1024, 2),
|
|
43
|
+
"type": "nvidia"
|
|
44
|
+
}
|
|
45
|
+
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"detected": False,
|
|
50
|
+
"name": "None",
|
|
51
|
+
"vram_gb": 0.0,
|
|
52
|
+
"type": "none"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def detect_cuda():
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(["nvcc", "--version"], capture_output=True, text=True, check=True)
|
|
58
|
+
for line in result.stdout.split("\n"):
|
|
59
|
+
if "release" in line:
|
|
60
|
+
version = line.split("release")[1].strip().split(",")[0]
|
|
61
|
+
return {"available": True, "version": version}
|
|
62
|
+
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
result = subprocess.run(["nvidia-smi"], capture_output=True, text=True, check=True)
|
|
67
|
+
if "CUDA Version: " in result.stdout:
|
|
68
|
+
version = result.stdout.split("CUDA Version: ")[1].split(" ")[0]
|
|
69
|
+
return {"available": True, "version": version}
|
|
70
|
+
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
return {"available": False, "version": None}
|
|
74
|
+
|
|
75
|
+
def detect_python():
|
|
76
|
+
return {
|
|
77
|
+
"version": platform.python_version()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
import shutil
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
|
|
83
|
+
def detect_tools():
|
|
84
|
+
system = platform.system().lower()
|
|
85
|
+
vscode = shutil.which("code") is not None
|
|
86
|
+
bt = False
|
|
87
|
+
if system == "windows":
|
|
88
|
+
bt_path = Path("C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools")
|
|
89
|
+
bt_path2 = Path("C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools")
|
|
90
|
+
bt = bt_path.exists() or bt_path2.exists()
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"vscode": vscode,
|
|
94
|
+
"build_tools": bt
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def detect_all():
|
|
98
|
+
return {
|
|
99
|
+
"os": detect_os(),
|
|
100
|
+
"cpu": detect_cpu(),
|
|
101
|
+
"ram": detect_ram(),
|
|
102
|
+
"gpu": detect_gpu(),
|
|
103
|
+
"cuda": detect_cuda(),
|
|
104
|
+
"python": detect_python(),
|
|
105
|
+
"tools": detect_tools()
|
|
106
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import zipfile
|
|
8
|
+
import requests
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from rich.progress import Progress, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
|
|
11
|
+
from . import reporter
|
|
12
|
+
|
|
13
|
+
def check_python_version():
|
|
14
|
+
return sys.version_info >= (3, 9)
|
|
15
|
+
|
|
16
|
+
def check_prerequisites():
|
|
17
|
+
system = platform.system().lower()
|
|
18
|
+
missing_tools = []
|
|
19
|
+
|
|
20
|
+
reporter.print_step("Checking prerequisites...")
|
|
21
|
+
|
|
22
|
+
# Check VS Code
|
|
23
|
+
if not shutil.which("code"):
|
|
24
|
+
missing_tools.append(("Visual Studio Code", "Microsoft.VisualStudioCode"))
|
|
25
|
+
|
|
26
|
+
# Check Build Tools (Windows only)
|
|
27
|
+
if system == "windows":
|
|
28
|
+
bt_path = Path("C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools")
|
|
29
|
+
bt_path2 = Path("C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools")
|
|
30
|
+
if not bt_path.exists() and not bt_path2.exists():
|
|
31
|
+
missing_tools.append(("Visual Studio Build Tools (C++)", "Microsoft.VisualStudio.2022.BuildTools"))
|
|
32
|
+
|
|
33
|
+
if missing_tools:
|
|
34
|
+
reporter.print_error("The following required tools are missing:")
|
|
35
|
+
for name, _ in missing_tools:
|
|
36
|
+
print(f" - {name}")
|
|
37
|
+
|
|
38
|
+
ans = input("Would you like to automatically install them now? [y/N]: ")
|
|
39
|
+
if ans.lower() == 'y':
|
|
40
|
+
for name, pkg_id in missing_tools:
|
|
41
|
+
reporter.print_step(f"Installing {name}...")
|
|
42
|
+
if system == "windows":
|
|
43
|
+
try:
|
|
44
|
+
subprocess.run(["winget", "install", "-e", "--id", pkg_id, "--accept-package-agreements", "--accept-source-agreements"], check=True)
|
|
45
|
+
reporter.print_success(f"✓ {name} installed.")
|
|
46
|
+
except:
|
|
47
|
+
reporter.print_error(f"Failed to install {name} via winget. Please install it manually.")
|
|
48
|
+
else:
|
|
49
|
+
reporter.print_error("Auto-install for this tool is only supported on Windows via winget.")
|
|
50
|
+
else:
|
|
51
|
+
reporter.print_error("Please install them manually to avoid issues.")
|
|
52
|
+
else:
|
|
53
|
+
reporter.print_success("✓ All prerequisites are installed.")
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def get_registry_path():
|
|
57
|
+
p = Path(os.path.expanduser("~/.lmms/config/installed_components.json"))
|
|
58
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
return p
|
|
60
|
+
|
|
61
|
+
def get_installed_components():
|
|
62
|
+
reg = get_registry_path()
|
|
63
|
+
if reg.exists():
|
|
64
|
+
try:
|
|
65
|
+
with open(reg, "r") as f:
|
|
66
|
+
return json.load(f)
|
|
67
|
+
except:
|
|
68
|
+
pass
|
|
69
|
+
return {}
|
|
70
|
+
|
|
71
|
+
def mark_installed(component):
|
|
72
|
+
installed = get_installed_components()
|
|
73
|
+
installed[component] = True
|
|
74
|
+
with open(get_registry_path(), "w") as f:
|
|
75
|
+
json.dump(installed, f)
|
|
76
|
+
|
|
77
|
+
def get_app_dir():
|
|
78
|
+
return Path.home() / ".lmms" / "app"
|
|
79
|
+
|
|
80
|
+
def get_venv_python():
|
|
81
|
+
system = platform.system().lower()
|
|
82
|
+
app_dir = get_app_dir()
|
|
83
|
+
if system == "windows":
|
|
84
|
+
return app_dir / "venv" / "Scripts" / "python.exe"
|
|
85
|
+
return app_dir / "venv" / "bin" / "python"
|
|
86
|
+
|
|
87
|
+
def install_python_os(system):
|
|
88
|
+
reporter.print_step("Attempting to auto-install Python...")
|
|
89
|
+
try:
|
|
90
|
+
if system == "linux":
|
|
91
|
+
reporter.print_step("Using apt to install Python 3.11... (You may be prompted for sudo password)")
|
|
92
|
+
subprocess.run(["sudo", "apt", "update"], check=True)
|
|
93
|
+
subprocess.run(["sudo", "apt", "install", "-y", "python3.11", "python3.11-venv"], check=True)
|
|
94
|
+
return True
|
|
95
|
+
elif system == "darwin":
|
|
96
|
+
reporter.print_step("Using brew to install Python 3.11...")
|
|
97
|
+
subprocess.run(["brew", "install", "python@3.11"], check=True)
|
|
98
|
+
return True
|
|
99
|
+
elif system == "windows":
|
|
100
|
+
reporter.print_step("Using winget to install Python 3.11...")
|
|
101
|
+
subprocess.run(["winget", "install", "-e", "--id", "Python.Python.3.11", "--accept-package-agreements", "--accept-source-agreements"], check=True)
|
|
102
|
+
return True
|
|
103
|
+
except subprocess.CalledProcessError as e:
|
|
104
|
+
reporter.print_error(f"Failed to auto-install Python: {e}")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def ensure_stable_python():
|
|
108
|
+
system = platform.system().lower()
|
|
109
|
+
# Check if a stable version (3.11 or 3.12) exists
|
|
110
|
+
for py_cmd in ["python3.11", "python3.12", "python3.10"]:
|
|
111
|
+
cmd_path = shutil.which(py_cmd)
|
|
112
|
+
if cmd_path:
|
|
113
|
+
reporter.print_success(f"✓ Found compatible stable Python at {cmd_path}")
|
|
114
|
+
return cmd_path
|
|
115
|
+
|
|
116
|
+
# Fallback to general 'python3' or 'python' and check version
|
|
117
|
+
for py_cmd in ["python3", "python"]:
|
|
118
|
+
cmd_path = shutil.which(py_cmd)
|
|
119
|
+
if cmd_path:
|
|
120
|
+
try:
|
|
121
|
+
output = subprocess.check_output([cmd_path, "--version"], text=True, stderr=subprocess.STDOUT)
|
|
122
|
+
if "Python 3.11" in output or "Python 3.12" in output or "Python 3.10" in output:
|
|
123
|
+
reporter.print_success(f"✓ Found compatible stable Python at {cmd_path}")
|
|
124
|
+
return cmd_path
|
|
125
|
+
except Exception:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Not found!
|
|
129
|
+
reporter.print_error("Warning: A highly compatible Python version (3.11 or 3.12) was NOT found on your system!")
|
|
130
|
+
reporter.print_error("Llama-cpp-python and GPU acceleration run best on Python 3.11/3.12. Using Python 3.13 may cause build failures.")
|
|
131
|
+
try:
|
|
132
|
+
ans = input("Would you like me to attempt to auto-download and install Python 3.11 now? (y/n): ").strip().lower()
|
|
133
|
+
except EOFError:
|
|
134
|
+
ans = "n"
|
|
135
|
+
|
|
136
|
+
if ans == "y":
|
|
137
|
+
if install_python_os(system):
|
|
138
|
+
# Check again
|
|
139
|
+
for py_cmd in ["python3.11", "python3.12"]:
|
|
140
|
+
cmd_path = shutil.which(py_cmd)
|
|
141
|
+
if cmd_path:
|
|
142
|
+
reporter.print_success(f"✓ Successfully installed and found Python at {cmd_path}")
|
|
143
|
+
return cmd_path
|
|
144
|
+
reporter.print_error("Python installation finished but executable not found in PATH. You may need to restart your terminal.")
|
|
145
|
+
else:
|
|
146
|
+
reporter.print_error("Auto-installation failed.")
|
|
147
|
+
|
|
148
|
+
reporter.print_step("Falling back to current Python executable (this may be 3.13 and might cause issues)...")
|
|
149
|
+
return sys.executable
|
|
150
|
+
|
|
151
|
+
def setup_venv(python_path):
|
|
152
|
+
app_dir = get_app_dir()
|
|
153
|
+
venv_dir = app_dir / "venv"
|
|
154
|
+
if not venv_dir.exists():
|
|
155
|
+
reporter.print_step(f"Creating isolated Virtual Environment at {venv_dir}...")
|
|
156
|
+
try:
|
|
157
|
+
subprocess.run([python_path, "-m", "venv", str(venv_dir)], check=True)
|
|
158
|
+
reporter.print_success("✓ Virtual Environment created successfully.")
|
|
159
|
+
except subprocess.CalledProcessError as e:
|
|
160
|
+
reporter.print_error(f"Failed to create VENV: {e}")
|
|
161
|
+
return False
|
|
162
|
+
else:
|
|
163
|
+
reporter.print_success("✓ Virtual Environment already exists.")
|
|
164
|
+
|
|
165
|
+
if platform.system().lower() == "windows":
|
|
166
|
+
try:
|
|
167
|
+
subprocess.run(["attrib", "+h", str(app_dir.parent)], shell=True, check=False)
|
|
168
|
+
except: pass
|
|
169
|
+
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
def download_source(component_name):
|
|
173
|
+
app_dir = get_app_dir()
|
|
174
|
+
src_dir = app_dir / "src"
|
|
175
|
+
|
|
176
|
+
repo_url = "https://github.com/MarkanM-Official/LMMs/archive/refs/heads/main.zip"
|
|
177
|
+
zip_path = app_dir / "main.zip"
|
|
178
|
+
|
|
179
|
+
app_dir.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
|
|
181
|
+
reporter.print_step("Downloading latest LMMs source code...")
|
|
182
|
+
try:
|
|
183
|
+
response = requests.get(repo_url, stream=True)
|
|
184
|
+
response.raise_for_status()
|
|
185
|
+
total_size = int(response.headers.get('content-length', 0))
|
|
186
|
+
|
|
187
|
+
with Progress(
|
|
188
|
+
TextColumn("[bold cyan]{task.description}"),
|
|
189
|
+
BarColumn(bar_width=40),
|
|
190
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
191
|
+
"•",
|
|
192
|
+
DownloadColumn(),
|
|
193
|
+
"•",
|
|
194
|
+
TransferSpeedColumn(),
|
|
195
|
+
"•",
|
|
196
|
+
TimeRemainingColumn(),
|
|
197
|
+
console=reporter.console
|
|
198
|
+
) as progress:
|
|
199
|
+
task = progress.add_task("Downloading...", total=total_size if total_size > 0 else None)
|
|
200
|
+
with open(zip_path, "wb") as file:
|
|
201
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
202
|
+
if chunk:
|
|
203
|
+
file.write(chunk)
|
|
204
|
+
progress.update(task, advance=len(chunk))
|
|
205
|
+
|
|
206
|
+
reporter.print_step("Extracting source code...")
|
|
207
|
+
if src_dir.exists():
|
|
208
|
+
shutil.rmtree(src_dir)
|
|
209
|
+
|
|
210
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
211
|
+
zip_ref.extractall(app_dir)
|
|
212
|
+
|
|
213
|
+
# The zip extracts as "LMMs-main", rename it to "src"
|
|
214
|
+
extracted_dir = app_dir / "LMMs-main"
|
|
215
|
+
if extracted_dir.exists():
|
|
216
|
+
extracted_dir.rename(src_dir)
|
|
217
|
+
|
|
218
|
+
zip_path.unlink()
|
|
219
|
+
reporter.print_success(f"✓ Source code installed to {src_dir}")
|
|
220
|
+
return True
|
|
221
|
+
except Exception as e:
|
|
222
|
+
reporter.print_error(f"Download/Extraction failed: {e}")
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
def create_global_wrapper(component_name):
|
|
226
|
+
system = platform.system().lower()
|
|
227
|
+
app_dir = get_app_dir()
|
|
228
|
+
venv_python = get_venv_python()
|
|
229
|
+
launcher_script = app_dir / "src" / "lmms_launcher.py"
|
|
230
|
+
|
|
231
|
+
if system == "windows":
|
|
232
|
+
install_dir = Path.home() / ".lmms" / "bin"
|
|
233
|
+
install_path = install_dir / "lmms.bat"
|
|
234
|
+
wrapper_content = f'@echo off\n"{venv_python}" "{launcher_script}" %*'
|
|
235
|
+
else:
|
|
236
|
+
install_dir = Path("/usr/local/bin")
|
|
237
|
+
if not os.access(install_dir, os.W_OK):
|
|
238
|
+
install_dir = Path.home() / ".local" / "bin"
|
|
239
|
+
install_path = install_dir / "lmms"
|
|
240
|
+
wrapper_content = f'#!/bin/bash\n"{venv_python}" "{launcher_script}" "$@"'
|
|
241
|
+
|
|
242
|
+
install_dir.mkdir(parents=True, exist_ok=True)
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
with open(install_path, "w") as f:
|
|
246
|
+
f.write(wrapper_content)
|
|
247
|
+
|
|
248
|
+
if system != "windows":
|
|
249
|
+
os.chmod(install_path, 0o755)
|
|
250
|
+
|
|
251
|
+
reporter.print_success(f"✓ Global wrapper created at {install_path}")
|
|
252
|
+
return True
|
|
253
|
+
except Exception as e:
|
|
254
|
+
reporter.print_error(f"Failed to create wrapper: {e}")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
def install_pip_requirements(component_name):
|
|
258
|
+
req_file = get_app_dir() / "src" / "requirements.txt"
|
|
259
|
+
if not req_file.exists():
|
|
260
|
+
reporter.print_error("requirements.txt not found in source code!")
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
reporter.print_step("Installing Pip Requirements...")
|
|
264
|
+
reporter.print_step("The following dependencies will be installed:")
|
|
265
|
+
try:
|
|
266
|
+
with open(req_file, "r") as f:
|
|
267
|
+
for line in f:
|
|
268
|
+
if line.strip() and not line.startswith("#"):
|
|
269
|
+
print(f" - {line.strip()}")
|
|
270
|
+
except: pass
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
venv_python = get_venv_python()
|
|
274
|
+
# Upgrade pip
|
|
275
|
+
subprocess.run([str(venv_python), "-m", "pip", "install", "--upgrade", "pip"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
276
|
+
|
|
277
|
+
# Install requirements
|
|
278
|
+
cmd = [str(venv_python), "-m", "pip", "install", "-r", str(req_file)]
|
|
279
|
+
subprocess.run(cmd, check=True)
|
|
280
|
+
|
|
281
|
+
# Check if playwright is in requirements and install browser binaries
|
|
282
|
+
with open(req_file, "r") as f:
|
|
283
|
+
reqs_text = f.read().lower()
|
|
284
|
+
if "playwright" in reqs_text:
|
|
285
|
+
reporter.print_step("Installing Playwright browser binaries...")
|
|
286
|
+
subprocess.run([str(venv_python), "-m", "playwright", "install", "chromium"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
287
|
+
|
|
288
|
+
reporter.print_success("✓ Pip requirements installed successfully.")
|
|
289
|
+
return True
|
|
290
|
+
except subprocess.CalledProcessError as e:
|
|
291
|
+
reporter.print_error(f"Failed to install pip requirements: {e}")
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
def install_component(component_name, force=False):
|
|
295
|
+
installed = get_installed_components()
|
|
296
|
+
if installed.get(component_name) and not force:
|
|
297
|
+
reporter.print_success(f"✓ {component_name.capitalize()} is already installed. Skipping. (Use update to force)")
|
|
298
|
+
return True, ""
|
|
299
|
+
|
|
300
|
+
if force:
|
|
301
|
+
reporter.print_step(f"Updating {component_name.capitalize()} to the latest version...")
|
|
302
|
+
if component_name == "all":
|
|
303
|
+
try:
|
|
304
|
+
reporter.print_step("Checking for LMMs-builder updates...")
|
|
305
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "lmms-builder"],
|
|
306
|
+
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
else:
|
|
310
|
+
reporter.print_step(f"Installing {component_name.capitalize()}...")
|
|
311
|
+
|
|
312
|
+
if component_name in ["engine", "backend", "gui", "cli", "all"]:
|
|
313
|
+
# 0. Check Prerequisites
|
|
314
|
+
check_prerequisites()
|
|
315
|
+
|
|
316
|
+
# 1. Check Python version
|
|
317
|
+
python_path = ensure_stable_python()
|
|
318
|
+
|
|
319
|
+
# 2. Setup VENV
|
|
320
|
+
if not setup_venv(python_path):
|
|
321
|
+
return False, "VENV setup failed"
|
|
322
|
+
|
|
323
|
+
# 3. Download Source
|
|
324
|
+
if not download_source(component_name):
|
|
325
|
+
return False, "Source download failed"
|
|
326
|
+
|
|
327
|
+
# 4. Install Pip Requirements inside VENV
|
|
328
|
+
if not install_pip_requirements(component_name):
|
|
329
|
+
return False, "Pip installation failed"
|
|
330
|
+
|
|
331
|
+
# 5. Create Global Wrapper
|
|
332
|
+
if not create_global_wrapper(component_name):
|
|
333
|
+
return False, "Wrapper creation failed"
|
|
334
|
+
|
|
335
|
+
mark_installed(component_name)
|
|
336
|
+
return True, ""
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
reporter.print_error(f"Unknown component: {component_name}")
|
|
340
|
+
return False, "Unknown component"
|
|
341
|
+
|
lmms_builder/reporter.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
def print_header():
|
|
8
|
+
console.print(Panel("[bold cyan]LMMs Builder[/bold cyan]\n[bold yellow]Powered by MarkanM. For more details: Lmms.markanm.com[/bold yellow]\n[dim]Bootstrapper & Hardware Profiler[/dim]", border_style="cyan"))
|
|
9
|
+
|
|
10
|
+
def print_hardware_report(hw):
|
|
11
|
+
table = Table(title="Hardware Detection Report", show_header=True, header_style="bold magenta")
|
|
12
|
+
table.add_column("Component", style="cyan")
|
|
13
|
+
table.add_column("Details", style="green")
|
|
14
|
+
|
|
15
|
+
os_info = f"{hw['os']['system']} {hw['os']['release']} ({hw['os']['architecture']})"
|
|
16
|
+
table.add_row("OS", os_info)
|
|
17
|
+
|
|
18
|
+
cpu_info = f"{hw['cpu']['cores']} Cores / {hw['cpu']['threads']} Threads"
|
|
19
|
+
table.add_row("CPU", cpu_info)
|
|
20
|
+
|
|
21
|
+
ram_info = f"{hw['ram']['total_gb']} GB (Available: {hw['ram']['available_gb']} GB)"
|
|
22
|
+
table.add_row("RAM", ram_info)
|
|
23
|
+
|
|
24
|
+
if hw['gpu']['detected']:
|
|
25
|
+
gpu_info = f"{hw['gpu']['name']} ({hw['gpu']['vram_gb']} GB VRAM)"
|
|
26
|
+
else:
|
|
27
|
+
gpu_info = "None Detected"
|
|
28
|
+
table.add_row("GPU", gpu_info)
|
|
29
|
+
|
|
30
|
+
if hw['cuda']['available']:
|
|
31
|
+
cuda_info = f"Version {hw['cuda']['version']}"
|
|
32
|
+
else:
|
|
33
|
+
cuda_info = "Not Available"
|
|
34
|
+
table.add_row("CUDA", cuda_info)
|
|
35
|
+
|
|
36
|
+
table.add_row("Python", hw['python']['version'])
|
|
37
|
+
|
|
38
|
+
vscode_status = "[green]Installed[/green]" if hw['tools']['vscode'] else "[red]Not Installed[/red]"
|
|
39
|
+
table.add_row("VS Code", vscode_status)
|
|
40
|
+
|
|
41
|
+
import platform
|
|
42
|
+
if platform.system().lower() == "windows":
|
|
43
|
+
bt_status = "[green]Installed[/green]" if hw['tools']['build_tools'] else "[red]Not Installed[/red]"
|
|
44
|
+
table.add_row("C++ Build Tools", bt_status)
|
|
45
|
+
|
|
46
|
+
console.print(table)
|
|
47
|
+
|
|
48
|
+
def print_compatibility_report(hw):
|
|
49
|
+
console.print("\n[bold magenta]========================[/bold magenta]")
|
|
50
|
+
console.print("[bold cyan]LMMS Compatibility[/bold cyan]\n")
|
|
51
|
+
|
|
52
|
+
engine_comp = 100
|
|
53
|
+
cli_comp = 100
|
|
54
|
+
gui_comp = 100
|
|
55
|
+
air_comp = 100 if hw['ram']['total_gb'] >= 16 else 50
|
|
56
|
+
vllm_comp = 100 if hw['gpu']['detected'] and hw['gpu']['vram_gb'] >= 8 else 0
|
|
57
|
+
|
|
58
|
+
console.print(f"Engine: {engine_comp}%")
|
|
59
|
+
console.print(f"CLI: {cli_comp}%")
|
|
60
|
+
console.print(f"GUI: {gui_comp}%")
|
|
61
|
+
console.print(f"Air Engine: {air_comp}%")
|
|
62
|
+
console.print(f"vLLM: {vllm_comp}%")
|
|
63
|
+
|
|
64
|
+
overall = (engine_comp + cli_comp + gui_comp + air_comp + vllm_comp) // 5
|
|
65
|
+
console.print(f"\n[bold green]Overall: {overall}%[/bold green]")
|
|
66
|
+
console.print("[bold magenta]========================[/bold magenta]\n")
|
|
67
|
+
|
|
68
|
+
def print_benchmark_estimate(hw):
|
|
69
|
+
console.print("\n[bold cyan]Hardware Benchmark (Estimated)[/bold cyan]")
|
|
70
|
+
|
|
71
|
+
# Calculate an estimated TPS multiplier based on hardware
|
|
72
|
+
base = 5.0
|
|
73
|
+
if hw['gpu']['detected']:
|
|
74
|
+
base += hw['gpu']['vram_gb'] * 2
|
|
75
|
+
else:
|
|
76
|
+
base += hw['cpu']['cores'] * 1.5
|
|
77
|
+
|
|
78
|
+
console.print("llama.cpp (7B): ~[green]{:.1f} tok/s[/green]".format(base))
|
|
79
|
+
console.print("ollama (7B): ~[green]{:.1f} tok/s[/green]".format(base * 0.95))
|
|
80
|
+
if hw['ram']['total_gb'] >= 16:
|
|
81
|
+
console.print("air (70B): ~[yellow]{:.1f} tok/s[/yellow]".format(max(1.0, base / 10)))
|
|
82
|
+
else:
|
|
83
|
+
console.print("air (70B): [red]Not Recommended (Insufficent RAM)[/red]")
|
|
84
|
+
|
|
85
|
+
def print_step(step_msg):
|
|
86
|
+
console.print(f"[bold blue]>[/bold blue] {step_msg}")
|
|
87
|
+
|
|
88
|
+
def print_success(msg):
|
|
89
|
+
console.print(f"[bold green]✓[/bold green] {msg}")
|
|
90
|
+
|
|
91
|
+
def print_error(msg):
|
|
92
|
+
console.print(f"[bold red]✗[/bold red] {msg}")
|
|
93
|
+
|
|
94
|
+
def print_autoset_summary(mode, runtime):
|
|
95
|
+
console.print(Panel(
|
|
96
|
+
f"[bold green]AutoSet Complete![/bold green]\n\n"
|
|
97
|
+
f"[cyan]Default Mode:[/cyan] {mode}\n"
|
|
98
|
+
f"[cyan]Default Runtime:[/cyan] {runtime}\n\n"
|
|
99
|
+
"Run [bold magenta]lmms[/bold magenta] to launch.",
|
|
100
|
+
border_style="green"
|
|
101
|
+
))
|