Compare commits

..

2 Commits

Author SHA1 Message Date
CODeRUS
80e19ae30e Merge 2aefd86040 into 2a08e3eb15 2025-02-02 22:49:39 +07:00
Andrey Kozhevnikov
2aefd86040 feature: save and select kconfig
Signed-off-by: Andrey Kozhevnikov <coderusinbox@gmail.com>
2025-02-02 22:44:58 +07:00
8 changed files with 71 additions and 155 deletions

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
from __future__ import annotations from __future__ import annotations
import json
import subprocess import subprocess
from typing import List from typing import List
@@ -30,7 +31,6 @@ from components.moonraker.moonraker_dialogs import print_moonraker_overview
from components.moonraker.moonraker_utils import ( from components.moonraker.moonraker_utils import (
backup_moonraker_dir, backup_moonraker_dir,
create_example_moonraker_conf, create_example_moonraker_conf,
parse_sysdeps_file,
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
enable_mainsail_remotemode, enable_mainsail_remotemode,
@@ -53,8 +53,6 @@ from utils.sys_utils import (
cmd_sysctl_manage, cmd_sysctl_manage,
cmd_sysctl_service, cmd_sysctl_service,
create_python_venv, create_python_venv,
get_distro_name,
get_distro_version,
install_python_requirements, install_python_requirements,
parse_packages_from_file, parse_packages_from_file,
) )
@@ -159,35 +157,9 @@ def install_moonraker_packages() -> None:
moonraker_deps = [] moonraker_deps = []
if MOONRAKER_DEPS_JSON_FILE.exists(): if MOONRAKER_DEPS_JSON_FILE.exists():
Logger.print_status( with open(MOONRAKER_DEPS_JSON_FILE, "r") as deps:
f"Parsing system dependencies from {MOONRAKER_DEPS_JSON_FILE.name} ..." moonraker_deps = json.load(deps).get("debian", [])
)
parsed_sysdeps = parse_sysdeps_file(MOONRAKER_DEPS_JSON_FILE)
distro_name = get_distro_name().lower()
distro_version = get_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)
elif MOONRAKER_INSTALL_SCRIPT.exists(): 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) moonraker_deps = parse_packages_from_file(MOONRAKER_INSTALL_SCRIPT)
if not moonraker_deps: if not moonraker_deps:

View File

@@ -6,11 +6,9 @@
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import json
import re
import shutil import shutil
from pathlib import Path from typing import Dict, List, Optional
from typing import Dict, List, Optional, Tuple
from components.moonraker import ( from components.moonraker import (
MODULE_PATH, MODULE_PATH,
@@ -140,34 +138,3 @@ def backup_moonraker_db_dir() -> None:
bm.backup_directory( bm.backup_directory(
name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR 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 import KLIPPER_DIR
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipper_firmware.menus.klipper_build_menu import ( from components.klipper_firmware.menus.klipper_build_menu import (
KlipperBuildFirmwareMenu,
KlipperKConfigMenu, KlipperKConfigMenu,
KlipperBuildFirmwareMenu,
) )
from components.klipper_firmware.menus.klipper_flash_menu import ( from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperFlashMethodMenu, KlipperFlashMethodMenu,

View File

@@ -31,7 +31,7 @@ def change_system_hostname() -> None:
"http://<hostname>.local", "http://<hostname>.local",
"\n\n", "\n\n",
"Example: If you set your hostname to 'my-printer', you can access an " "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.", "browser.",
], ],
custom_title="CHANGE SYSTEM HOSTNAME", custom_title="CHANGE SYSTEM HOSTNAME",

View File

@@ -539,10 +539,3 @@ def get_service_file_path(instance_type: type, suffix: str) -> Path:
file_path: Path = SYSTEMD.joinpath(f"{name}.service") file_path: Path = SYSTEMD.joinpath(f"{name}.service")
return file_path return file_path
def get_distro_name() -> str:
return check_output(["lsb_release", "-is"]).decode().strip()
def get_distro_version() -> str:
return check_output(["lsb_release", "-rs"]).decode().strip()