mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-09 13:34:14 +02:00
qet-mcp: run the original QElectroTech on macOS, not a private copy (#1178)
Every headless call failed with the macOS package: _launch_executable() copied the program out of the signed .app bundle, and macOS kills such a copy at launch (exit 137). The copy has also lost the path to the bundle's Qt frameworks. Windows already ran the original for the same kind of reason (DLLs beside the program); macOS now does too. That is safe for the same reason as on Windows: the export flags and --run return before QElectroTech constructs SingleApplication, so there is no running instance to hand the call to. Linux keeps the private copy. Reported and tested by DarkBiscuits on macOS 26.7 with the r10796 DMG: with this one change and QT_QPA_PLATFORM=offscreen, every tool works headless with no Dock icon. test_qet_mcp: 421 tests pass on Linux against a master build with QET_BINARY/QET_ELEMENTS/QET_EXAMPLES and scripting on, none skipped; test_which_systems_copy fails with darwin removed from the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+24
-11
@@ -896,19 +896,32 @@ def tool_element_info(path: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _launch_executable(src: Path, sandbox: Path, windows: bool) -> Path:
|
||||
"""The executable _run_qet() starts: a private copy, except on Windows.
|
||||
def _copies_executable(platform: str = sys.platform) -> bool:
|
||||
"""Whether _run_qet() runs a private copy of QElectroTech on @p platform:
|
||||
not on Windows or macOS (see _launch_executable())."""
|
||||
return platform not in ("win32", "darwin")
|
||||
|
||||
|
||||
def _launch_executable(src: Path, sandbox: Path, copy: bool) -> Path:
|
||||
"""The executable _run_qet() starts: a private copy when @p copy is set,
|
||||
otherwise the original.
|
||||
|
||||
The copy gives each run its own SingleApplication key, which is derived
|
||||
from the executable's path. On Windows a program loads its DLLs from its
|
||||
own folder, so a copy on its own dies before main() (0xC0000135, DLL not
|
||||
found) and nothing could ever be exported or edited there. Run the
|
||||
original instead: every flag this server passes is a CLI export flag or
|
||||
--run, and QElectroTech handles both and returns before it constructs
|
||||
SingleApplication (main.cpp), so there is no instance to be handed to.
|
||||
The copy stays elsewhere for builds from before that early return.
|
||||
from the executable's path. Two systems cannot run a copy:
|
||||
|
||||
- Windows: a program loads its DLLs from its own folder, so a copy on
|
||||
its own dies before main() (0xC0000135, DLL not found).
|
||||
- macOS: a copy taken out of the signed .app bundle is killed at launch
|
||||
(exit 137) and has lost the path to the bundle's Qt frameworks
|
||||
(qelectrotech-source-mirror#1178).
|
||||
|
||||
There, run the original instead: every flag this server passes is a CLI
|
||||
export flag or --run, and QElectroTech handles both and returns before
|
||||
it constructs SingleApplication (main.cpp), so there is no instance to
|
||||
be handed to. The copy stays elsewhere for builds from before that
|
||||
early return.
|
||||
"""
|
||||
if windows:
|
||||
if not copy:
|
||||
return src
|
||||
exe = sandbox / f"qet-mcp-{os.getpid()}"
|
||||
shutil.copy2(src, exe)
|
||||
@@ -996,7 +1009,7 @@ def _run_qet(binary: str, args: list[str], timeout: int = 180,
|
||||
raise ValueError(f"not an executable: {src}")
|
||||
with tempfile.TemporaryDirectory(prefix="qet-mcp-") as tmp:
|
||||
sandbox = Path(tmp)
|
||||
exe = _launch_executable(src, sandbox, os.name == "nt")
|
||||
exe = _launch_executable(src, sandbox, _copies_executable())
|
||||
home = sandbox / "home"
|
||||
(home / ".config").mkdir(parents=True)
|
||||
(home / ".local" / "share").mkdir(parents=True)
|
||||
|
||||
@@ -3045,7 +3045,8 @@ class BinaryPolicy(unittest.TestCase):
|
||||
|
||||
class LaunchExecutable(unittest.TestCase):
|
||||
"""Windows cannot run a lone copy of QElectroTech (F065): its DLLs sit
|
||||
beside the original. Everywhere else the private copy stays."""
|
||||
beside the original. Nor can macOS: a copy out of the signed .app is
|
||||
killed at launch (#1178). Everywhere else the private copy stays."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
@@ -3059,9 +3060,14 @@ class LaunchExecutable(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_windows_runs_the_original(self):
|
||||
self.assertEqual(m._launch_executable(self.src, self.sandbox, True), self.src)
|
||||
self.assertEqual(list(self.sandbox.iterdir()), [], "nothing is copied on Windows")
|
||||
def test_windows_and_macos_run_the_original(self):
|
||||
self.assertEqual(m._launch_executable(self.src, self.sandbox, False), self.src)
|
||||
self.assertEqual(list(self.sandbox.iterdir()), [], "nothing is copied")
|
||||
|
||||
def test_which_systems_copy(self):
|
||||
self.assertFalse(m._copies_executable("win32"))
|
||||
self.assertFalse(m._copies_executable("darwin"))
|
||||
self.assertTrue(m._copies_executable("linux"))
|
||||
|
||||
def test_windows_keeps_its_own_qt_platform(self):
|
||||
"""The Windows packages have no offscreen plugin; asking for it
|
||||
@@ -3083,7 +3089,7 @@ class LaunchExecutable(unittest.TestCase):
|
||||
self.assertEqual(env["QET_SETTINGS_DIR"], str(self.sandbox / ".config"))
|
||||
|
||||
def test_elsewhere_runs_a_private_copy(self):
|
||||
exe = m._launch_executable(self.src, self.sandbox, False)
|
||||
exe = m._launch_executable(self.src, self.sandbox, True)
|
||||
self.assertEqual(exe.parent, self.sandbox)
|
||||
self.assertNotEqual(exe, self.src)
|
||||
self.assertEqual(exe.read_text(), self.src.read_text())
|
||||
|
||||
Reference in New Issue
Block a user