Compare commits

..

1 Commits

Author SHA1 Message Date
Andrey Kozhevnikov
81f409c5ca feature: save and select kconfig
Signed-off-by: Andrey Kozhevnikov <coderusinbox@gmail.com>
2025-02-09 03:13:27 +07:00
9 changed files with 72 additions and 180 deletions

View File

@@ -7,7 +7,6 @@
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import re
from pathlib import Path
from subprocess import (
DEVNULL,
PIPE,
@@ -167,14 +166,14 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}")
else:
Logger.print_ok("Flashing successful!", start="\n", end="\n\n")
Logger.print_ok("Flashing successfull!", start="\n", end="\n\n")
except (Exception, CalledProcessError):
Logger.print_error("Flashing failed!", start="\n")
Logger.print_error("See the console output above!", end="\n\n")
def run_make_clean(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None:
def run_make_clean(kconfig = '.config') -> None:
try:
run(
f"make KCONFIG_CONFIG={kconfig} clean",
@@ -187,7 +186,7 @@ def run_make_clean(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None:
raise
def run_make_menuconfig(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None:
def run_make_menuconfig(kconfig = '.config') -> None:
try:
run(
f"make PYTHON=python3 KCONFIG_CONFIG={kconfig} menuconfig",
@@ -200,7 +199,7 @@ def run_make_menuconfig(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None:
raise
def run_make(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None:
def run_make(kconfig = '.config') -> None:
try:
run(
f"make PYTHON=python3 KCONFIG_CONFIG={kconfig}",

View File

@@ -9,9 +9,9 @@
from __future__ import annotations
import textwrap
from pathlib import Path
from shutil import copyfile
from typing import List, Set, Type
from os import path, listdir, mkdir
from shutil import copyfile
from components.klipper import KLIPPER_DIR, KLIPPER_KCONFIGS_DIR
from components.klipper_firmware.firmware_utils import (
@@ -22,14 +22,14 @@ from components.klipper_firmware.firmware_utils import (
from components.klipper_firmware.flash_options import FlashOptions
from core.logger import DialogType, Logger
from core.menus import Option
from core.menus.base_menu import BaseMenu
from core.menus.base_menu import BaseMenu, print_back_footer
from core.types.color import Color
from utils.input_utils import get_confirm, get_string_input
from utils.sys_utils import (
check_package_install,
install_system_packages,
update_system_package_lists,
)
from utils.input_utils import get_confirm, get_string_input, get_selection_input
# noinspection PyUnusedLocal
@@ -42,11 +42,8 @@ class KlipperKConfigMenu(BaseMenu):
self.previous_menu: Type[BaseMenu] | None = previous_menu
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = KLIPPER_DIR.joinpath(".config")
self.configs: List[Path] = []
self.kconfig = (
self.kconfig_default if not Path(self.kconfigs_dirname).is_dir() else None
)
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.kconfig_default if not path.isdir(self.kconfigs_dirname) else None
def run(self) -> None:
if not self.kconfig:
@@ -62,53 +59,46 @@ class KlipperKConfigMenu(BaseMenu):
)
def set_options(self) -> None:
if not Path(self.kconfigs_dirname).is_dir():
if not path.isdir(self.kconfigs_dirname):
return
self.input_label_txt = "Select config or action to continue (default=N)"
self.default_option = Option(
method=self.select_config, opt_data=self.kconfig_default
)
self.input_label_txt = "Select config or action to continue (default=n)"
self.default_option = Option(method=self.select_config, opt_data=self.kconfig_default)
self.configs = []
option_index = 1
for kconfig in Path(self.kconfigs_dirname).iterdir():
if not kconfig.name.endswith(".config"):
for kconfig in listdir(self.kconfigs_dirname):
if not kconfig.endswith(".config"):
continue
kconfig_path = self.kconfigs_dirname.joinpath(kconfig)
if Path(kconfig_path).is_file():
kconfig_path = path.join(self.kconfigs_dirname, kconfig)
if path.isfile(kconfig_path):
self.configs += [kconfig]
self.options[str(option_index)] = Option(
method=self.select_config, opt_data=kconfig_path
)
self.options[str(option_index)] = Option(method=self.select_config, opt_data=kconfig_path)
option_index += 1
self.options["n"] = Option(
method=self.select_config, opt_data=self.kconfig_default
)
self.options['n'] = Option(method=self.select_config, opt_data=self.kconfig_default)
def print_menu(self) -> None:
cfg_found_str = Color.apply(
"Previously saved firmware configs found!", Color.GREEN
)
menu = textwrap.dedent(
f"""
"""
╟───────────────────────────────────────────────────────╢
{cfg_found_str:^62}
Found previously saved firmware configs
║ ║
║ You can select existing firmware config or create a ║
║ new one. ║
║ ║
║ Select an existing config or create a new one. ║
╟───────────────────────────────────────────────────────╢
║ Available firmware configs: ║
"""
)[1:]
start_index = 1
for i, s in enumerate(self.configs):
line = f"{start_index + i}) {s.name}"
line = f"{start_index + i}) {s}"
menu += f"{line:<54}\n"
new_config = Color.apply("n) New firmware config", Color.YELLOW)
menu += f"{new_config:<63}\n"
new_config = Color.apply("N) Create new firmware config", Color.GREEN)
menu += "║ ║\n"
menu += f"{new_config:<62}\n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")
@@ -117,17 +107,14 @@ class KlipperKConfigMenu(BaseMenu):
selection: str | None = kwargs.get("opt_data", None)
if selection is None:
raise Exception("opt_data is None")
if not Path(selection).is_file() and selection != self.kconfig_default:
if not path.isfile(selection) and selection != self.kconfig_default:
raise Exception("opt_data does not exists")
self.kconfig = selection
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu):
def __init__(
self, kconfig: str | None = None, previous_menu: Type[BaseMenu] | None = None
):
def __init__(self, kconfig: str | None = None, previous_menu: Type[BaseMenu] | None = None):
super().__init__()
self.title = "Build Firmware Menu"
self.title_color = Color.CYAN
@@ -136,7 +123,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
self.missing_deps: List[str] = check_package_install(self.deps)
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = KLIPPER_DIR.joinpath(".config")
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.flash_options.selected_kconfig
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
@@ -147,22 +134,16 @@ class KlipperBuildFirmwareMenu(BaseMenu):
)
def set_options(self) -> None:
self.input_label_txt = "Press ENTER to install dependencies"
self.default_option = Option(method=self.install_missing_deps)
def run(self):
# immediately start the build process if all dependencies are met
if len(self.missing_deps) == 0:
self.start_build_process()
self.input_label_txt = "Press ENTER to continue"
self.default_option = Option(method=self.start_build_process)
else:
super().run()
self.input_label_txt = "Press ENTER to install dependencies"
self.default_option = Option(method=self.install_missing_deps)
def print_menu(self) -> None:
txt = Color.apply("Dependencies are missing!", Color.RED)
menu = textwrap.dedent(
f"""
╟───────────────────────────────────────────────────────╢
{txt:^62}
"""
╟───────────────────────────────────────────────────────╢
║ The following dependencies are required: ║
║ ║
@@ -176,8 +157,16 @@ class KlipperBuildFirmwareMenu(BaseMenu):
padding = 40 - len(d) + len(status) + (len(status_ok) - len(status))
d = Color.apply(f"{d}", Color.CYAN)
menu += f"{d}{status:>{padding}}\n"
menu += "║ ║\n"
color = Color.GREEN if len(self.missing_deps) == 0 else Color.RED
txt = (
"All dependencies are met!"
if len(self.missing_deps) == 0
else "Dependencies are missing!"
)
menu += f"{Color.apply(txt, color):<62}\n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")
@@ -213,7 +202,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
finally:
if self.previous_menu is not None:
self.previous_menu().run()
def save_firmware_config(self) -> None:
Logger.print_dialog(
DialogType.CUSTOM,
@@ -223,9 +212,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
],
custom_title="Save firmware config",
)
if not get_confirm(
"Do you want to save firmware config?", default_choice=False
):
if not get_confirm("Do you want to save firmware config?", default_choice=False):
return
filename = self.kconfig_default
@@ -244,31 +231,28 @@ class KlipperBuildFirmwareMenu(BaseMenu):
"Enter the new firmware config name",
regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$",
)
filename = self.kconfigs_dirname.joinpath(f"{input_name}.config")
filename = path.join(self.kconfigs_dirname, f"{input_name}.config")
if Path(filename).is_file():
if get_confirm(
f"Firmware config {input_name} already exists, overwrite?",
default_choice=False,
):
if path.isfile(filename):
if get_confirm(f"Firmware config {input_name} already exists, overwrite?", default_choice=False):
break
if Path(filename).is_dir():
if path.isdir(filename):
Logger.print_error(f"Path {filename} exists and it's a directory")
if not Path(filename).exists():
if not path.exists(filename):
break
if not get_confirm(
f"Save firmware config to '{filename}'?", default_choice=True
):
if not get_confirm(f"Save firmware config to '{filename}'?", default_choice=True):
Logger.print_info("Aborted saving firmware config ...")
return
if not Path(self.kconfigs_dirname).exists():
Path(self.kconfigs_dirname).mkdir()
if not path.exists(self.kconfigs_dirname):
mkdir(self.kconfigs_dirname)
copyfile(self.kconfig_default, filename)
Logger.print_ok()
Logger.print_ok(f"Firmware config successfully saved to {filename}")

View File

@@ -10,8 +10,8 @@ from __future__ import annotations
import textwrap
import time
from pathlib import Path
from typing import Type
from os.path import basename
from components.klipper_firmware.firmware_utils import (
find_firmware_file,
@@ -37,6 +37,7 @@ from components.klipper_firmware.menus.klipper_flash_help_menu import (
KlipperFlashMethodHelpMenu,
KlipperMcuConnectionHelpMenu,
)
from components.klipper_firmware.menus.klipper_build_menu import KlipperKConfigMenu
from core.logger import DialogType, Logger
from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu, MenuTitleStyle
@@ -421,7 +422,7 @@ class KlipperFlashOverviewMenu(BaseMenu):
mcu = self.flash_options.selected_mcu.split("/")[-1]
board = self.flash_options.selected_board
baudrate = self.flash_options.selected_baudrate
kconfig = Path(self.flash_options.selected_kconfig).name
kconfig = basename(self.flash_options.selected_kconfig)
color = Color.CYAN
subheader = f"[{Color.apply('Overview', color)}]"
menu = textwrap.dedent(

View File

@@ -8,6 +8,7 @@
# ======================================================================= #
from __future__ import annotations
import json
import subprocess
from typing import List
@@ -30,7 +31,6 @@ from components.moonraker.moonraker_dialogs import print_moonraker_overview
from components.moonraker.moonraker_utils import (
backup_moonraker_dir,
create_example_moonraker_conf,
parse_sysdeps_file,
)
from components.webui_client.client_utils import (
enable_mainsail_remotemode,
@@ -53,7 +53,6 @@ from utils.sys_utils import (
cmd_sysctl_manage,
cmd_sysctl_service,
create_python_venv,
get_distro_info,
install_python_requirements,
parse_packages_from_file,
)
@@ -158,37 +157,9 @@ def install_moonraker_packages() -> None:
moonraker_deps = []
if MOONRAKER_DEPS_JSON_FILE.exists():
Logger.print_status(
f"Parsing system dependencies from {MOONRAKER_DEPS_JSON_FILE.name} ..."
)
parsed_sysdeps = parse_sysdeps_file(MOONRAKER_DEPS_JSON_FILE)
distro_name, distro_version = get_distro_info()
Logger.print_info(f"Distro name: {distro_name}")
Logger.print_info(f"Distro version: {distro_version}")
for dep in parsed_sysdeps.get(distro_name, []):
pkg = dep[0].strip()
comparator = dep[1].strip()
req_version = dep[2].strip()
comparisons = {
"": lambda x, y: True,
"<": lambda x, y: x < y,
">": lambda x, y: x > y,
"<=": lambda x, y: x <= y,
">=": lambda x, y: x >= y,
"==": lambda x, y: x == y,
"!=": lambda x, y: x != y,
}
if comparisons[comparator](float(distro_version), float(req_version or 0)):
moonraker_deps.append(pkg)
with open(MOONRAKER_DEPS_JSON_FILE, "r") as deps:
moonraker_deps = json.load(deps).get("debian", [])
elif MOONRAKER_INSTALL_SCRIPT.exists():
Logger.print_status(
f"Parsing system dependencies from {MOONRAKER_INSTALL_SCRIPT.name} ..."
)
moonraker_deps = parse_packages_from_file(MOONRAKER_INSTALL_SCRIPT)
if not moonraker_deps:

View File

@@ -6,11 +6,9 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import json
import re
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Optional
from components.moonraker import (
MODULE_PATH,
@@ -140,34 +138,3 @@ def backup_moonraker_db_dir() -> None:
bm.backup_directory(
name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR
)
# This function is from sync_dependencies.py script from the Moonraker project on GitHub:
# https://github.com/Arksine/moonraker/blob/master/scripts/sync_dependencies.py
# Thanks to Arksine for his work on this project!
def parse_sysdeps_file(sysdeps_file: Path) -> Dict[str, List[Tuple[str, str, str]]]:
"""
Parses the system dependencies file and returns a dictionary with the parsed dependencies.
:param sysdeps_file: The path to the system dependencies file.
:return: A dictionary with the parsed dependencies in the format {distro: [(package, comparator, version)]}.
"""
base_deps: Dict[str, List[str]] = json.loads(sysdeps_file.read_bytes())
parsed_deps: Dict[str, List[Tuple[str, str, str]]] = {}
for distro, pkgs in base_deps.items():
parsed_deps[distro] = []
for dep in pkgs:
parts = dep.split(";", maxsplit=1)
if len(parts) == 1:
parsed_deps[distro].append((dep.strip(), "", ""))
else:
pkg_name = parts[0].strip()
dep_parts = re.split(r"(==|!=|<=|>=|<|>)", parts[1].strip())
comp_var = dep_parts[0].strip().lower()
if len(dep_parts) != 3 or comp_var != "distro_version":
continue
operator = dep_parts[1].strip()
req_version = dep_parts[2].strip()
parsed_deps[distro].append((pkg_name, operator, req_version))
return parsed_deps

View File

@@ -14,8 +14,8 @@ from typing import Type
from components.klipper import KLIPPER_DIR
from components.klipper.klipper import Klipper
from components.klipper_firmware.menus.klipper_build_menu import (
KlipperBuildFirmwareMenu,
KlipperKConfigMenu,
KlipperBuildFirmwareMenu,
)
from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperFlashMethodMenu,

View File

@@ -31,7 +31,7 @@ def change_system_hostname() -> None:
"http://<hostname>.local",
"\n\n",
"Example: If you set your hostname to 'my-printer', you can access an "
"installed webinterface by typing 'http://my-printer.local' in the "
"installed webinterface by tyoing 'http://my-printer.local' in the "
"browser.",
],
custom_title="CHANGE SYSTEM HOSTNAME",

View File

@@ -19,7 +19,7 @@ import urllib.error
import urllib.request
from pathlib import Path
from subprocess import DEVNULL, PIPE, CalledProcessError, Popen, check_output, run
from typing import List, Literal, Set, Tuple
from typing import List, Literal, Set
from core.constants import SYSTEMD
from core.logger import Logger
@@ -539,32 +539,3 @@ def get_service_file_path(instance_type: type, suffix: str) -> Path:
file_path: Path = SYSTEMD.joinpath(f"{name}.service")
return file_path
def get_distro_info() -> Tuple[str, str]:
distro_info: str = check_output(["cat", "/etc/os-release"]).decode().strip()
if not distro_info:
raise ValueError("Error reading distro info!")
distro_id: str = ""
distro_id_like: str = ""
distro_version: str = ""
for line in distro_info.split("\n"):
if line.startswith("ID="):
distro_id = line.split("=")[1].strip('"').strip()
if line.startswith("ID_LIKE="):
distro_id_like = line.split("=")[1].strip('"').strip()
if line.startswith("VERSION_ID="):
distro_version = line.split("=")[1].strip('"').strip()
if distro_id == "raspbian":
distro_id = distro_id_like
if not distro_id:
raise ValueError("Error reading distro id!")
if not distro_version:
raise ValueError("Error reading distro version!")
return distro_id.lower(), distro_version

View File

@@ -16,7 +16,6 @@ trusted_clients:
cors_domains:
*.lan
*.local
*.internal
*://localhost
*://localhost:*
*://my.mainsail.xyz