[sdk/kde-builder] kde_builder_lib: fix: Do not derive BuildContext from Module

Andrew Shark <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 932c53b2bcc57edde2893859b13e17fdc242a44a by Andrew Shark.
Committed on 31/07/2026 at 19:03.
Pushed by ashark into branch 'master'.

fix: Do not derive BuildContext from Module

See #131

M  +1    -1    kde_builder_lib/application.py
M  +13   -27   kde_builder_lib/build_context.py
M  +11   -73   kde_builder_lib/module/module.py
M  +1    -1    kde_builder_lib/module_set/module_set.py
M  +61   -0    kde_builder_lib/options_base.py

https://invent.kde.org/sdk/kde-builder/-/commit/932c53b2bcc57edde2893859b13e17fdc242a44a

diff --git a/kde_builder_lib/application.py b/kde_builder_lib/application.py
index ad0342f5..ec787955 100644
--- a/kde_builder_lib/application.py
+++ b/kde_builder_lib/application.py
@@ -737,7 +737,7 @@ class Application:
             ctx.merge_options_from(global_opts)
 
         # Now we have determined log-dir, and can set screen log file.
-        Debug().set_log_file(ctx.get_log_dir_for(ctx) + "/screen.log")
+        Debug().set_log_file(ctx.get_log_dir() + "/screen.log")
 
         # Now, after global options were resolved and set, we can resolve paths in include lines and read those config files.
         node_reader = RecursiveConfigNodesIterator(config_content, rcfile, ctx)
diff --git a/kde_builder_lib/build_context.py b/kde_builder_lib/build_context.py
index 64a817e1..fbbc9502 100644
--- a/kde_builder_lib/build_context.py
+++ b/kde_builder_lib/build_context.py
@@ -24,7 +24,7 @@ from .metadata.kde_projects_reader import KDEProjectsReader
 from .metadata.metadata import Metadata
 from .module.branch_group_resolver import ModuleBranchGroupResolver
 from .module.module import Module
-from .options_base import OptionsBase
+from .options_base import PathResolvingOptions
 from .phase_list import PhaseList
 from .status_view import StatusView
 from .util.util import Util
@@ -33,10 +33,7 @@ from .util.textwrap_mod import dedent
 logger_buildcontext = KBLogger.getLogger("build-context")
 
 
-# We derive from Module so that BuildContext acts like the "global"
-# Module, with some extra functionality.
-# TODO: Derive from OptionsBase directly and remove get_option override
-class BuildContext(Module):
+class BuildContext(PathResolvingOptions):
     """
     Contains the information needed about the build context, e.g. list of modules, what phases each module is in, the various options, etc.
 
@@ -168,7 +165,6 @@ class BuildContext(Module):
         self.modules: list[Module] = []
         """List of modules to build."""
 
-        self.context = self  # Fix link to buildContext (i.e. self)
         self.build_options = {
             "global": {
                 **self.global_options_private,
@@ -336,7 +332,11 @@ class BuildContext(Module):
         except Exception as e:
             logger_buildcontext.warning(f" y[*] Failed to close lock: {e}")
 
-    def get_log_dir_for(self, module: Module) -> str:
+    # @override
+    def get_log_dir(self) -> str:
+        return self.get_log_dir_for(self)
+
+    def get_log_dir_for(self, module: PathResolvingOptions) -> str:
         """
         Return the log directory of specified module.
 
@@ -372,7 +372,11 @@ class BuildContext(Module):
 
         return log_dir
 
-    def get_log_path_for(self, module: Module, path: str) -> str:
+    # @override
+    def get_log_path(self, path: str) -> str:
+        return self.get_log_path_for(self, path)
+
+    def get_log_path_for(self, module: PathResolvingOptions, path: str) -> str:
         """
         Return the absolute filename to open() for a log file for this module based on the given basename (including extensions). Update the "latest" symlink.
 
@@ -560,21 +564,6 @@ class BuildContext(Module):
         modules = [module for module in modules if module.name in self.errors]
         return modules
 
-    # @override(check_signature=False)
-    def get_option(self, key: str) -> str | dict | list | bool:
-        """
-        Get context option.
-
-        Our immediate parent class Module overrides this, but we actually
-        want the OptionsBase version to be used instead, until we break the recursive
-        use of Module's own get_option calls on our get_option.
-
-        Returns:
-             The same types that OptionsBase.get_option returns.
-        """
-        ret = OptionsBase.get_option(self, key)
-        return ret
-
     # @override
     def set_option(self, opt_name: str, opt_val) -> None:
 
@@ -584,10 +573,7 @@ class BuildContext(Module):
                 self.phases.filter_out_phase(phase)
             return
 
-        # Our immediate parent class Module overrides this, but we actually
-        # want the OptionsBase version to be used instead, because Module's version specifically checks for
-        # some options prohibited for it (such as "ignore-projects") but we may want such for BuildContext.
-        OptionsBase.set_option(self, opt_name, opt_val)
+        super().set_option(opt_name, opt_val)
 
         # Automatically respond to various global option changes.
         if opt_name == "colorful-output":
diff --git a/kde_builder_lib/module/module.py b/kde_builder_lib/module/module.py
index e2f4d32b..65da41db 100644
--- a/kde_builder_lib/module/module.py
+++ b/kde_builder_lib/module/module.py
@@ -28,7 +28,7 @@ from ..build_system.qmake6 import BuildSystemQMake6
 from ..debug import Debug
 from ..debug import KBLogger
 from ..ipc.ipc import IPC
-from ..options_base import OptionsBase
+from ..options_base import PathResolvingOptions
 from ..updater.updater import Updater
 from ..util.util import Util
 from ..util.textwrap_mod import dedent
@@ -41,7 +41,7 @@ if TYPE_CHECKING:
 logger_module = KBLogger.getLogger("module")
 
 
-class Module(OptionsBase):
+class Module(PathResolvingOptions):
     """
     Represents a source code module of some sort that can be updated, built, tested and installed.
 
@@ -75,14 +75,8 @@ class Module(OptionsBase):
         It is used in _compare_build_order_depends() as a pre-last way of ordering modules for building.
         """
 
-        # If building a BuildContext instead of a `Module`, then the context
-        # can't have been set up yet...
-        if self.__class__.__name__ != "BuildContext" and ctx.__class__.__name__ != "BuildContext":
-            raise ProgramError(f"Invalid context {ctx}")
-
-        phases = None
-        if ctx:
-            phases = copy.copy(ctx.phases)
+        assert ctx
+        phases = copy.copy(ctx.phases)
         self.phases: PhaseList = phases
 
         self.scm: Updater = Updater(self)
@@ -94,16 +88,10 @@ class Module(OptionsBase):
         self.current_phase: str | None = None
         """For customizing behavior depending on the phase."""
 
-        if self.__class__.__name__ != "BuildContext":
-            # Avoid setting this for BuildContext, because it has its own option value type verification code, which needs BuildContext to be already initialized
-            # (for reading self.all_boolean_options).
-            # But currently, BuildContext is inherited from Module, so we initialize Module first (as part of BuildContext initialization).
-            # TODO make a proper inheritance scheme. The BuildContext and Module most likely should be inherited from common abstract class.
-
-            # Record current values of what would be last source/build dir, if present,
-            # before they are potentially reset during the module build.
-            self.set_option("#last-source-dir", self.get_persistent_option("source-dir") or "")
-            self.set_option("#last-build-dir", self.get_persistent_option("build-dir") or "")
+        # Record current values of what would be last source/build dir, if present,
+        # before they are potentially reset during the module build.
+        self.set_option("#last-source-dir", self.get_persistent_option("source-dir") or "")
+        self.set_option("#last-build-dir", self.get_persistent_option("build-dir") or "")
 
     def __str__(self) -> str:  # Add stringify operator.
         return self.name
@@ -114,46 +102,6 @@ class Module(OptionsBase):
         """
         self.module_set = module_set
 
-    def get_absolute_path(self, option_name: str) -> str:
-        """
-        Converts the value of path-like option to an absolute path. Non-absolute paths in option values are treated as subdirs of source-dir.
-
-        Handles tilde expansion and relative paths.
-
-        Args:
-            option_name: The option key (e.g. "build-dir" or "log-dir") to read and interpret.
-        """
-        directory = self.get_option(option_name)
-
-        # If build-dir starts with a slash, it is an absolute path.
-        if directory.startswith("/"):
-            return directory
-
-        # Make sure we got a valid option result.
-        if not directory:
-            raise ValueError(f"Reading option for {option_name} gave empty directory!")
-
-        # If it starts with a tilde, expand it out.
-        if directory.startswith("~"):
-            directory = re.sub(r"^~", os.getenv("HOME"), directory)
-        else:
-            # Relative directory, tack it on to the end of kdesrcdir.
-            kdesrcdir = self.get_option("source-dir")
-            directory = f"{kdesrcdir}/{directory}"
-
-        return directory
-
-    def get_source_dir(self) -> str:
-        """
-        Return absolute base path to the source directory.
-
-        Do note that this returns the *base* path to the source directory,
-        without the module name or kde_projects stuff appended. If you want that, use :meth:`fullpath()`.
-        """
-        return self.get_absolute_path("source-dir")
-
-
-
     def current_scm_revision(self) -> str:
         """
         Return a string with scm-specific revision ID.
@@ -504,22 +452,12 @@ class Module(OptionsBase):
         for key, value in module_set_env_dict.items():
             self.queue_environment_variable(key, value)
 
+    # @override
     def get_log_dir(self) -> str:
-        """
-        Return the base path to the log directory for this module during this execution.
-
-        **NOTE** Different modules can have different base paths.
-        The dir name is based on an autogenerated unique id. The id doesn't change once generated within a single run of the kde-builder.
-        """
         return self.context.get_log_dir_for(self)
 
+    # @override
     def get_log_path(self, path: str) -> str:
-        """
-        Return a full path that can be open()'d to write a log file, based on the given basename (with extension).
-
-        Updates the "latest" symlink as well, unlike get_log_dir
-        Use when you know you're going to create a new log
-        """
         return self.context.get_log_path_for(self, path)
 
     def update(self, ipc: IPC, ctx: BuildContext) -> bool:
@@ -665,7 +603,7 @@ class Module(OptionsBase):
                 self.phases.clear()
             return
 
-        OptionsBase.set_option(self,  opt_name, opt_val)
+        super().set_option(opt_name, opt_val)
 
     # @override(check_signature=False)
     def get_option(self, key: str, level_limit="allow-inherit") -> str | dict | None:
diff --git a/kde_builder_lib/module_set/module_set.py b/kde_builder_lib/module_set/module_set.py
index 3a682258..ad6d361a 100644
--- a/kde_builder_lib/module_set/module_set.py
+++ b/kde_builder_lib/module_set/module_set.py
@@ -107,7 +107,7 @@ class ModuleSet(OptionsBase):
             return
 
         # Actually set options.
-        OptionsBase.set_option(self, opt_name, opt_val)
+        super().set_option(opt_name, opt_val)
 
     def convert_to_modules(self) -> list[Module]:
         """
diff --git a/kde_builder_lib/options_base.py b/kde_builder_lib/options_base.py
index f19c56ad..7dafe345 100644
--- a/kde_builder_lib/options_base.py
+++ b/kde_builder_lib/options_base.py
@@ -284,3 +284,64 @@ class OptionsBase:
         key = "#defined-at"
         sources = self.get_option(key) or []
         return ", ".join(sources)
+
+
+class PathResolvingOptions(OptionsBase):
+    """
+    Common methods for BuildContext and Module, but not for ModuleSet.
+    """
+
+    def get_log_dir(self) -> str:
+        """
+        Return the base path to the log directory during this execution.
+
+        The dir name is based on an autogenerated unique id. The id doesn't change once generated within a single run of the kde-builder.
+        """
+        raise NotImplementedError("get_log_dir() is not implemented in PathResolvingOptions.")
+
+    def get_log_path(self, path: str) -> str:
+        """
+        Return a full path that can be open()'d to write a log file, based on the given basename (with extension).
+
+        Updates the "latest" symlink as well, unlike get_log_dir().
+        Use when you know you're going to create a new log.
+        """
+        raise NotImplementedError("get_log_path() is not implemented in PathResolvingOptions.")
+
+    def get_absolute_path(self, option_name: str) -> str:
+        """
+        Converts the value of path-like option to an absolute path. Non-absolute paths in option values are treated as subdirs of source-dir.
+
+        Handles tilde expansion and relative paths.
+
+        Args:
+            option_name: The option key (e.g. "build-dir" or "log-dir") to read and interpret.
+        """
+        directory = self.get_option(option_name)
+
+        # If build-dir starts with a slash, it is an absolute path.
+        if directory.startswith("/"):
+            return directory
+
+        # Make sure we got a valid option result.
+        if not directory:
+            raise ValueError(f"Reading option for {option_name} gave empty directory!")
+
+        # If it starts with a tilde, expand it out.
+        if directory.startswith("~"):
+            directory = re.sub(r"^~", os.getenv("HOME"), directory)
+        else:
+            # Relative directory, tack it on to the end of kdesrcdir.
+            kdesrcdir = self.get_option("source-dir")
+            directory = f"{kdesrcdir}/{directory}"
+
+        return directory
+
+    def get_source_dir(self) -> str:
+        """
+        Return absolute base path to the source directory.
+
+        Do note that this returns the *base* path to the source directory,
+        without the module name or kde_projects stuff appended. If you want that, use :meth:`fullpath()`.
+        """
+        return self.get_absolute_path("source-dir")
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.