From c96a8d4b488ad4c6162d2406d115e22da6c65d61 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 May 2026 16:28:22 +0100 Subject: [PATCH 01/15] ci: Add workflow for Python linting rules --- .github/workflows/python-lint.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/python-lint.yml diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 0000000..067bed5 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,29 @@ +name: Python Linting + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Set up environment + run: | + sudo apt update + sudo apt install python3-pip python3-flake8 python3-mypy + + - name: Flake8 + run: | + python3 -m flake8 src + + - name: MyPy + run: | + python3 -m mypy src From aa410d08079c7d9d18c86c990102ab841f409baa Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 May 2026 16:34:28 +0100 Subject: [PATCH 02/15] Add flake8 configuration Increase the line length to 140; we have template strings that cannot be easily be broken. --- .flake8 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..593d37f --- /dev/null +++ b/.flake8 @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 Igalia S.L. +# SPDX-License-Identifier: MIT + +[flake8] +max-line-length = 140 +doctests = True +exclude = .git, .eggs, __pycache__, build/, dist/ +#ignore = From f6b7d143db5428ddf5009e5cbd9951d7041f8c30 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 May 2026 16:41:15 +0100 Subject: [PATCH 03/15] Remove unused imports (F401) --- src/moonforgecli/feature.py | 4 ---- src/moonforgecli/features/docker.py | 2 +- src/moonforgecli/features/graphics_weston.py | 2 +- src/moonforgecli/features/podman.py | 2 +- src/moonforgecli/init.py | 5 ++--- src/moonforgecli/kas.py | 4 ++-- src/moonforgecli/log.py | 1 - src/moonforgecli/machine.py | 4 ---- src/moonforgecli/utils.py | 1 - 9 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/moonforgecli/feature.py b/src/moonforgecli/feature.py index d27d4bf..c2180fb 100644 --- a/src/moonforgecli/feature.py +++ b/src/moonforgecli/feature.py @@ -1,10 +1,6 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -import argparse -import os -import sys - from . import log, term from .features import available_features, get_feature diff --git a/src/moonforgecli/features/docker.py b/src/moonforgecli/features/docker.py index 4961ea5..a49c482 100644 --- a/src/moonforgecli/features/docker.py +++ b/src/moonforgecli/features/docker.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -from . import Feature, FeatureFragment, FeatureInclude +from . import Feature, FeatureInclude DOCKER_FEATURE = Feature(name="docker", diff --git a/src/moonforgecli/features/graphics_weston.py b/src/moonforgecli/features/graphics_weston.py index 0f33408..3031847 100644 --- a/src/moonforgecli/features/graphics_weston.py +++ b/src/moonforgecli/features/graphics_weston.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -from . import Feature, FeatureFragment, FeatureInclude +from . import Feature, FeatureInclude GRAPHICS_WESTON_FEATURE = Feature(name="graphics-weston", diff --git a/src/moonforgecli/features/podman.py b/src/moonforgecli/features/podman.py index a8544e7..2403269 100644 --- a/src/moonforgecli/features/podman.py +++ b/src/moonforgecli/features/podman.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -from . import Feature, FeatureFragment, FeatureInclude +from . import Feature, FeatureInclude PODMAN_FEATURE = Feature(name="podman", diff --git a/src/moonforgecli/init.py b/src/moonforgecli/init.py index 0ce4de9..c32ea70 100644 --- a/src/moonforgecli/init.py +++ b/src/moonforgecli/init.py @@ -1,15 +1,14 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -import argparse import os import subprocess from pathlib import Path from . import log, kas, utils -from .features import check_conflicts, get_feature -from .machines import get_machine +from .features import Feature, check_conflicts, get_feature +from .machines import Machine, get_machine HELP_MSG = "Initialize a Moonforge project" diff --git a/src/moonforgecli/kas.py b/src/moonforgecli/kas.py index 7e81857..229b2b7 100644 --- a/src/moonforgecli/kas.py +++ b/src/moonforgecli/kas.py @@ -3,8 +3,8 @@ from dataclasses import dataclass, field -from .features import FeatureFragment, Feature -from .machines import MachineFragment, Machine +from .features import Feature +from .machines import Machine from . import log diff --git a/src/moonforgecli/log.py b/src/moonforgecli/log.py index 4d01c0c..6386f2d 100644 --- a/src/moonforgecli/log.py +++ b/src/moonforgecli/log.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT import os -import platform import sys import time import threading diff --git a/src/moonforgecli/machine.py b/src/moonforgecli/machine.py index e5d4be5..7c965aa 100644 --- a/src/moonforgecli/machine.py +++ b/src/moonforgecli/machine.py @@ -1,10 +1,6 @@ # SPDX-FileCopyrightText: 2026 Igalia S.L. # SPDX-License-Identifier: MIT -import argparse -import os -import sys - from . import log, term from .machines import available_machines, get_machine diff --git a/src/moonforgecli/utils.py b/src/moonforgecli/utils.py index 5988c9b..253515b 100644 --- a/src/moonforgecli/utils.py +++ b/src/moonforgecli/utils.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: MIT import os -import sys from . import log From f995279efd4ccb33913dfa91a4344a1dfeb4a986 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 May 2026 16:48:52 +0100 Subject: [PATCH 04/15] Fix whitespace (E302, E303, E225, E127, W293) --- src/moonforgecli/feature.py | 1 - src/moonforgecli/init.py | 3 +-- src/moonforgecli/machine.py | 1 - src/moonforgecli/term.py | 3 +-- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/moonforgecli/feature.py b/src/moonforgecli/feature.py index c2180fb..125d1a7 100644 --- a/src/moonforgecli/feature.py +++ b/src/moonforgecli/feature.py @@ -41,7 +41,6 @@ def run(options) -> int: if f is None: log.error(f"Invalid feature {feat}.") features.append(f) - for feature in features: res = [] res.append(f"{term.heading('Feature:')} {term.bold(feature.name)}") diff --git a/src/moonforgecli/init.py b/src/moonforgecli/init.py index c32ea70..b5505c7 100644 --- a/src/moonforgecli/init.py +++ b/src/moonforgecli/init.py @@ -183,7 +183,6 @@ def add_conf_dir(project: Project) -> None: def add_kas_dir(project: Project) -> None: project_name = sanitize_project_name(project.name) - layer_name = sanitize_layer_name(project.name) kas_path = project.path / "kas" os.makedirs(kas_path, exist_ok=True) @@ -265,7 +264,7 @@ def run(options): machine = get_machine(options.machine) if machine is None: log.error(f"Invalid target machine {options.machine}. " - "Use 'list-machines' to list the available machines.") + "Use 'list-machines' to list the available machines.") features = [] for feat in options.features: f = get_feature(feat) diff --git a/src/moonforgecli/machine.py b/src/moonforgecli/machine.py index 7c965aa..ad399f9 100644 --- a/src/moonforgecli/machine.py +++ b/src/moonforgecli/machine.py @@ -41,7 +41,6 @@ def run(options) -> int: if m is None: log.error(f"Invalid machine {machine}.") machines.append(m) - for machine in machines: res = [] res.append(f"{term.heading('Machine:')} {term.bold(machine.name)}") diff --git a/src/moonforgecli/term.py b/src/moonforgecli/term.py index 94bcf67..9561dde 100644 --- a/src/moonforgecli/term.py +++ b/src/moonforgecli/term.py @@ -67,7 +67,6 @@ class AnsiEscape(object): WHITE_BG = 47 DEFAULT_BG = 49 - def __init__(self, *args, **kwargs): self._text = kwargs.get('text', '') self._mods = kwargs.get('mods', AnsiEscape.NONE) @@ -155,7 +154,7 @@ def __str__(self): return f"{self.pre}{self.text}{self.post}" -def red(text) ->str: +def red(text) -> str: return AnsiEscape(text=text).color(fg=AnsiEscape.RED_FG) From 3f2ac5a58273d6862d25261d8ef184cffae741de Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 4 May 2026 16:51:13 +0100 Subject: [PATCH 05/15] Fix type name (F821) --- src/moonforgecli/features/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/moonforgecli/features/__init__.py b/src/moonforgecli/features/__init__.py index e9dece6..9750649 100644 --- a/src/moonforgecli/features/__init__.py +++ b/src/moonforgecli/features/__init__.py @@ -53,7 +53,7 @@ class Feature: variables: list[FeatureVariable] | None = field(default_factory=list) -def available_features() -> list[Features]: +def available_features() -> list[Feature]: from .docker import DOCKER_FEATURE from .graphics_weston import GRAPHICS_WESTON_FEATURE from .graphics_wpe import GRAPHICS_WPE_FEATURE From 4afba063749fb34a0d9c3f7b67a0d312c5de37cd Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 11:56:15 +0100 Subject: [PATCH 06/15] term: Use a proper enumeration type --- src/moonforgecli/term.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/moonforgecli/term.py b/src/moonforgecli/term.py index 9561dde..242a39d 100644 --- a/src/moonforgecli/term.py +++ b/src/moonforgecli/term.py @@ -4,8 +4,11 @@ import os import sys +from enum import Enum -class TermColor: + +class TermColor(Enum): + """Type of color support inside the terminal.""" NONE = 0 EXT = 1 TRUE = 2 From 52ac674c0e5cbfbfbd24c41d6062617361355289 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 11:57:04 +0100 Subject: [PATCH 07/15] utils: Annotate the programs cache --- src/moonforgecli/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/moonforgecli/utils.py b/src/moonforgecli/utils.py index 253515b..8624eb6 100644 --- a/src/moonforgecli/utils.py +++ b/src/moonforgecli/utils.py @@ -6,7 +6,7 @@ from . import log -FOUND_PROGRAMS = {} +FOUND_PROGRAMS: dict[str, str] = {} def find_program(bin_name: str, path: str | None = None, error_if_not_found: bool = False) -> str | None: From dc5232098d7b32efe5ea5235dc5c24aa7db9d282 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 14:15:55 +0100 Subject: [PATCH 08/15] Handle nullability of features and machines Instead of using "list | None", always return a list; use an empty list as a valid condition, and raise an exception for a programmer error. --- src/moonforgecli/feature.py | 9 +++++---- src/moonforgecli/features/__init__.py | 23 ++++++++++++++--------- src/moonforgecli/init.py | 21 ++++++++++++--------- src/moonforgecli/machine.py | 7 ++++--- src/moonforgecli/machines/__init__.py | 4 ++-- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/moonforgecli/feature.py b/src/moonforgecli/feature.py index 125d1a7..47c7431 100644 --- a/src/moonforgecli/feature.py +++ b/src/moonforgecli/feature.py @@ -37,9 +37,10 @@ def run(options) -> int: features = [] for feat in options.features: - f = get_feature(feat) - if f is None: - log.error(f"Invalid feature {feat}.") + try: + f = get_feature(feat) + except IndexError as err: + log.error(f"{err}") features.append(f) for feature in features: res = [] @@ -59,7 +60,7 @@ def run(options) -> int: for include in feature.includes: res.append(f" - {term.green(include.file)} from {term.green(include.repo)}") res.append("") - if len(feature.conflicts) > 0: + if feature.conflicts is not None and len(feature.conflicts) > 0: res.append(term.heading("Conflicts:")) for conflict in feature.conflicts: res.append(f" - {term.red(conflict)}") diff --git a/src/moonforgecli/features/__init__.py b/src/moonforgecli/features/__init__.py index 9750649..1fa2433 100644 --- a/src/moonforgecli/features/__init__.py +++ b/src/moonforgecli/features/__init__.py @@ -68,22 +68,30 @@ def available_features() -> list[Feature]: ] -def get_feature(name: str) -> Feature | None: +def get_feature(name: str) -> Feature: for feature in available_features(): if feature.name == name: return feature - return None + raise IndexError(f"Feature {name} not found") -def check_conflicts(name: str, features: list[str]) -> list[str] | None: - feature = get_feature(name) +def check_conflicts(name: str, features: list[str]) -> list[str]: + try: + feature = get_feature(name) + except IndexError: + return [] + feature_conflicts = getattr(feature, "conflicts", []) if len(feature_conflicts) == 0: - return None + return [] res = [] for feat in features: - check = get_feature(feat) + try: + check = get_feature(feat) + except IndexError: + continue + if check.name in feature_conflicts: res.append(check.name) else: @@ -91,7 +99,4 @@ def check_conflicts(name: str, features: list[str]) -> list[str] | None: if feature.name in check_conflicts: res.append(check.name) - if len(res) == 0: - return None - return res diff --git a/src/moonforgecli/init.py b/src/moonforgecli/init.py index b5505c7..d0c53e2 100644 --- a/src/moonforgecli/init.py +++ b/src/moonforgecli/init.py @@ -6,7 +6,7 @@ from pathlib import Path -from . import log, kas, utils +from . import log, kas, term, utils from .features import Feature, check_conflicts, get_feature from .machines import Machine, get_machine @@ -222,7 +222,7 @@ def init_vcs(project: Project) -> None: stderr=subprocess.PIPE) output, err = proc.communicate() if proc.returncode: - log.warning(f"Unable to initialize VCS: {err}") + log.warning(f"Unable to initialize VCS: {err!r}") return except Exception as e: log.warning(f"Unable to initialize VCS: {e}") @@ -261,17 +261,20 @@ def run(options): path = Path(project_path) if path.exists() and not path.is_dir(): log.error(f"Project path {path} exists and is not a directory.") - machine = get_machine(options.machine) - if machine is None: + try: + machine = get_machine(options.machine) + except IndexError: log.error(f"Invalid target machine {options.machine}. " - "Use 'list-machines' to list the available machines.") + f"Use {term.command('machine')} to list the available machines.") features = [] for feat in options.features: - f = get_feature(feat) - if f is None: - log.error(f"Invalid feature {feat}.") + try: + f = get_feature(feat) + except IndexError: + log.error(f"Invalid feature {feat}. " + f"Use {term.command('feature')} to list the available features.") conflicts = check_conflicts(feat, options.features) - if conflicts is not None: + if len(conflicts) > 0: res = ", ".join(conflicts) log.error(f"Feature {feat} conflicts with the following features: {res}") features.append(f) diff --git a/src/moonforgecli/machine.py b/src/moonforgecli/machine.py index ad399f9..dde28dd 100644 --- a/src/moonforgecli/machine.py +++ b/src/moonforgecli/machine.py @@ -37,9 +37,10 @@ def run(options) -> int: machines = [] for machine in options.machines: - m = get_machine(machine) - if m is None: - log.error(f"Invalid machine {machine}.") + try: + m = get_machine(machine) + except IndexError as err: + log.error(f"{err}") machines.append(m) for machine in machines: res = [] diff --git a/src/moonforgecli/machines/__init__.py b/src/moonforgecli/machines/__init__.py index 6cf2b84..444b720 100644 --- a/src/moonforgecli/machines/__init__.py +++ b/src/moonforgecli/machines/__init__.py @@ -49,10 +49,10 @@ def available_machines() -> list[Machine]: ] -def get_machine(name: str) -> Machine | None: +def get_machine(name: str) -> Machine: for machine in available_machines(): if name == "default" and machine.default: return machine if machine.name == name: return machine - return None + raise IndexError(f"Machine {name} not found") From f85fb2023ab5b0320886c1f9ca00ba981c5d4dbf Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 14:27:26 +0100 Subject: [PATCH 09/15] kas: Fix type signatures Handle nullability and empty lists. --- src/moonforgecli/kas.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/moonforgecli/kas.py b/src/moonforgecli/kas.py index 229b2b7..0db7661 100644 --- a/src/moonforgecli/kas.py +++ b/src/moonforgecli/kas.py @@ -12,7 +12,7 @@ @dataclass class KasInclude: """Class for kas include directives.""" - repo: str + repo: str | None file: str @@ -26,7 +26,8 @@ class KasHeader: @dataclass class KasFragment: section: str - text: list[str] + key: str + value: str weight: int = 0 @@ -37,7 +38,7 @@ class KasRepo: url: str | None = None commit: str | None = None branch: str | None = None - layers: list[str] = field(default_factory=list) + layers: list[str] | None = None class KasFile: @@ -47,15 +48,14 @@ def __init__(self): self._repos: list[KasRepo] = [] self._distro: str | None = None self._machine: Machine | None = None - self._features: list[Feature] | None = [] - self._variables: dict[str, str] | None = {} + self._features: list[Feature] = [] + self._variables: dict[str, str] = {} self._local_conf: list[KasFragment] = [] - self._wks: list[KasFragment] = [] def add_include(self, repo: str | None, file: str) -> None: self._header.includes.append(KasInclude(repo, file)) - def add_repo(self, name: str, url: str, commit: str | None = None, branch: str = None) -> None: + def add_repo(self, name: str, url: str, commit: str | None = None, branch: str | None = None) -> None: for r in self._repos: if r.name == name: log.debug(f"Repository {r.name} (url: {r.url}) already exists") @@ -148,7 +148,7 @@ def _build_repos(self) -> list[str]: output.append(f" commit: {r.commit}") if r.branch is not None: output.append(f" branch: {r.branch}") - if len(r.layers) > 0: + if r.layers is not None and len(r.layers) > 0: output.append(" layers:") for layer in r.layers: output.append(f" {layer}:") @@ -185,10 +185,9 @@ def _build_local_conf(self) -> list[str]: effective_fragments = sorted(seen_keys.values(), key=lambda frag: frag.weight) # Split fragments into sections - sections = {} + sections: dict[str, list[KasFragment]] = {} for frag in effective_fragments: - section = sections.setdefault(f"{frag.weight}_{frag.section}", []) - section.append(frag) + sections.setdefault(f"{frag.weight}_{frag.section}", []).append(frag) output = [] if len(sections) > 0: From ef65fbd4b0a3c121c9ff5592ceb64e6b255fef4e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 15:02:44 +0100 Subject: [PATCH 10/15] ci: Install latest versions of flake8 and mypy --- .github/workflows/python-lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index 067bed5..7939992 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -18,7 +18,8 @@ jobs: - name: Set up environment run: | sudo apt update - sudo apt install python3-pip python3-flake8 python3-mypy + sudo apt install python3-pip + sudo pip3 install --break-system-packages flake8 mypy - name: Flake8 run: | From cc09b9e6d83ef22a970f69baa2ec99f9dc0fe992 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 16:23:23 +0100 Subject: [PATCH 11/15] Ignore a few flake warnings --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 593d37f..95ff315 100644 --- a/.flake8 +++ b/.flake8 @@ -5,4 +5,4 @@ max-line-length = 140 doctests = True exclude = .git, .eggs, __pycache__, build/, dist/ -#ignore = +ignore = F824, W504 From 7e6233e90f4d6671199fdb0315f6aa3cc8e3376e Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 16:23:41 +0100 Subject: [PATCH 12/15] Reformat machine and feature definitions Use black to reformat the code to fit in with Python's formal style. --- src/moonforgecli/features/__init__.py | 3 + src/moonforgecli/features/docker.py | 14 ++- src/moonforgecli/features/graphics_weston.py | 16 ++- src/moonforgecli/features/graphics_wpe.py | 44 ++++--- src/moonforgecli/features/podman.py | 14 ++- src/moonforgecli/features/rauc_simple.py | 117 +++++++++++-------- src/moonforgecli/machines/__init__.py | 2 + src/moonforgecli/machines/qemu.py | 48 ++++---- src/moonforgecli/machines/raspberrypi5.py | 48 +++++--- 9 files changed, 183 insertions(+), 123 deletions(-) diff --git a/src/moonforgecli/features/__init__.py b/src/moonforgecli/features/__init__.py index 1fa2433..0a3c035 100644 --- a/src/moonforgecli/features/__init__.py +++ b/src/moonforgecli/features/__init__.py @@ -7,6 +7,7 @@ @dataclass class FeatureFragment: """Class for weighted template fragments.""" + section: str key: str value: str @@ -44,6 +45,7 @@ class FeatureRepo: @dataclass class Feature: """Class for feature templates.""" + name: str description: str includes: list[FeatureInclude] = field(default_factory=list) @@ -59,6 +61,7 @@ def available_features() -> list[Feature]: from .graphics_wpe import GRAPHICS_WPE_FEATURE from .podman import PODMAN_FEATURE from .rauc_simple import RAUC_FEATURE + return [ DOCKER_FEATURE, GRAPHICS_WESTON_FEATURE, diff --git a/src/moonforgecli/features/docker.py b/src/moonforgecli/features/docker.py index a49c482..02335ee 100644 --- a/src/moonforgecli/features/docker.py +++ b/src/moonforgecli/features/docker.py @@ -4,9 +4,11 @@ from . import Feature, FeatureInclude -DOCKER_FEATURE = Feature(name="docker", - description="Container support using Docker", - includes=[ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-docker.yml") - ], - conflicts=["podman"]) +DOCKER_FEATURE = Feature( + name="docker", + description="Container support using Docker", + includes=[ + FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-docker.yml") + ], + conflicts=["podman"], +) diff --git a/src/moonforgecli/features/graphics_weston.py b/src/moonforgecli/features/graphics_weston.py index 3031847..1959f23 100644 --- a/src/moonforgecli/features/graphics_weston.py +++ b/src/moonforgecli/features/graphics_weston.py @@ -4,9 +4,13 @@ from . import Feature, FeatureInclude -GRAPHICS_WESTON_FEATURE = Feature(name="graphics-weston", - description="Graphics support, using Weston", - includes=[ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-graphics.yml") - ], - conflicts=['graphics-wpe']) +GRAPHICS_WESTON_FEATURE = Feature( + name="graphics-weston", + description="Graphics support, using Weston", + includes=[ + FeatureInclude( + "meta-moonforge", "kas/include/layer/meta-moonforge-graphics.yml" + ) + ], + conflicts=["graphics-wpe"], +) diff --git a/src/moonforgecli/features/graphics_wpe.py b/src/moonforgecli/features/graphics_wpe.py index cb93d6d..edb6712 100644 --- a/src/moonforgecli/features/graphics_wpe.py +++ b/src/moonforgecli/features/graphics_wpe.py @@ -4,21 +4,29 @@ from . import Feature, FeatureFragment, FeatureInclude, FeatureVariable -GRAPHICS_WPE_FEATURE = Feature(name="graphics-wpe", - description="Web Platform for Embedded", - includes=[ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-graphics.yml"), - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-wpe.yml"), - ], - local_conf=[ - FeatureFragment(section="meta-moonforge-wpe", - weight=30, - key="WAYLAND_COG_LAUNCH_URL", - value="http://10.0.2.2:8080"), - ], - conflicts=['graphics-weston'], - variables=[ - FeatureVariable(name="WAYLAND_COG_LAUNCH_URL", - description="The URL to display at boot", - default="http://10.0.2.2:8080"), - ]) +GRAPHICS_WPE_FEATURE = Feature( + name="graphics-wpe", + description="Web Platform for Embedded", + includes=[ + FeatureInclude( + "meta-moonforge", "kas/include/layer/meta-moonforge-graphics.yml" + ), + FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-wpe.yml"), + ], + local_conf=[ + FeatureFragment( + section="meta-moonforge-wpe", + weight=30, + key="WAYLAND_COG_LAUNCH_URL", + value="http://10.0.2.2:8080", + ), + ], + conflicts=["graphics-weston"], + variables=[ + FeatureVariable( + name="WAYLAND_COG_LAUNCH_URL", + description="The URL to display at boot", + default="http://10.0.2.2:8080", + ), + ], +) diff --git a/src/moonforgecli/features/podman.py b/src/moonforgecli/features/podman.py index 2403269..c1b033b 100644 --- a/src/moonforgecli/features/podman.py +++ b/src/moonforgecli/features/podman.py @@ -4,9 +4,11 @@ from . import Feature, FeatureInclude -PODMAN_FEATURE = Feature(name="podman", - description="Container support using Podman", - includes=[ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-podman.yml") - ], - conflicts=["docker"]) +PODMAN_FEATURE = Feature( + name="podman", + description="Container support using Podman", + includes=[ + FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-podman.yml") + ], + conflicts=["docker"], +) diff --git a/src/moonforgecli/features/rauc_simple.py b/src/moonforgecli/features/rauc_simple.py index 68ec9c7..8fc5831 100644 --- a/src/moonforgecli/features/rauc_simple.py +++ b/src/moonforgecli/features/rauc_simple.py @@ -4,51 +4,72 @@ from . import Feature, FeatureFragment, FeatureInclude, FeatureVariable -RAUC_FEATURE = Feature(name="rauc-simple", - description="RAUC support with update bundles via update script", - includes=[ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-rauc-update.yml"), - ], - local_conf=[ - FeatureFragment(section="meta-moonforge-rauc-update", - weight=40, - key="RAUC_BUNDLE_URL", - value="http://10.0.2.2:3333/LATEST.raucb"), - FeatureFragment(section="meta-moonforge-rauc-update", - weight=40, - key="RAUC_FORCE_REBOOT_ON_UPDATE", - value="1"), - ], - machine_overrides={ - "local_conf": { - "qemux86-64": [ - FeatureFragment(section="meta-moonforge-rauc-qemu", - weight=40, - key="WKS_FILE", - value="moonforge-image-rauc-qemux86-64.wks.in"), - ], - "raspberrypi5": [ - FeatureFragment(section="meta-moonforge-rauc-raspberry", - weight=40, - key="WKS_FILE", - value="moonforge-image-rauc-raspberrypi.wks.in"), - FeatureFragment(section="meta-moonforge-distro", - weight=20, - key="OVERLAYFS_ETC_DEVICE", - value="/dev/mmcblk0p4"), - ], - }, - "includes": { - "qemux86-64": [ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-rauc-qemu.yml"), - ], - "raspberrypi5": [ - FeatureInclude("meta-moonforge", "kas/include/layer/meta-moonforge-rauc-raspberrypi.yml"), - ], - }, - }, - variables=[ - FeatureVariable(name="RAUC_FORCE_REBOOT_ON_UPDATE", - description="Reboot on update", - default="1"), - ]) +RAUC_FEATURE = Feature( + name="rauc-simple", + description="RAUC support with update bundles via update script", + includes=[ + FeatureInclude( + "meta-moonforge", "kas/include/layer/meta-moonforge-rauc-update.yml" + ), + ], + local_conf=[ + FeatureFragment( + section="meta-moonforge-rauc-update", + weight=40, + key="RAUC_BUNDLE_URL", + value="http://10.0.2.2:3333/LATEST.raucb", + ), + FeatureFragment( + section="meta-moonforge-rauc-update", + weight=40, + key="RAUC_FORCE_REBOOT_ON_UPDATE", + value="1", + ), + ], + machine_overrides={ + "local_conf": { + "qemux86-64": [ + FeatureFragment( + section="meta-moonforge-rauc-qemu", + weight=40, + key="WKS_FILE", + value="moonforge-image-rauc-qemux86-64.wks.in", + ), + ], + "raspberrypi5": [ + FeatureFragment( + section="meta-moonforge-rauc-raspberry", + weight=40, + key="WKS_FILE", + value="moonforge-image-rauc-raspberrypi.wks.in", + ), + FeatureFragment( + section="meta-moonforge-distro", + weight=20, + key="OVERLAYFS_ETC_DEVICE", + value="/dev/mmcblk0p4", + ), + ], + }, + "includes": { + "qemux86-64": [ + FeatureInclude( + "meta-moonforge", "kas/include/layer/meta-moonforge-rauc-qemu.yml" + ), + ], + "raspberrypi5": [ + FeatureInclude( + "meta-moonforge", + "kas/include/layer/meta-moonforge-rauc-raspberrypi.yml", + ), + ], + }, + }, + variables=[ + FeatureVariable( + name="RAUC_FORCE_REBOOT_ON_UPDATE", + description="Reboot on update", + default="1", + ), + ], +) diff --git a/src/moonforgecli/machines/__init__.py b/src/moonforgecli/machines/__init__.py index 444b720..983910d 100644 --- a/src/moonforgecli/machines/__init__.py +++ b/src/moonforgecli/machines/__init__.py @@ -7,6 +7,7 @@ @dataclass class MachineFragment: """Class for weighted template fragments.""" + section: str key: str value: str @@ -31,6 +32,7 @@ class MachineRepo: @dataclass class Machine: """Class for machine templates.""" + name: str description: str includes: list[MachineInclude] = field(default_factory=list) diff --git a/src/moonforgecli/machines/qemu.py b/src/moonforgecli/machines/qemu.py index 81e7fc4..e289f81 100644 --- a/src/moonforgecli/machines/qemu.py +++ b/src/moonforgecli/machines/qemu.py @@ -4,23 +4,31 @@ from . import Machine, MachineFragment, MachineInclude -QEMU_MACHINE = Machine(name="qemux86-64", - description="QEMU x86_64 builds", - includes=[ - MachineInclude("meta-moonforge", "kas/include/layer/meta-moonforge-qemu.yml") - ], - local_conf=[ - MachineFragment(section="meta-moonforge-distro", - weight=20, - key="OVERLAYFS_ETC_DEVICE", - value="/dev/sda4"), - MachineFragment(section="meta-moonforge-distro", - weight=20, - key="IMAGE_DATA_MIN_SIZE", - value="4096M"), - MachineFragment(section="meta-moonforge-qemu", - weight=20, - key="WKS_FILE", - value="moonforge-image-base-qemux86-64.wks.in"), - ], - default=True) +QEMU_MACHINE = Machine( + name="qemux86-64", + description="QEMU x86_64 builds", + includes=[ + MachineInclude("meta-moonforge", "kas/include/layer/meta-moonforge-qemu.yml") + ], + local_conf=[ + MachineFragment( + section="meta-moonforge-distro", + weight=20, + key="OVERLAYFS_ETC_DEVICE", + value="/dev/sda4", + ), + MachineFragment( + section="meta-moonforge-distro", + weight=20, + key="IMAGE_DATA_MIN_SIZE", + value="4096M", + ), + MachineFragment( + section="meta-moonforge-qemu", + weight=20, + key="WKS_FILE", + value="moonforge-image-base-qemux86-64.wks.in", + ), + ], + default=True, +) diff --git a/src/moonforgecli/machines/raspberrypi5.py b/src/moonforgecli/machines/raspberrypi5.py index 64dac42..72b752a 100644 --- a/src/moonforgecli/machines/raspberrypi5.py +++ b/src/moonforgecli/machines/raspberrypi5.py @@ -4,22 +4,32 @@ from . import Machine, MachineFragment, MachineInclude -RPI5_MACHINE = Machine(name="raspberrypi5", - description="RaspberryPi 5 builds", - includes=[ - MachineInclude("meta-moonforge", "kas/include/layer/meta-moonforge-raspberrypi.yml"), - ], - local_conf=[ - MachineFragment(section="meta-moonforge-raspberrypi", - weight=20, - key="WKS_FILE", - value="moonforge-image-base-raspberrypi.wks.in"), - MachineFragment(section="meta-moonforge-distro", - weight=20, - key="OVERLAYFS_ETC_DEVICE", - value="/dev/mmcblk0p3"), - MachineFragment(section="meta-moonforge-distro", - weight=20, - key="IMAGE_DATA_MIN_SIZE", - value="4096M"), - ]) +RPI5_MACHINE = Machine( + name="raspberrypi5", + description="RaspberryPi 5 builds", + includes=[ + MachineInclude( + "meta-moonforge", "kas/include/layer/meta-moonforge-raspberrypi.yml" + ), + ], + local_conf=[ + MachineFragment( + section="meta-moonforge-raspberrypi", + weight=20, + key="WKS_FILE", + value="moonforge-image-base-raspberrypi.wks.in", + ), + MachineFragment( + section="meta-moonforge-distro", + weight=20, + key="OVERLAYFS_ETC_DEVICE", + value="/dev/mmcblk0p3", + ), + MachineFragment( + section="meta-moonforge-distro", + weight=20, + key="IMAGE_DATA_MIN_SIZE", + value="4096M", + ), + ], +) From 7a3dd3c7a3247b28e6f67a3dbf487ef5404f1f2d Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 18:06:15 +0100 Subject: [PATCH 13/15] ci: Add REUSE lint --- .github/workflows/reuse-lint.yml | 26 ++++++++++++++++++++++++++ REUSE.toml | 18 +++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/reuse-lint.yml diff --git a/.github/workflows/reuse-lint.yml b/.github/workflows/reuse-lint.yml new file mode 100644 index 0000000..be08489 --- /dev/null +++ b/.github/workflows/reuse-lint.yml @@ -0,0 +1,26 @@ +name: License Linting + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: Set up environment + run: | + sudo apt update + sudo apt install python3-pip + sudo pip3 install --break-system-packages reuse + + - name: REUSE + run: | + python3 -m reuse lint diff --git a/REUSE.toml b/REUSE.toml index 954a802..2958913 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,28 +1,28 @@ version = 1 SPDX-PackageName = "moonforge-cli" -SPDX-PackageSupplier = "The Moonforge Project" +SPDX-PackageSupplier = "Igalia S.L." SPDX-PackageDownloadLocation = "https://github.com/moonforgelinux/moonforge-cli" [[annotations]] -path = "README.md" +path = ".github/workflows/*.yml" precedence = "aggregate" -SPDX-FileCopyrightText = "2026 Igalia S.L." -SPDX-License-Identifier = "CC0-1.0" +SPDX-FileCopyrightText = "2026 Igalia S.L." +SPDX-License-Identifier = "MIT" [[annotations]] -path = "CHANGELOG.md" +path = ["CHANGELOG.md", "README.md"] precedence = "aggregate" -SPDX-FileCopyrightText = "2026 Igalia S.L." +SPDX-FileCopyrightText = "2026 Igalia S.L." SPDX-License-Identifier = "CC0-1.0" [[annotations]] path = "pyproject.toml" precedence = "aggregate" -SPDX-FileCopyrightText = "2026 Igalia S.L." -SPDX-License-Identifier = "CC0-1.0" +SPDX-FileCopyrightText = "2026 Igalia S.L." +SPDX-License-Identifier = "MIT" [[annotations]] path = "src/moonforgecli/.gitignore" precedence = "aggregate" -SPDX-FileCopyrightText = "2026 Igalia S.L." +SPDX-FileCopyrightText = "2026 Igalia S.L." SPDX-License-Identifier = "CC0-1.0" From 417fb00d3797264e89ad90c98dd6369854630a73 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 18:07:13 +0100 Subject: [PATCH 14/15] kas: Annotate initializer --- src/moonforgecli/kas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/moonforgecli/kas.py b/src/moonforgecli/kas.py index 0db7661..750343f 100644 --- a/src/moonforgecli/kas.py +++ b/src/moonforgecli/kas.py @@ -43,7 +43,7 @@ class KasRepo: class KasFile: """Class for kas files.""" - def __init__(self): + def __init__(self) -> None: self._header: KasHeader = KasHeader() self._repos: list[KasRepo] = [] self._distro: str | None = None From 03ccda37eb61d0247d47dfccb4dd45c3310a33b2 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 5 May 2026 18:11:47 +0100 Subject: [PATCH 15/15] init: Fix variables annotation --- src/moonforgecli/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/moonforgecli/init.py b/src/moonforgecli/init.py index d0c53e2..ca2301b 100644 --- a/src/moonforgecli/init.py +++ b/src/moonforgecli/init.py @@ -94,7 +94,7 @@ def sanitize_project_name(name: str) -> str: class Project: - def __init__(self, name: str, path: Path, machine: Machine, features: list[Feature], variables: dict[str], vcs: str): + def __init__(self, name: str, path: Path, machine: Machine, features: list[Feature], variables: dict[str, str], vcs: str) -> None: self._name = name self._path = path self._machine = machine