diff --git a/examples/cross_build.rst b/examples/cross_build.rst index 7d61808020a6..a5f3e4f9ad41 100644 --- a/examples/cross_build.rst +++ b/examples/cross_build.rst @@ -9,5 +9,6 @@ Cross-building examples cross_build/toolchain_packages cross_build/android/ndk cross_build/android/android_studio + cross_build/emscripten cross_build/tricore cross_build/linux_to_windows_mingw diff --git a/examples/cross_build/emscripten.rst b/examples/cross_build/emscripten.rst new file mode 100644 index 000000000000..9350a6a2108a --- /dev/null +++ b/examples/cross_build/emscripten.rst @@ -0,0 +1,333 @@ +.. _examples_cross_build_emscripten: + +Cross-building with Emscripten - WebAssembly and asm.js +======================================================= + +This example demonstrates how to cross-build a simple C++ project using Emscripten and Conan. + +Conan supports building for both `asm.js `_ and `WASM +`_, giving you the flexibility to target different +JavaScript/WebAssembly runtimes in the browser. + +We recommend creating separate Conan profiles for each target. Below are +recommended profiles and instructions on how to build with them. + +What’s the difference between asm.js and WASM? +---------------------------------------------- + +- **asm.js** is a subset of JavaScript optimized for speed. It is fully supported by all browsers (even older ones) and compiles to a large ``.js`` file. +- **WebAssembly (WASM)** is a binary format that is smaller and faster to load and execute. Most modern browsers support it, and it is generally recommended for new projects. **WASM** is also easier to integrate with native browser APIs compared to **asm.js**. + +Setting up Conan profiles +------------------------- + +**For asm.js (JavaScript-based output):** + +.. code-block:: text + + [settings] + arch=asm.js + build_type=Release + compiler=emcc + compiler.cppstd= + compiler.libcxx= + compiler.version= + os=Emscripten + + [tool_requires] + emsdk/[*] + + [conf] + tools.build:exelinkflags=['-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB'] + tools.build:sharedlinkflags=['-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=2GB', '-sINITIAL_MEMORY=64MB'] + +**For WebAssembly (WASM):** + +.. code-block:: text + + include(default) + [settings] + arch=wasm + build_type=Release + compiler=emcc + compiler.cppstd= + compiler.libcxx= + compiler.version= + os=Emscripten + + [tool_requires] + emsdk/[*] + + [conf] + tools.build:exelinkflags=['-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB'] + tools.build:sharedlinkflags=['-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB'] + +.. note:: + + Even though Emscripten is not a true runtime environment (like Linux or + Windows), it is part of a toolchain ecosystem that compiles C/C++ to + WebAssembly (WASM) and asm.js. + + Conan uses ``os=Emscripten`` to: + - Align with the toolchain: Emscripten integrates the compiler, runtime + glue, and JavaScript environment, making it practical to treat as an + "OS-like" target. + - Support backward compatibility: Many recipes in ConanCenterIndex use + os=Emscripten to enable or disable features and dependencies that + specifically target Emscripten. + - Maintain stability: Changing this setting would break recipes that rely on + it, and would complicate compatibility with alternative WASM toolchains. + + +.. note:: + + ``wasm`` arch refers to ``WASM 32-bit`` target architecture, which is the + default. If you wish to target ``WASM64``, set ``arch=wasm64`` in your profile. + **Note that WASM64 is still experimental** and requires Node.js v20+ and a browser that supports it. + +.. note:: + + The profiles above use the ``emsdk`` package from Conan Center, which provides the Emscripten SDK, including ``emcc``, ``em++``, and tools like ``emrun`` and ``node``. + + If you prefer to use your system-installed Emscripten instead of the Conan-provided one, ``tool_requires`` could be replaced by custom ``compiler_executables`` and ``buildenv``: + + .. code-block:: text + + [conf] + tools.build:compiler_executables={'c':'/path/to/emcc', 'cpp':'/path/to/em++'} + + [buildenv] + CC=emcc + CXX=em++ + AR=emar + NM=emnm + RANLIB=emranlib + STRIP=emstrip + + + This way conan could configure `emsdk` local installation to be used from `CMake`, `Meson`, `Autotools` or other build systems. + + +.. note:: + + The ``tools.build:exelinkflags`` and ``tools.build:sharedlinkflags`` in + previous profiles are recomendations but users can modify them or define + their values in the CMakeLists.txt file using the + ``set_target_properties()`` command. + + - By enabling ``ALLOW_MEMORY_GROWTH`` we allow the runtime to grow its + memory dynamically at runtime by calling ``emscripten_resize_heap()``. Without + this flag, memory is allocated at startup and cannot grow. + + - The ``MAXIMUM_MEMORY`` and ``INITIAL_MEMORY`` values specifies the maximum + and initial memory size for the Emscripten runtime. These values can be + adjusted based on your application's needs. + + Take into account that ``arch=wasm64`` has a theorical exabytes maximum + memory size, but runtime currently limits it to 16GB, while ``arch=wasm32`` + has a maximum memory size of 4GB and ``arch=asm.js`` has a maximum memory size of 2GB. + + +.. important:: + + ``emcc`` compiler does not guarantee any ABI compatibility between different versions (patches included) + To ensure a new ``package_id`` is generated when the Emscripten version + changes, it is recommended to update the ``compiler.version`` setting in your profile accordingly. + + Also, when requiring ``emsdk`` package as a tool, it is recommended to use it this way: + + .. code-block:: python + + self.tool_requires("emsdk/[*]", package_id_mode="patch_mode") + + + This will ensure that the package ID is generated based on the Emscripten + version, allowing Conan to detect changes in the Emscripten toolchain and + rebuild the project accordingly. + + +Example Usage +------------- + +Please, first clone the sources to recreate this project. You can find them in the +`examples2 repository `_ in GitHub: + +.. code-block:: bash + + $ git clone https://github.com/conan-io/examples2.git + $ cd examples2/examples/cross_build/emscripten/bindings + + +You can check the contents of the project: + +.. code-block:: text + + . + ├── CMakeLists.txt + ├── conanfile.py + ├── main.cpp + ├── shell.html + └── ... + + +As we can see in the conanfile and CMakeLists.txt, this project depends on +external libraries such as `eigen `_, +`zlib `_ and `fmt `_. +This library is used to perform a simple floating point operation and to +demonstrate how easy it is to cross-build a project with emscripten using Conan even if it depends on external libraries. + +To simplify the CMakeLists.txt, all the Emscripten specific configuration +have been moved to the conanfile.py, only one line is needed in the +CMakeLists.txt to enable the generation of the ``html`` output (testing +purposes). + +The main.cpp file contains some basic functions which will be called from +JavaScript. Notice the usage of ``EMSCRIPTEN_KEEPALIVE`` specifier to ensure that +the functions are not removed by the Emscripten optimizer, allowing them to be +called from JavaScript. This could be avoided by using the ``-s EXPORTED_FUNCTIONS`` flag. + +In the conanfile.py we may focus on the ``generate()`` method, more specifically in the following lines: + +.. code-block:: python + + def generate(self): + ... + tc.extra_exelinkflags.append( + "-sEXPORTED_FUNCTIONS=['_malloc','_free'] \ + -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','getValue','setValue','HEAPF32'] \ + -sENVIRONMENT=web \ + -sALLOW_MEMORY_GROWTH=1 \ + -sNO_EXIT_RUNTIME=1 \ + --shell-file ${CMAKE_SOURCE_DIR}/shell.html" + ) + +This line is crucial as it specifies the Emscripten flags that will be used +during the linking phase. It exports the necessary functions to be callable +from JavaScript, sets the environment to web, allows memory growth, and +prevents the runtime from exiting immediately after execution. +Also, defines the ``shell.html`` file. This file will act as a ``html`` template to produce the final output. + + +These linker options could also be passed from CMakeLists.txt using the +``set_target_properties()`` command. + +And finally, the ``shell.html`` file is a slightly modified version of the default shell packaged in ``emsdk`` with the following changes: + +- Simplified to only include the necessary scripts and styles. +- Added buttons and input fields to act as a user interface for the exported functions. +- Added in the ``