[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")