install: support for package requirement specifiers
Initial support for pip-like requirement specifiers applicable to system packages. This allows for a package dependencies specific to distribution version. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
@@ -11,19 +11,27 @@ import pathlib
|
||||
import tomllib
|
||||
import json
|
||||
import re
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
MAX_LINE_LENGTH = 88
|
||||
SCRIPTS_PATH = pathlib.Path(__file__).parent
|
||||
INST_PKG_HEADER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCES START ***"
|
||||
INST_PKG_FOOTER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCES END ***"
|
||||
INST_PKG_HEADER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCIES START ***"
|
||||
INST_PKG_FOOTER = "# *** AUTO GENERATED OS PACKAGE DEPENDENCIES END ***"
|
||||
|
||||
def gen_multline_var(var_name: str, values: List[str], indent: int = 0) -> str:
|
||||
def gen_multline_var(
|
||||
var_name: str,
|
||||
values: List[str],
|
||||
indent: int = 0,
|
||||
is_first: bool = True
|
||||
) -> str:
|
||||
idt = " " * indent
|
||||
if not values:
|
||||
return f'{idt}{var_name}=""'
|
||||
line_list: List[str] = []
|
||||
current_line = f"{idt}{var_name}=\"{values.pop(0)}"
|
||||
if is_first:
|
||||
current_line = f"{idt}{var_name}=\"{values.pop(0)}"
|
||||
else:
|
||||
current_line = (f"{idt}{var_name}=\"${{{var_name}}} {values.pop(0)}")
|
||||
for val in values:
|
||||
if len(current_line) + len(val) + 2 > MAX_LINE_LENGTH:
|
||||
line_list.append(f'{current_line}"')
|
||||
@@ -33,14 +41,35 @@ def gen_multline_var(var_name: str, values: List[str], indent: int = 0) -> str:
|
||||
line_list.append(f'{current_line}"')
|
||||
return "\n".join(line_list)
|
||||
|
||||
def parse_sysdeps_file() -> Dict[str, List[Tuple[str, str, str]]]:
|
||||
sys_deps_file = SCRIPTS_PATH.joinpath("system-dependencies.json")
|
||||
base_deps: Dict[str, List[str]] = json.loads(sys_deps_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
|
||||
|
||||
def sync_packages() -> int:
|
||||
inst_script = SCRIPTS_PATH.joinpath("install-moonraker.sh")
|
||||
sys_deps_file = SCRIPTS_PATH.joinpath("system-dependencies.json")
|
||||
new_deps: Dict[str, List[str]] = json.loads(sys_deps_file.read_bytes())
|
||||
new_deps = parse_sysdeps_file()
|
||||
# Copy install script in memory.
|
||||
install_data: List[str] = []
|
||||
prev_deps: Dict[str, List[str]] = {}
|
||||
prev_deps: Dict[str, List[Tuple[str, str, str]]] = {}
|
||||
distro_name = ""
|
||||
cur_spec: Tuple[str, str] | None = None
|
||||
skip_data = False
|
||||
with inst_script.open("r") as inst_file:
|
||||
for line in inst_file:
|
||||
@@ -56,12 +85,30 @@ def sync_packages() -> int:
|
||||
if distro_match is not None:
|
||||
distro_name = distro_match.group(1)
|
||||
prev_deps[distro_name] = []
|
||||
elif cur_line.startswith("PACKAGES"):
|
||||
pkgs = cur_line.split("=", maxsplit=1)[1].strip('"')
|
||||
pkg_list = pkgs.split()
|
||||
if pkg_list and pkg_list[0] == "${PACKAGES}":
|
||||
pkg_list.pop(0)
|
||||
prev_deps[distro_name].extend(pkg_list)
|
||||
else:
|
||||
if cur_spec is not None and cur_line == "fi":
|
||||
cur_spec = None
|
||||
else:
|
||||
req_match = re.match(
|
||||
r"if \( compare_version \"(<|>|==|!=|<=|>=)\" "
|
||||
r"\"([a-zA-Z0-9._-]+)\" \); then",
|
||||
cur_line
|
||||
)
|
||||
if req_match is not None:
|
||||
parts = req_match.groups()
|
||||
cur_spec = (parts[0], parts[1])
|
||||
elif cur_line.startswith("PACKAGES"):
|
||||
pkgs = cur_line.split("=", maxsplit=1)[1].strip('"')
|
||||
pkg_list = pkgs.split()
|
||||
if pkg_list and pkg_list[0] == "${PACKAGES}":
|
||||
pkg_list.pop(0)
|
||||
operator, req_version = "", ""
|
||||
if cur_spec is not None:
|
||||
operator, req_version = cur_spec
|
||||
for pkg in pkg_list:
|
||||
prev_deps[distro_name].append(
|
||||
(pkg, operator, req_version)
|
||||
)
|
||||
if cur_line == INST_PKG_HEADER:
|
||||
skip_data = True
|
||||
elif cur_line == INST_PKG_FOOTER:
|
||||
@@ -69,9 +116,9 @@ def sync_packages() -> int:
|
||||
install_data.append(line)
|
||||
# Check if an update is necessary
|
||||
if set(prev_deps.keys()) == set(new_deps.keys()):
|
||||
for distro, pkg_list in prev_deps.items():
|
||||
for distro, prev_pkgs in prev_deps.items():
|
||||
new_pkgs = new_deps[distro]
|
||||
if set(pkg_list) != set(new_pkgs):
|
||||
if set(prev_pkgs) != set(new_pkgs):
|
||||
break
|
||||
else:
|
||||
# Dependencies match, exit
|
||||
@@ -93,9 +140,35 @@ def sync_packages() -> int:
|
||||
inst_file.write(
|
||||
f'{prefix} [ ${{DISTRIBUTION}} = "{distro}" ]; then\n'
|
||||
)
|
||||
pkg_var = gen_multline_var("PACKAGES", packages, indent_count + 4)
|
||||
inst_file.write(pkg_var)
|
||||
inst_file.write("\n")
|
||||
pkgs_by_op: Dict[Tuple[str, str], List[str]] = {}
|
||||
base_list: List[str] = []
|
||||
for pkg_spec in packages:
|
||||
if not pkg_spec[1] or not pkg_spec[2]:
|
||||
base_list.append(pkg_spec[0])
|
||||
else:
|
||||
key = (pkg_spec[1], pkg_spec[2])
|
||||
pkgs_by_op.setdefault(key, []).append(pkg_spec[0])
|
||||
is_first = True
|
||||
if base_list:
|
||||
pkg_var = gen_multline_var(
|
||||
"PACKAGES", base_list, indent_count + 4
|
||||
)
|
||||
inst_file.write(pkg_var)
|
||||
inst_file.write("\n")
|
||||
is_first = False
|
||||
if pkgs_by_op:
|
||||
for (operator, req_ver), pkg_list in pkgs_by_op.items():
|
||||
req_idt = idt + " " * 4
|
||||
inst_file.write(
|
||||
f"{req_idt}if ( compare_version \"{operator}\" "
|
||||
f"\"{req_ver}\" ); then\n"
|
||||
)
|
||||
req_pkgs = gen_multline_var(
|
||||
"PACKAGES", pkg_list, indent_count + 8, is_first
|
||||
)
|
||||
inst_file.write(req_pkgs)
|
||||
inst_file.write("\n")
|
||||
inst_file.write(f"{req_idt}fi\n")
|
||||
inst_file.write(f"{idt}fi\n")
|
||||
return 1
|
||||
|
||||
|
Reference in New Issue
Block a user