Skip to content

Commit 7a922ad

Browse files
committed
Refactor configuration management by merging Core.ArkConfig and Core.UserConfig into Core.Configs
- Introduced Core.Configs as a unified configuration module for PyCompiler ARK. - Merged workspace/project configuration and user-level persistent settings into a single package. - Updated all relevant imports across the codebase to reflect the new configuration structure. - Added backward-compatibility shims in Core.ArkConfig and Core.UserConfig to ensure existing code continues to function. - Implemented new engine runner functionality in Core.Compiler.engine_runner for better compilation command resolution and execution. - Enhanced MainProcess to support compilation from a BuildContext, delegating to the new engine runner. - Updated various modules and tests to accommodate the refactored configuration and engine execution logic.
1 parent e80e214 commit 7a922ad

18 files changed

Lines changed: 998 additions & 612 deletions

Core/ArkConfig/__init__.py

Lines changed: 26 additions & 471 deletions
Large diffs are not rendered by default.

Core/Compiler/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@
6262
get_process_info,
6363
)
6464

65+
from Core.Compiler.engine_runner import (
66+
BuildContext,
67+
EngineRunnerError,
68+
resolve_engine_command,
69+
run_engine_compile,
70+
)
71+
6572
from EngineLoader.registry import get_engine, create
6673

6774
__all__ = [
@@ -86,6 +93,11 @@
8693
"kill_process",
8794
"kill_process_tree",
8895
"get_process_info",
96+
# engine_runner.py — source of truth for compilation
97+
"BuildContext",
98+
"EngineRunnerError",
99+
"resolve_engine_command",
100+
"run_engine_compile",
89101
# Business orchestration helpers (used by Ui/Gui/Dialogs/CompilerDialog.py)
90102
"_get_main_process",
91103
"_resolve_default_engine_id",

Core/Compiler/engine_runner.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2026 Ague Samuel Amen
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""
17+
Engine Runner — pure-Python, Qt-free compilation executor.
18+
19+
Source of truth for how ARK loads an engine and runs a compilation
20+
against a BuildContext. Both the CLI (Ui/Cli/spec_helpers.py) and the
21+
Qt async path (MainProcess.compile_from_context) delegate to this module.
22+
23+
Provides:
24+
- `resolve_engine_command` — load an engine and derive (program, args)
25+
- `run_engine_compile` — full synchronous compilation pipeline
26+
"""
27+
28+
from __future__ import annotations
29+
30+
import subprocess
31+
from pathlib import Path
32+
from typing import Any
33+
34+
from Core.process_security import hardened_popen_kwargs, secure_command
35+
36+
# BuildContext lives in engine_sdk; imported here so callers of this module
37+
# only need to import from Core.Compiler.
38+
from engine_sdk.build_context import BuildContext
39+
40+
41+
class EngineRunnerError(RuntimeError):
42+
"""Raised when an engine cannot be loaded or does not produce a command."""
43+
44+
45+
def resolve_engine_command(
46+
engine_id: str,
47+
context: BuildContext,
48+
engine_config: dict[str, Any] | None = None,
49+
) -> tuple[str, list[str]]:
50+
"""
51+
Load *engine_id* and derive the (program, args) pair for *context*.
52+
53+
Args:
54+
engine_id: Registered engine identifier (e.g. ``"pyinstaller"``).
55+
context: BuildContext describing the project.
56+
engine_config: Optional per-engine config overrides.
57+
58+
Returns:
59+
A ``(program, args)`` tuple ready to be passed to subprocess.
60+
61+
Raises:
62+
EngineRunnerError: When the engine cannot be loaded, does not support
63+
BuildContext builds, or returns an empty command.
64+
"""
65+
try:
66+
import EngineLoader as engines_loader
67+
engine = engines_loader.create(engine_id)
68+
except Exception as exc:
69+
raise EngineRunnerError(
70+
f"Unable to load engine '{engine_id}': {exc}"
71+
) from exc
72+
73+
try:
74+
setattr(engine, "_config_overrides", dict(engine_config or {}))
75+
except Exception:
76+
pass
77+
78+
try:
79+
resolved = engine.program_and_args_from_context(context)
80+
except NotImplementedError as exc:
81+
raise EngineRunnerError(
82+
f"Engine '{engine_id}' does not support BuildContext builds"
83+
) from exc
84+
except Exception as exc:
85+
raise EngineRunnerError(
86+
f"Engine '{engine_id}' failed to build command: {exc}"
87+
) from exc
88+
89+
if not resolved:
90+
raise EngineRunnerError(f"Engine '{engine_id}' returned no command")
91+
92+
program, args = resolved
93+
return str(program), list(args)
94+
95+
96+
def run_engine_compile(
97+
*,
98+
workspace: Path,
99+
engine_id: str,
100+
context: BuildContext,
101+
engine_config: dict[str, Any] | None = None,
102+
) -> dict[str, Any]:
103+
"""
104+
Execute a compilation synchronously.
105+
106+
This is the **source of truth** for ARK's compilation pipeline.
107+
It validates the entry point, resolves the command via the engine,
108+
applies security hardening, and runs the subprocess.
109+
110+
Args:
111+
workspace: Absolute path to the project workspace.
112+
engine_id: Registered engine identifier.
113+
context: BuildContext describing the project.
114+
engine_config: Optional per-engine config overrides (forwarded to
115+
``engine._config_overrides``).
116+
117+
Returns:
118+
A result dict with the following keys:
119+
120+
``success`` (bool)
121+
Whether ``returncode == 0``.
122+
``return_code`` (int | None)
123+
Process return code, or ``None`` when the process never started.
124+
``command`` (list[str] | None)
125+
The resolved, security-hardened command list, or ``None`` on early
126+
failure.
127+
``stdout`` (str | None)
128+
Captured standard output.
129+
``stderr`` (str | None)
130+
Captured standard error.
131+
``error`` (str | None)
132+
Human-readable error message, or ``None`` on success.
133+
"""
134+
# ── 1. Validate entry point ──────────────────────────────────────────────
135+
entry_path = workspace / Path(context.entry_point)
136+
if not context.entry_point or not entry_path.is_file():
137+
return _failure(
138+
f"Entrypoint missing or obsolete: {context.entry_point}"
139+
)
140+
141+
# ── 2. Resolve (program, args) from engine ───────────────────────────────
142+
try:
143+
program, args = resolve_engine_command(engine_id, context, engine_config)
144+
except EngineRunnerError as exc:
145+
return _failure(str(exc))
146+
147+
# ── 3. Security hardening ────────────────────────────────────────────────
148+
try:
149+
safe_program, safe_args, safe_env = secure_command(
150+
program, args, {"ARK_WORKSPACE": str(workspace)}
151+
)
152+
except Exception as exc:
153+
return _failure(f"Unsafe compile command blocked: {exc}")
154+
155+
# ── 4. Run ───────────────────────────────────────────────────────────────
156+
command = [safe_program] + safe_args
157+
try:
158+
completed = subprocess.run(
159+
command,
160+
cwd=str(workspace),
161+
env=safe_env,
162+
capture_output=True,
163+
text=True,
164+
**hardened_popen_kwargs(),
165+
)
166+
except Exception as exc:
167+
return {
168+
"success": False,
169+
"return_code": None,
170+
"command": command,
171+
"stdout": None,
172+
"stderr": None,
173+
"error": f"Compilation failed to start: {exc}",
174+
}
175+
176+
return {
177+
"success": completed.returncode == 0,
178+
"return_code": completed.returncode,
179+
"command": command,
180+
"stdout": completed.stdout,
181+
"stderr": completed.stderr,
182+
"error": (
183+
None
184+
if completed.returncode == 0
185+
else (completed.stderr.strip() or "Build failed")
186+
),
187+
}
188+
189+
190+
# ── helpers ──────────────────────────────────────────────────────────────────
191+
192+
def _failure(error: str) -> dict[str, Any]:
193+
return {
194+
"success": False,
195+
"return_code": None,
196+
"command": None,
197+
"stdout": None,
198+
"stderr": None,
199+
"error": error,
200+
}
201+
202+
203+
__all__ = [
204+
"BuildContext",
205+
"EngineRunnerError",
206+
"resolve_engine_command",
207+
"run_engine_compile",
208+
]

Core/Compiler/mainprocess.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import subprocess
3434
import shlex
3535
import re
36+
from pathlib import Path
3637
from typing import Optional, Dict, Any, List, Tuple
3738
from enum import Enum
3839

@@ -44,12 +45,18 @@
4445
)
4546

4647
# Importations ArkConfig pour la gestion des exclusions
47-
from Core.ArkConfig import (
48+
from Core.Configs import (
4849
load_ark_config,
4950
should_exclude_file,
5051
DEFAULT_EXCLUSION_PATTERNS,
5152
)
5253

54+
from Core.Compiler.engine_runner import (
55+
resolve_engine_command,
56+
EngineRunnerError,
57+
)
58+
from engine_sdk.build_context import BuildContext
59+
5360

5461
class ProcessState(Enum):
5562
"""Possible states of the main process."""
@@ -389,6 +396,49 @@ def get_compilation_info(self) -> Dict[str, Any]:
389396
"duration": self.compiler.duration,
390397
}
391398

399+
def compile_from_context(
400+
self,
401+
workspace: Path | str,
402+
engine_id: str,
403+
context: BuildContext,
404+
engine_config: Optional[Dict[str, Any]] = None,
405+
) -> bool:
406+
"""
407+
Start an async compilation from a :class:`BuildContext`.
408+
409+
Resolves ``(program, args)`` via the engine, then delegates to
410+
:class:`CompilerCore` (Qt thread) for non-blocking execution.
411+
412+
Args:
413+
workspace: Absolute path to the project workspace.
414+
engine_id: Registered engine identifier.
415+
context: BuildContext describing the project.
416+
engine_config: Optional per-engine config overrides.
417+
418+
Returns:
419+
``True`` if the compilation thread started successfully.
420+
"""
421+
workspace = Path(workspace)
422+
423+
# Resolve command via engine (pure-Python, no Qt)
424+
try:
425+
program, args = resolve_engine_command(engine_id, context, engine_config)
426+
except EngineRunnerError as exc:
427+
self.log_message.emit("error", str(exc))
428+
return False
429+
430+
env = {"ARK_WORKSPACE": str(workspace)}
431+
file_path = str(workspace / context.entry_point)
432+
433+
return self.compile(
434+
program=program,
435+
args=args,
436+
env=env,
437+
engine_id=engine_id,
438+
file_path=file_path,
439+
workspace_dir=str(workspace),
440+
)
441+
392442
def reset(self) -> None:
393443
"""Reset process state for a new compilation."""
394444
self._current_file = None

0 commit comments

Comments
 (0)