Skip to content

Commit 64d84f3

Browse files
authored
CMake: option for WASM build
Option `BUILD_WASM` to specify WebAssembly as target arch Support building current main branch with emscripten 3.1.14 Only build qml_minimal example for WASM target * Book: Wasm Builds * Book: cmake anchors exclude BUILD_WASM, explain qt_add_executable * Book: wasm: document issues more, simplify version table * Add BUILD_WASM info to changelog
1 parent 7fca33d commit 64d84f3

File tree

12 files changed

+309
-18
lines changed

12 files changed

+309
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
- Add support for specifying read write and notify in qproperty macro, including support for custom user defined functions
3030
- Add support for the constant, required, reset and final flags in the qproperty macro
3131
- QObject subclasses can now inherit from other CXX-Qt generated QObject classes
32+
- `BUILD_WASM` CMake option to support WebAssembly builds and a book page for building for WASM
3233

3334
### Changed
3435

CMakeLists.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,27 @@ add_compile_definitions(
121121
QT_USE_QSTRINGBUILDER
122122
)
123123

124+
if(BUILD_WASM)
125+
# TODO: fix wasm test failures
126+
set(BUILD_TESTING OFF)
127+
# Ensure Rust build for the correct target
128+
set(Rust_CARGO_TARGET wasm32-unknown-emscripten)
129+
set(THREADS_PREFER_PTHREAD_FLAG ON)
130+
find_package(Threads REQUIRED)
131+
endif()
132+
124133
# QMAKE environment variable is needed by qt-build-utils to ensure that Cargo
125134
# uses the same installation of Qt as CMake does.
126135
if(NOT USE_QT5)
127136
find_package(Qt6 COMPONENTS Core Gui Test QuickControls2)
128137
endif()
129138
if(NOT Qt6_FOUND)
130-
find_package(Qt5 5.15 COMPONENTS Core Gui Test QuickControls2 REQUIRED)
139+
if(BUILD_WASM)
140+
message(FATAL_ERROR
141+
"CXX-Qt for WebAssembly only currently supports Qt 6 builds."
142+
)
143+
endif()
144+
find_package(Qt5 5.15 COMPONENTS Core Gui Test REQUIRED)
131145
endif()
132146

133147
if (NOT Qt5_FOUND)

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0
1414
- [Creating the QML GUI](./getting-started/3-qml-gui.md)
1515
- [Building with Cargo](./getting-started/4-cargo-executable.md)
1616
- [Building with CMake](./getting-started/5-cmake-integration.md)
17+
- [Building for WebAssembly](./getting-started/6-wasm-builds.md)
1718
- [Core Concepts](./concepts/index.md)
1819
- [Build Systems](./concepts/build_systems.md)
1920
- [Generated `QObject`](./concepts/generated_qobject.md)

book/src/getting-started/5-cmake-integration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ For this example, we are [supporting both Qt5 and Qt6 with CMake](https://doc.qt
130130

131131
```cmake,ignore
132132
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_setup}}
133+
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_setup-2}}
134+
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_setup-3}}
135+
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_setup-4}}
133136
```
134137

135138
Download CXX-Qts CMake code with FetchContent:
@@ -164,6 +167,7 @@ This will create two new CMake targets:
164167
Finally, we can create the CMake executable target and link it to our crate:
165168

166169
```cmake,ignore
170+
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_executable-2}}
167171
{{#include ../../../examples/qml_minimal/CMakeLists.txt:book_cmake_executable}}
168172
```
169173

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
SPDX-FileContributor: Matt Aber <[email protected]>
4+
5+
SPDX-License-Identifier: MIT OR Apache-2.0
6+
-->
7+
8+
# Building for WebAssembly
9+
10+
CXX-Qt and applications written with it can be compiled for WebAssembly, with a few limitations. Below you will find detailed instructions regarding how to build for the WASM target.
11+
12+
You will need to have Qt for WebAssembly installed. The next section shows versions that have been tested.
13+
14+
Additionally, if you haven't already, clone the `emsdk` git repo from [here](https://github.com/emscripten-core/emsdk).
15+
16+
## Using Correct Versions
17+
18+
The version of Emscripten used to build CXX-Qt and programs using it should match the one that was used to build Qt for WebAssembly. This is because Emscripten does not guarantee ABI compatibility between versions, so using different versions is not guaranteed to work fully or even at all.
19+
20+
Here are the associated Qt and Emscripten versions, and whether they are currently working with CXX-Qt for WebAssembly:
21+
22+
Qt|Emscripten
23+
-|-
24+
6.2|2.0.14
25+
6.3|3.0.0
26+
6.4|3.1.14
27+
6.5|3.1.25
28+
6.6|3.1.37
29+
30+
Info about other Qt and emscripten versions can be found in the [Qt documentation](https://doc.qt.io/qt-6/wasm.html).
31+
32+
## Setting Up `emsdk`
33+
34+
Once you know which Qt and Emscripten versions you will use, navigate to the root directory of the `emsdk` repo and run the following commands:
35+
36+
```bash
37+
$ ./emsdk install <emscripten version>
38+
$ ./emsdk activate <emscripten version>
39+
$ source ./emsdk_env.sh
40+
```
41+
42+
For example, if you are going to use Qt 6.4, the corresponding version of Emscripten is 3.1.14, so the first command will be:
43+
44+
```bash
45+
$ ./emsdk install 3.1.14
46+
```
47+
48+
On Windows, the third step, which sets up environment variables (`source` command above on Unix-like environments) is unnecessary because the required environment setup will already be done.
49+
50+
## Toolchains
51+
52+
When configuring with CMake, the `CMAKE_TOOLCHAIN_FILE` variable needs to be set to the correct toolchain file; for example, if using Qt 6.4.2 on WebAssembly, the toolchain file is typically located at `/path/to/Qt/6.4.2/wasm_32/lib/cmake/Qt6/qt.toolchain.cmake`. This will set CMake up to use the correct Qt path, compiler, linker, and so forth.
53+
54+
Generally, this does not need to be done manually. Using the `qt-cmake` binary bundled with your selected version of Qt WASM will set the toolchain file for you.
55+
56+
For example, if using Qt 6.4.2:
57+
58+
```bash
59+
$ /path/to/Qt/6.4.2/wasm_32/bin/qt-cmake -B build .
60+
```
61+
62+
However, in Qt 6.3 and below, the bundled CMake is version 3.22, while CXX-Qt requires at least version 3.24. For these versions of Qt, a more up-to-date CMake binary needs to be used to configure, so `CMAKE_TOOLCHAIN_FILE` needs to be passed into the `cmake` command.
63+
64+
If using a different CMake binary, instead do this:
65+
66+
```bash
67+
$ cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/qt.toolchain.cmake -B build .
68+
```
69+
70+
For Qt 6.5 or later, use the `wasm_singlethread` toolchain. For versions earlier than 6.5 use `wasm_32`.
71+
72+
The `wasm_multithread` toolchain available in 6.5 and later is currently not supported. For more information, see the [Known Issues](#known-issues) section at the bottom of this page.
73+
74+
## Compiling Your Project for WebAssembly
75+
76+
To build for WebAssembly in a project that uses CXX-Qt crates, first follow the instructions in the [Using Correct Versions](#using-correct-versions) and [Setting Up `emsdk`](#setting-up-emsdk) sections.
77+
78+
### CMakeLists.txt
79+
80+
When compiling a CXX-Qt project for wasm, the Rust target must be set to `wasm32-unknown-emscripten`, and the project must be configured to use POSIX threads. Make sure you have the Emscripten target for `rustc` with `rustup target add wasm-unknown-emscripten`.
81+
82+
```cmake
83+
set(Rust_CARGO_TARGET wasm32-unknown-emscripten)
84+
set(THREADS_PREFER_PTHREAD_FLAG ON)
85+
find_package(Threads REQUIRED)
86+
```
87+
88+
Using CMake, `add_executable` will not output an HTML file when targeting wasm. In order to render an HTML file, one must use `qt_add_executable` in its place. Assuming a project has a CMake flag `BUILD_WASM` to toggle wasm and native builds, one could write the following:
89+
90+
```cmake
91+
if(BUILD_WASM)
92+
qt_add_executable(${APP_NAME} ${SOURCE_FILES})
93+
else()
94+
add_executable(${APP_NAME} ${SOURCE_FILES})
95+
endif()
96+
```
97+
98+
### Configure, Build, and Run
99+
100+
Configure your build directory following the instructions in the [Toolchains](#toolchains) section.
101+
102+
Now, run `cmake --build` on the build directory to compile and link the project. This can be any CMake binary; here the OS package works just fine:
103+
104+
```bash
105+
$ cmake --build build
106+
```
107+
108+
You can then run your built application like so:
109+
110+
```bash
111+
$ emrun ./build/path/to/<appname>.html
112+
```
113+
114+
## Compiling CXX-Qt WASM from Source
115+
116+
If you are compiling CXX-Qt from source, the workflow is similar. First, follow the instructions in the [Using Correct Versions](#using-correct-versions) and [Setting Up `emsdk`](#setting-up-emsdk) sections.
117+
118+
The `CMakeLists.txt` file at the root of the CXX-Qt repository has an option `BUILD_WASM` to toggle WebAssembly builds. Simply compiling with the correct emsdk and toolchain and flipping this option `ON` should build the libraries and examples for WebAssembly.
119+
120+
### Building
121+
122+
Read the [Toolchains](#toolchains) section before proceeding. Then navigate to the root directory of the CXX-Qt repo.
123+
124+
If using the `qt-cmake` binary packaged with your version of Qt for WebAssembly, run the following command to configure CXX-Qt:
125+
126+
```bash
127+
$ /path/to/qt-cmake -DBUILD_WASM=ON -B build .
128+
```
129+
130+
If using a different CMake binary, instead do this:
131+
132+
```bash
133+
$ <cmake binary> -DCMAKE_TOOLCHAIN_FILE=/path/to/qt.toolchain.cmake -DBUILD_WASM=ON -B build .
134+
```
135+
136+
Finally, run `cmake --build` on the configured build directory to compile and link the project and examples. This can be any CMake binary; here the OS package works just fine:
137+
138+
```bash
139+
$ cmake --build build
140+
```
141+
142+
Then you can run the `qml_minimal` example like so:
143+
144+
```bash
145+
$ emrun ./build/examples/qml_minimal/example_qml_minimal.html
146+
```
147+
148+
### Working Examples
149+
150+
Not all of the examples are currently supported for WASM builds.
151+
152+
Example|Working
153+
-|-
154+
`qml-minimal-no-cmake`|❌ broken
155+
`demo_threading`|❌ broken
156+
`qml_features`|✅ working
157+
`qml_minimal`|✅ working
158+
159+
For more information, see the [Known Issues](#known-issues) section at the bottom of this page.
160+
161+
## Known Issues
162+
163+
### `wasm_multithread` toolchain
164+
165+
CXX-Qt will currently not build with `wasm_multithread` versions of Qt.
166+
167+
```console
168+
wasm-ld: error: --shared-memory is disallowed by qml_minimal-e6f36338b0e1fa5c.17g6vcid2nczsjj0.rcgu.o
169+
because it was not compiled with 'atomics' or 'bulk-memory' features.
170+
```
171+
172+
This issue is related to `pthread` in the `libc` crate. It is possible that manually compiling `cxx` and `libc` crates with `-pthread` may solve this.
173+
174+
### `cargo`-only builds
175+
176+
The example `qml-minimal-no-cmake` will not build for WebAssembly with `cargo`, and attempts to build with `cargo` without `cmake` will not work. This is due to an upstream issue with the `libc` crate, which does not support wasm and can cause breakage.
177+
178+
```console
179+
cannot find function `pthread_kill` in crate `libc`
180+
```
181+
182+
### `demo_threading` example
183+
184+
The example `demo_threading` will not build for WebAssembly due to an upstream issue with `async-std`, which does not support wasm. On Linux, the observed breakage is due to `socket2` using its `unix.rs` file to target a Unix rather than wasm environment, resulting in error messages from `unix.rs` like the following:
185+
186+
```console
187+
error[E0433]: failed to resolve: use of undeclared type `IovLen`
188+
```
189+
190+
`socket2` is a dependency of `async-io`, which is a dependency of `async-std`.
191+
192+
There is discussion around supporting wasm in the GitHub repository for `async-std`, and the progress is being tracked [here](https://github.com/async-rs/async-std/issues/220).

examples/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
# When using `cargo test`
1010
add_subdirectory(qml_features)
1111
add_subdirectory(qml_minimal)
12-
add_subdirectory(demo_threading)
12+
13+
# TODO: get demo_threading working for wasm builds
14+
if(NOT BUILD_WASM)
15+
add_subdirectory(demo_threading)
16+
endif()

examples/demo_threading/CMakeLists.txt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,28 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
1616
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
1717
endif()
1818

19+
if(BUILD_WASM)
20+
# Ensure Rust build for the correct target
21+
set(Rust_CARGO_TARGET wasm32-unknown-emscripten)
22+
set(THREADS_PREFER_PTHREAD_FLAG ON)
23+
find_package(Threads REQUIRED)
24+
endif()
25+
1926
set(CMAKE_AUTOMOC ON)
2027
set(CMAKE_AUTORCC ON)
2128
set(CMAKE_CXX_STANDARD 17)
2229
set(CMAKE_CXX_STANDARD_REQUIRED ON)
2330

31+
set(CXXQT_QTCOMPONENTS Core Gui Qml QuickControls2)
32+
if(NOT BUILD_WASM)
33+
set(CXXQT_QTCOMPONENTS ${CXXQT_QTCOMPONENTS} QmlImportScanner)
34+
endif()
35+
2436
if(NOT USE_QT5)
25-
find_package(Qt6 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner)
37+
find_package(Qt6 COMPONENTS ${CXXQT_QTCOMPONENTS})
2638
endif()
2739
if(NOT Qt6_FOUND)
28-
find_package(Qt5 5.15 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner REQUIRED)
40+
find_package(Qt5 5.15 COMPONENTS ${CXXQT_QTCOMPONENTS} REQUIRED)
2941
endif()
3042

3143
find_package(CxxQt QUIET)
@@ -53,7 +65,7 @@ else()
5365
set(QML_COMPAT_RESOURCES qml/compat/compat_qt6.qrc)
5466
endif()
5567

56-
add_executable(${APP_NAME}
68+
set(DEMO_THREADING_SOURCES
5769
cpp/helpers/energyusageproxymodel.h
5870
cpp/helpers/energyusageproxymodel.cpp
5971
cpp/main.cpp
@@ -62,6 +74,19 @@ add_executable(${APP_NAME}
6274
images/images.qrc
6375
${QML_COMPAT_RESOURCES}
6476
)
77+
78+
if(BUILD_WASM)
79+
# Currently need to use qt_add_executable
80+
# for WASM builds, otherwise there is no
81+
# HTML output.
82+
#
83+
# TODO: Figure out how to configure such that
84+
# we can use add_executable for WASM
85+
qt_add_executable(${APP_NAME} ${DEMO_THREADING_SOURCES})
86+
else()
87+
add_executable(${APP_NAME} ${DEMO_THREADING_SOURCES})
88+
endif()
89+
6590
target_link_libraries(${APP_NAME}
6691
PRIVATE
6792
${CRATE}_qml

examples/qml_features/CMakeLists.txt

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,27 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
1717
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
1818
endif()
1919

20+
if(BUILD_WASM)
21+
# Ensure Rust build for the correct target
22+
set(Rust_CARGO_TARGET wasm32-unknown-emscripten)
23+
set(THREADS_PREFER_PTHREAD_FLAG ON)
24+
find_package(Threads REQUIRED)
25+
endif()
26+
2027
set(CMAKE_AUTOMOC ON)
2128
set(CMAKE_CXX_STANDARD 17)
2229
set(CMAKE_CXX_STANDARD_REQUIRED ON)
2330

31+
set(CXXQT_QTCOMPONENTS Core Gui Qml QuickControls2 QuickTest Test)
32+
if(NOT BUILD_WASM)
33+
set(CXXQT_QTCOMPONENTS ${CXXQT_QTCOMPONENTS} QmlImportScanner)
34+
endif()
35+
2436
if(NOT USE_QT5)
25-
find_package(Qt6 COMPONENTS Core Gui Qml Quick QuickControls2 QmlImportScanner QuickTest Test)
37+
find_package(Qt6 COMPONENTS ${CXXQT_QTCOMPONENTS})
2638
endif()
2739
if(NOT Qt6_FOUND)
28-
find_package(Qt5 5.15 COMPONENTS Core Gui Qml Quick QuickControls2 QmlImportScanner QuickTest Test REQUIRED)
40+
find_package(Qt5 5.15 COMPONENTS ${CXXQT_QTCOMPONENTS} REQUIRED)
2941
endif()
3042

3143
find_package(CxxQt QUIET)
@@ -46,9 +58,17 @@ cxx_qt_import_qml_module(${CRATE}_qml
4658
URI "com.kdab.cxx_qt.demo"
4759
SOURCE_CRATE ${CRATE})
4860

49-
add_executable(${APP_NAME}
50-
cpp/main.cpp
51-
)
61+
if(BUILD_WASM)
62+
# Currently need to use qt_add_executable
63+
# for WASM builds, otherwise there is no
64+
# HTML output.
65+
#
66+
# TODO: Figure out how to configure such that
67+
# we can use add_executable for WASM
68+
qt_add_executable(${APP_NAME} cpp/main.cpp)
69+
else()
70+
add_executable(${APP_NAME} cpp/main.cpp)
71+
endif()
5272

5373
target_include_directories(${APP_NAME} PRIVATE cpp)
5474
target_link_libraries(${APP_NAME}

0 commit comments

Comments
 (0)