subagent transcript
session 17f09f89 · workflow wf_6c234df9
delegated task
GOAL: port PySide6 + shiboken6 to the FreeCAD WebAssembly toolchain (static, no-dlopen, asyncify, no-pthreads) so FreeCAD's Python/PySide workbenches
▸ Magik
GOAL: port PySide6 + shiboken6 to the FreeCAD WebAssembly toolchain (static, no-dlopen, asyncify,
no-pthreads) so FreeCAD's Python/PySide workbenches and macros work in the browser. This is the
keystone dependency. Ground EVERYTHING in the real files — investigate, do not speculate.
ESTABLISHED GROUND TRUTH (verified this session):
- PySide+shiboken SOURCE: /tmp/pyside-setup (git branch 6.11, matches Qt). sources/shiboken6,
sources/shiboken6_generator, sources/pyside6 (check exact dir names). Built via setup.py +
coin_build_instructions.py OR raw CMake per sources/*/CMakeLists.txt + README.*.md at repo root.
- HOST toolchain (x86-64 Linux): clang/llvm 21 with libclang at /usr/lib/llvm-21/lib/libclang.so
(llvm-config = 21.1.8); host CPython 3.14.4 (/usr/bin/python3, headers
/opt/toolchains/src/Python-3.14.4/Include); host Qt 6.11.1 at /opt/toolchains/qt/6.11.1/gcc_64.
- TARGET (wasm): emscripten 4.0.12 (/opt/toolchains/emsdk); Qt 6.11.1 asyncify single-thread at
/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread (QT_FEATURE_thread == -1, i.e. -no-feature-thread);
wasm CPython 3.14 at /opt/toolchains/python-wasm (headers include/python3.14, lib/python3.14.zip).
FreeCAD-wasm build dir: /opt/toolchains/src/freecad-gui-build (reads Qt6_DIR=qt-asyncify/.../wasm_singlethread).
- FreeCAD build links Python C-extensions STATICALLY and registers them via PyImport_AppendInittab in
a generated src/Main/WasmInittab*.cpp (NO dlopen on wasm). PySide bindings must follow this model:
build as STATIC libs, expose PyInit_* for each module (PySide6.QtCore etc.), register in the inittab.
- FreeCAD source: /home/magik6k/lcad-wasm/freecad-port/FreeCAD (branch wasm-port). It ships a PySide
shim at src/Ext/PySide (a compat layer) and uses shiboken (getCppPointer/wrapInstance) e.g. in
Gui/PythonWrapper.cpp. FREECAD_USE_PYSIDE/FREECAD_USE_SHIBOKEN currently OFF.
- KNOWN HARD ISSUES to design around: (1) static + NO dlopen — PySide's normal build emits shared
modules; must emit static + inittab. (2) Qt is -no-feature-thread — shiboken typesystem for QtCore
references QThread/QMutex/etc.; those must be excluded/guarded. (3) CPython 3.14 C-API changes vs
what PySide 6.11 targets. (4) asyncify + JS-exceptions (-fexceptions) flags must match FreeCAD's;
libshiboken/libpyside signal dispatch must be single-thread/asyncify safe. (5) binary size (wasm is
already 196 MB; each Qt binding module is tens of MB — target ONLY the modules FreeCAD needs).
DELIVERABLES — do BOTH:
1. Write a thorough, concrete, execution-ready plan to /home/magik6k/lcad-wasm/pyside-port/<SLUG>/plan.md with exact commands,
cmake args, file paths, patch snippets (precise enough to apply), and any test outputs you gathered.
Put patch/new-file contents as separate files in that dir. If you build/test something, capture the
real output.
2. Return the structured summary (schema). Depth goes in plan.md; keep summary fields tight.
Token cost is not a concern — be exhaustive and correct. Prefer running real commands (grep, ls,
cmake --help, small test builds) over speculation.
=== YOUR SCOPE: Handle Qt -no-feature-thread in the shiboken typesystems (slug: qt-nothread-typesystem) ===
The wasm Qt is built -no-feature-thread (QT_FEATURE_thread=-1), so QThread/QMutex/QWaitCondition/QThreadPool/QThreadStorage/QReadWriteLock etc. are absent from the headers. shiboken's QtCore typesystem (sources/pyside6/PySide6/QtCore/typesystem_core*.xml) references these classes and will fail to bind (missing types). Enumerate exactly which typesystem entries reference thread types, and determine the fix: either remove/guard those <object-type>/<value-type> entries for the wasm build, or rely on shiboken skipping missing types. Grep the QtCore typesystem XMLs. Also check QtCore code that uses threads at runtime. Produce the precise typesystem patch (or a generation-time exclusion list).
Create /home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/ and write plan.md (required) + any patches. Return the schema with deliverableFile=/home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/plan.md.
Fable
thinking
I need to dig into the QtCore typesystem XML files to find how thread types are defined.
Fable
⚙ Bash {"command": "ls /tmp/pyside-setup/sources/pyside6/PySide6/QtCore/ | head -50; echo \"---\"; ls /tmp/pyside-setup/sources/", "description": "List QtCore typesystem files"}
Fable
⚙ Bash {"command": "mkdir -p /home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem", "description": "Create output dir"}
Fable
thinking
I'm looking at the typesystem files to understand the thread-related type definitions.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; grep -nE 'QThread|QMutex|QWaitCondition|QThreadPool|QThreadStorage|QReadWriteLock|QReadLocker|QWriteLocker|QMutexLocker|QSemaphore|QAtomic|QRunnable|QBasicMutex|QFuture|QThre…
Fable
thinking
I need to dig deeper into the specific regions and check what feature guards are actually defined in the Qt headers to verify these classes are missing. Let me look at the typesystem_core_common.xml file to understand the relevant bindings.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; sed -n '1615,1700p' typesystem_core_common.xml", "description": "Read locker/thread region"}
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; sed -n '2525,2700p' typesystem_core_common.xml", "description": "Read mutex/semaphore/waitcondition region"}
Fable
thinking
I'm realizing the typesystem already guards thread-related types using a configuration condition that checks `QT_CONFIG(thread)`, which automatically excludes these types when threading is disabled. But I notice that QReadWriteLock and QBasicReadWriteLock are missing this guard, which could be the issue.
Fable
Key finding: many thread types already have `<configuration condition="QT_CONFIG(thread)"/>` guards. Let me check the ones that don't, and how shiboken handles this condition.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; for n in QBasicReadWriteLock QReadWriteLock QThreadPool QRunnable QFutureInterfaceBase QThreadStorage QThreadPool; do echo \"=== $n ===\"; grep -n \"\\\"$n\\\"\" typesystem_c…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; sed -n '3120,3170p;3205,3230p;3630,3660p' typesystem_core_common.xml", "description": "Read QThreadPool/QFuture/QRunnable regions"}
Fable
thinking
I'm trying to understand how shiboken's configuration condition mechanism works—specifically how it determines whether `QT_CONFIG(thread)` is true or false at generation time, and whether that causes types to be excluded from the bindings. I need to dig into the shiboken generator source to see how it evaluates these conditions. I've noticed that most thread-related types have this guard, but a couple are missing it, including QBasicReadWriteLock.
Fable
Most thread types already carry `<configuration condition="QT_CONFIG(thread)"/>`. The two exceptions are `QReadWriteLock` and `QBasicReadWriteLock`. Now let me understand how shiboken evaluates this condition.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources; grep -rn \"configuration\" shiboken6/ApiExtractor/ | grep -iE \"condition|QT_CONFIG|feature\" | head -40", "description": "Find configuration condition handling"}
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources; grep -rln \"TypeSystemConfiguration\\|configuration-condition\\|<configuration\" shiboken6/ | head; echo \"===\"; grep -rn \"QT_CONFIG\\|qtConfig\\|configCondition\\|conditionAttribute\\|m_configur…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources; find shiboken6 -maxdepth 2 -type d | head -40", "description": "Find shiboken dir structure"}
Fable
thinking
I need to check the shiboken6_generator directory for the ApiExtractor.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources; find shiboken6_generator -maxdepth 2 -type d | grep -i \"apiextr\\|ApiExtractor\"; echo \"---files---\"; ls shiboken6_generator/ApiExtractor/ 2>/dev/null | head; find shiboken6_generator -name \"*.…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/ApiExtractor; grep -rn \"configuration\\|QT_CONFIG\\|conditio\" typesystemparser.cpp typesystem.h typesystem.cpp 2>/dev/null | grep -i \"condit\\|qt_config\\|configuration\" | he…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/ApiExtractor; grep -rn \"ConfigCondition\\|configCondition\\|configComplexTypes\\|writeConfiguration\\|generateConfig\\|QT_CONFIG\" *.cpp *.h | head -40", "description": "Find co…
Fable
thinking
The config condition gets converted into a preprocessor guard that wraps the type binding code in the generated C++, so when the feature is disabled at compile time, that entire section is excluded. I'm looking for where the generator actually uses this configCondition to confirm this mechanism is working as expected.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator; grep -rn \"configCondition\\|hasConfigCondition\\|ConfigCondition\" generator/ | head -40", "description": "Find config condition in generator"}
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '1,45p' configurablescope.h", "description": "Read configurablescope.h"}
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -rn \"ConfigurableScope\\|configCondition\" cppgenerator.cpp | head", "description": "Find ConfigurableScope usage in cppgenerator"}
Fable
thinking
The generated code conditionally compiles class registration using preprocessor directives, so when threading is disabled, the type registration is skipped entirely. Even though the per-class wrapper file gets generated and included in the build, its entire contents are wrapped in the same conditional, meaning it effectively becomes empty when the feature is disabled.
Now I need to trace through the actual scope of what ConfigurableScope guards — whether it wraps just the registration call in the main module file or the entire generated wrapper file for the class, and I should look at the surrounding context to understand the full picture.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '7030,7100p' cppgenerator.cpp", "description": "Read context around configScope usage"}
Fable
thinking
I need to trace through the code generation to see how per-class wrapper files are guarded and where the registration happens. Let me search the cppgenerator for where class-specific registration code gets written, particularly looking at how ConfigurableScope fits into the wrapper generation pipeline.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -n \"ConfigurableScope\\|configCondition\\|hasConfigCondition\\|#if\\|generateClass\\|void CppGenerator::generateClass\\|writeClassRegister\\|SbkObjectTy…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '5951,6010p' cppgenerator.cpp", "description": "Read writeClassRegister start"}
Fable
thinking
I'm checking how the generateClass function wraps the entire wrapper .cpp file within the config condition, and whether the per-class file's register function is properly guarded by ConfigurableScope at the point where it's written.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '670,700p' cppgenerator.cpp; echo \"...======955-970======\"; sed -n '940,970p' cppgenerator.cpp", "description": "Read generateClass top and end"}
Fable
thinking
I'm noticing that the wrapper .cpp file for QThread isn't wrapped in the `#if QT_CONFIG(thread)` guard, which means it would still try to include and reference QThread even when the thread feature is disabled — that shouldn't compile. But this is how PySide normally builds anyway since QT_CONFIG(thread) is typically enabled, so there must be something else going on here.
Actually, I think I see it now: the header parser using clang won't even see QThread when the thread feature is off, because qthread.h itself is guarded with `#if QT_CONFIG(thread)`. So shiboken's AbstractMetaBuilder won't find the type at all and simply won't generate a wrapper for it in the first place, emitting a warning …
Fable
The per-class wrapper body isn't wholesale-guarded. The real question is whether the header actually hides the class (so shiboken skips it) and whether the module-level references are guarded. Let me verify the Qt headers hide these classes and check the module registration path.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== qconfig thread feature ===\"; grep -rn \"QT_FEATURE_thread\\b\\|QT_FEATURE_future\\b\\|QT_FEATURE_cxx11_future\" $Q/include/QtCore/qtcore-config.h 2>/dev/null; …
Fable
thinking
I need to verify the actual state of QT_FEATURE_thread since it's showing as -1 in memory but not appearing in qtcore-config.h. More importantly, I'm noticing that QThread is defined unconditionally in qthread.h without any QT_CONFIG(thread) guards, which means the class exists regardless of whether threading is configured.
Looking at qmutex.h, there's a conditional compilation at line 17 with QT_CONFIG(thread) that branches to a stub implementation when threading is disabled, but the class itself still exists. I should check what that else branch looks like and confirm the actual QT_FEATURE_thread value.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== search QT_FEATURE_thread across config headers ===\"; grep -rn \"QT_FEATURE_thread\\b\" $Q/include/QtCore/ 2>/dev/null; echo; echo \"=== QT_FEATURE_future / thr…
Fable
thinking
So QT_FEATURE_thread is disabled, which means QT_CONFIG(thread) returns false. The QThread class itself is declared unconditionally in qthread.h without any preprocessor guards around it, though I should double-check if there are guards protecting specific methods inside the class definition.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== qthread.h QT_CONFIG usage ===\"; grep -nE \"QT_CONFIG|#if|#else|#endif|class Q_CORE\" $Q/include/QtCore/qthread.h; echo; echo \"=== line count ===\"; wc -l $Q/i…
Fable
thinking
QThread is defined unconditionally regardless of the thread feature flag, so the class itself always exists. I need to examine the class definition in qthread.h to see what methods are available, since the typesystem references rely on methods like start() and run().
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; sed -n '28,166p' $Q/include/QtCore/qthread.h", "description": "Read QThread class body"}
Fable
thinking
QThread remains fully available even with threading disabled since it's integral to the event loop, so the typesystem binding should work without issues. QMutex, however, uses conditional compilation with `#if QT_CONFIG(thread)` to provide either a full implementation or a stub, so I need to examine what methods are actually available in the stub version to understand the binding constraints.
Fable
Major finding: QThread is a full class even with thread feature off. Let me check the mutex/lock/semaphore headers' `#else` stub branches to see what's actually available.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"########## qmutex.h #else (thread off) branch ##########\"; sed -n '311,362p' $Q/include/QtCore/qmutex.h", "description": "Read qmutex.h thread-off stub"}
Fable
thinking
Looking at the thread-off build configuration, the QMutex stub has a different method signature than the full version, specifically including `tryLock(int)` which the typesystem references and modifies. The stub doesn't expose all methods from the complete implementation, but the typesystem only needs to override `tryLock(int)`, which is present. QRecursiveMutex inherits from QMutex as an empty subclass, so it inherits the stub's method set.
QBasicMutex is just a typedef to QMutex rather than a real class, and while the typesystem declares it as an object-type, the configCondition guard skips it during the thread-off build. QMutexLocker is a template in the stub, but the typesystem actually …
Fable
QMutex etc. are stubbed (different signatures) with `QT_CONFIG(thread)` guards. The typesystem guards exist to match. Now let me check the two entries that LACK guards — `QReadWriteLock`/`QBasicReadWriteLock` — and whether those classes exist in the thread-off headers.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== qreadwritelock.h structure ===\"; grep -nE \"QT_CONFIG|#if|#else|#endif|class Q_CORE_EXPORT QReadWriteLock|class Q_CORE_EXPORT QReadLocker|class Q_CORE_EXPORT Q…
Fable
thinking
I see there's a conditional compilation structure in the header file with thread support enabled at line 14 and a fallback stub implementation starting at line 311. Let me check what the non-threaded versions of these classes look like to understand the API contract.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; sed -n '311,366p' $Q/include/QtCore/qreadwritelock.h", "description": "Read qreadwritelock.h thread-off stub"}
Fable
thinking
Looking at the thread-off stub, QReadWriteLock is defined standalone without deriving from QBasicReadWriteLock, since QBasicReadWriteLock only exists in the QT_CONFIG(thread) branch. However, in the typesystem, QBasicReadWriteLock appears as an object type at line 1621 without any config condition, which creates a mismatch between the stub and the type definitions.
When shiboken processes this, it will emit a warning that QBasicReadWriteLock isn't found and skip its generation, but since the stub's QReadWriteLock doesn't inherit from it, no generated code actually references it—so it's harmless, though adding a config guard would be cleaner. Meanwhile, QReadWriteLock itself at line 1625 doe …
Fable
Now let me verify: whether "type not found" is fatal in shiboken, check `QT_FEATURE_future`, and scan for any other thread references (glue code, other typesystem files).
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== future/concurrent features ===\"; grep -nE \"QT_FEATURE_future|QT_FEATURE_concurrent\" $Q/include/QtCore/qtcore-config.h; echo; echo \"=== other QtCore typesyst…
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== grep future/concurrent in all core config headers ===\"; grep -rnE \"QT_FEATURE_future|QT_FEATURE_concurrent\" $Q/include/QtCore/*.h; echo; echo \"=== glue loca…
Fable
thinking
QFuture types are available even in the thread-disabled wasm build since they rely on std::future which works fine in single-threaded mode via deferred execution, so the QT_CONFIG guards won't exclude them. The glue file referenced in the typesystem is actually located in the core directory rather than where the path suggests.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6; find . -name \"qtcore.cpp\" -path \"*glue*\"; echo \"---all glue qtcore---\"; find . -path \"*glue*qtcore*\"; echo \"=== thread snippets in the real glue file ===\"; f=$(find . -nam…
Fable
thinking
I'm noticing a potential issue with how the QThread binding is being generated — the wrapper code snippets are being injected through the typesystem, but even though QThread registration is conditionally compiled with `#if QT_CONFIG(thread)`, the wrapper file itself might still be generated unconditionally, which could cause problems.
Let me check the actual snippet definitions to see if they're already guarded. Looking at the pthread cleanup snippet around lines 1714-1745, I can see there's already a `#if` directive wrapping the pthread includes, so the code should be protected even if the wrapper file is generated unconditionally.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6; sed -n '1714,1745p' glue/qtcore.cpp", "description": "Read qthread pthread cleanup snippet guards"}
Fable
thinking
I'm wondering whether the pthread code is actually protected on emscripten since it defines Q_OS_WASM rather than Q_OS_UNIX, though QThread itself has its own guards so this might be a secondary issue. The real question is whether the wrapper file itself is guarded, which I should be able to determine by checking for a "generateClass" wrapping.
The key thing to verify is how PySide handles the wasm/no-thread build — specifically whether the module initialization file that calls all the class init functions, and the types array in the header, are properly guarded with ConfigurableScope. If the module references an init function for QThread but that function is compiled out, I'd get a linker …
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -n \"ConfigurableScope\\|TypeInitStruct\\|writeInitFunc\\|init_\\|_TypeF\\b\\|initFunctionName\\|writeInitInheritance\\|lazyInit\\|typeInitStruct\" cppge…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '6483,6535p' cppgenerator.cpp; echo \"====== 6940-6990 cppApi array ======\"; sed -n '6940,6990p' cppgenerator.cpp", "description": "Read writeInitFunc…
Fable
thinking
I need to check whether `writeClassCode` wraps each class's initialization function call in a ConfigurableScope, and understand how this applies to classes with a configCondition that are actually generated versus those that aren't found at all.
For classes like QThread that exist and get generated, the wrapper's init function is created unconditionally, but its registration into the lazy creation function and cppApi array should be guarded by the configCondition. The type array itself is written unconditionally in `writeTypeArrays` without ConfigurableScope, so the slot for QThread always exists in the array. If the registration call is guarded out, the slot just stays null and never gets …
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -n \"writeClassCode\" cppgenerator.cpp | head; echo \"---\"; sed -n '/void CppGenerator::writeClassCode/,/^}/p' cppgenerator.cpp | head -60", "descriptio…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -n \"::writeClassCode\\b\\|bool CppGenerator::writeClassCode\\|writeClassCode(TextStream\" cppgenerator.cpp cppgenerator.h; echo \"---body---\"; awk '/Cp…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; grep -rn \"writeClassCode\" *.h *.cpp | grep -v \"writeClassCodeSnips\\|writeClassCode(s\"; echo \"=== maybe template in header ===\"; grep -n \"writeClassCod…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/generator/shiboken; sed -n '1,70p' shibokengenerator_tpl.h", "description": "Read writeClassCode template"}
Fable
thinking
So `writeClassCode` wraps the generated code for each class—both the init function declarations and the AddTypeCreationFunction calls—inside a `ConfigurableScope` guard like `#if QT_CONFIG(thread)`. This means for guarded classes, both the init function declaration and the type creation function call get protected by the same conditional. However, the wrapper .cpp file defines the init function unconditionally, without the guard.
The real concern is when a guarded class doesn't actually exist in the type system—like QBasicReadWriteLock. In that case, the class won't appear in the API at all, and shiboken just emits a warning that the type was specified in the typesystem but not found. That …
Fable
`writeClassCode` wraps every class's registration in `ConfigurableScope` (the `#if QT_CONFIG(thread)`). The mechanism is confirmed. Now let me check whether a "type not found" (for QBasicReadWriteLock) is fatal or just a warning, and how it interacts with configCondition.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/shiboken6_generator/ApiExtractor; sed -n '395,420p' messages.cpp; echo \"=== where is this message emitted / severity ===\"; grep -rn \"specified in typesystem\\|but not defined\\|msgClassNotFound\\…
Fable
thinking
The `msgTypeNotDefined` message uses `hasConfigCondition` to distinguish between two scenarios: when a type lacks a definition but has a config condition, it notes this as potentially disabled by configuration, but when there's no config condition at all, it warns about likely compilation errors. This reveals the core purpose of the configCondition system—to differentiate between intentionally disabled types and genuinely missing definitions.
For QBasicReadWriteLock, the fix is straightforward: add the thread configuration condition so it's properly marked as disabled rather than flagged as a risky unguarded type. For QReadWriteLock, it's more nuanced—while it exists as a stub in thread-off …
Fable
This is conclusive. The `msgTypeNotDefined` code explicitly treats `configCondition` as the sanctioned way to handle types "disabled by configuration" — the entire mechanism exists for exactly this scenario. Let me produce the authoritative guard-status table for every thread-related entry.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup/sources/pyside6/PySide6/QtCore; python3 - <<'PY'\nimport re\ntxt=open('typesystem_core_common.xml').read().splitlines()\n# find object-type/value-type blocks and whether they contain configuration condition…
Fable
thinking
Looking at the table, QBasicReadWriteLock and QReadWriteLock are the only unguarded classes. I've confirmed that QBasicReadWriteLock doesn't exist in the thread-off stub, while QReadWriteLock does, so I need to add a thread configuration guard to QBasicReadWriteLock.
For QReadWriteLock, I'm weighing whether to guard it too. Since its RAII lockers (QReadLocker/QWriteLocker) are already guarded, guarding QReadWriteLock itself would be consistent. However, leaving it unguarded means Python gets a functional no-op version on wasm, which could be better for compatibility—though that comes at the cost of missing the locker classes. I'll present guarding as recommended but note that leaving it ung …
Fable
The picture is complete and authoritative. Let me do two final confirmations: that `QBasicReadWriteLock` truly is absent in the thread-off headers (so it's the one that triggers the "compile errors" warning), and check the typesystem documentation for the `<configuration>` element to cite the intended usage.
Fable
⚙ Bash {"command": "Q=/opt/toolchains/qt-asyncify/6.11.1/wasm_singlethread; echo \"=== QBasicReadWriteLock defined anywhere in thread-off-visible code? ===\"; grep -rn \"class.*QBasicReadWriteLock\\|QBasicReadWriteLock\" $Q/include/QtCore/qreadwri…
Fable
thinking
QBasicReadWriteLock is only defined within the thread-enabled configuration block, so it's missing entirely when threading is disabled. I need to add a guard around it to prevent compilation errors and avoid any dangling base class references in the thread-off scenario.
QReadWriteLock exists as a stub in the thread-off case, so it compiles fine without a guard, but I'll add one for consistency.
Now I'm writing out the plan document and creating the unified diff patch file with the exact indentation and XML structure from the source.
Fable
Everything is confirmed. `QBasicReadWriteLock` is only defined inside the `#if QT_CONFIG(thread)` branch, so it is absent in the wasm build and its unguarded typesystem entry triggers the "could lead to compilation errors" warning. Let me write the deliverables.
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup && git log --oneline -1 2>/dev/null; git rev-parse --abbrev-ref HEAD 2>/dev/null; echo \"---\"; sed -n '1619,1629p' sources/pyside6/PySide6/QtCore/typesystem_core_common.xml | cat -A | head -20", "descripti…
Fable
Now let me write the patch file and the plan.
Fable
⚙ Write {"file_path": "/home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/0001-qtcore-guard-readwritelock-nothread.patch", "content": "From: FreeCAD wasm port\nSubject: [PATCH] QtCore typesystem: guard QBasicReadWriteLock/QReadWriteLock\n b…
Fable
thinking
I should write a Python script that applies the changes using anchor-based matching instead of relying on line numbers, which would be more robust if the file has drifted.
Fable
⚙ Write {"file_path": "/home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/apply_guard.py", "content": "#!/usr/bin/env python3\n\"\"\"Idempotent, line-number-independent application of the QtCore no-thread guard.\n\nAdds <configuration condi…
Fable
⚙ Bash {"command": "cd /tmp/pyside-setup && cp sources/pyside6/PySide6/QtCore/typesystem_core_common.xml /tmp/ts_backup.xml && python3 /home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/apply_guard.py /tmp/pyside-setup && echo \"=== verify…
Fable
thinking
The patch applies cleanly with well-formed XML and is idempotent, so I'm considering whether to restore the backup to avoid interfering with other subagents who might be working with the pristine source, though leaving it patched is probably the right call since this is the actual fix we need.
Fable
Patch applies cleanly, XML validates, and it's idempotent. Let me restore the pristine source so concurrent subagents aren't surprised (the patch/script are the deliverable), then verify the restore.
Fable
⚙ Bash {"command": "cp /tmp/ts_backup.xml /tmp/pyside-setup/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml && grep -c \"configuration condition=\\\"QT_CONFIG(thread)\\\"\" /tmp/pyside-setup/sources/pyside6/PySide6/QtCore/typesystem_core…
Fable
thinking
I've confirmed the twelve threading-related classes that need the QT_CONFIG(thread) guards, and now I'm putting together the comprehensive plan document.
Fable
Now the comprehensive plan.md.
Fable
⚙ Write {"file_path": "/home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/plan.md", "content": "# QtCore shiboken typesystem vs. Qt `-no-feature-thread` (wasm) — analysis & fix\n\n**Scope slug:** `qt-nothread-typesystem`\n**Goal:** Make shi…
Fable
⚙ Bash {"command": "ls -la /home/magik6k/lcad-wasm/pyside-port/qt-nothread-typesystem/", "description": "List deliverables"}
Fable
thinking
All the deliverables are complete and verified, so I'm ready to format everything into the structured output.
Fable
⚙ StructuredOutput {"__unparsedToolInput": {"raw": "{\"slug\": \"qt-nothread-typesystem\", \"title\": \"Guard QtCore thread-type typesystem entries for -no-feature-thread wasm\", \"verdict\": \"PySide 6.11's QtCore typesystem is already built to bind against …
Fable
⚙ StructuredOutput {"slug": "qt-nothread-typesystem", "title": "Guard QtCore thread-type typesystem entries for -no-feature-thread wasm", "verdict": "PySide 6.11's QtCore typesystem is already built to bind against a -no-feature-thread Qt: 12 of the 14 thread…