Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
695ba52
emsdk: added new recipe
perseoGI Jun 13, 2025
ac1c0d2
Merge branch 'main' into pgi/new/emsdk
czoido Jun 16, 2025
8f27571
wip
czoido Jun 16, 2025
12820bd
wip
czoido Jun 16, 2025
1768ae6
wip
czoido Jun 16, 2025
8bfa242
wip
czoido Jun 16, 2025
240b479
wip
czoido Jun 16, 2025
e01773a
wip
czoido Jun 16, 2025
4c44611
wip
perseoGI Jun 16, 2025
e62adda
Removed requirements
perseoGI Jun 17, 2025
334f376
try to fix output
czoido Jun 17, 2025
48823f6
Updated profiles, readme and ci
perseoGI Jun 17, 2025
06b9bd6
Updated CI, build only changed recipes
perseoGI Jun 17, 2025
43ab52f
Updated readme
perseoGI Jun 17, 2025
ecb363a
Moved general config to base CI profile
perseoGI Jun 19, 2025
fa69614
Fix error
perseoGI Jun 19, 2025
d08d241
Fix error
perseoGI Jun 19, 2025
44f11d9
Added user_toolchain example on native profile
perseoGI Jun 24, 2025
c080b28
Restore build_folder_vars
perseoGI Jun 25, 2025
81f7789
Added threads comment
perseoGI Jun 25, 2025
6ff3792
Removed unneded min node version
perseoGI Jun 25, 2025
c5746ce
Update requirements.txt
czoido Jul 1, 2025
3c475c9
Removed asmjs profile
perseoGI Jul 1, 2025
9736f29
Applied some review suggestions
perseoGI Jul 2, 2025
9ec67da
Removed unneded builddirs environment as already included by Emscript…
perseoGI Jul 3, 2025
073931e
Removed auto install conf in profile
perseoGI Jul 3, 2025
64ef64c
Restored original test_package and created test_example
perseoGI Jul 4, 2025
b1fe0cb
Fix ci
perseoGI Jul 4, 2025
6e335fb
Testing publish profiles workflow
perseoGI Jul 4, 2025
cbc1e53
WIP
perseoGI Jul 4, 2025
a5ed161
WIP
perseoGI Jul 4, 2025
c6aae81
Publish profiles
perseoGI Jul 4, 2025
dfe8b47
Publish profiles
perseoGI Jul 4, 2025
8dc22f5
Publish profiles
perseoGI Jul 4, 2025
92be15d
Publish profiles
perseoGI Jul 4, 2025
516d065
Removed support for multi versions in CI
perseoGI Jul 8, 2025
25e9b71
Remove only if exists sanity file
perseoGI Oct 31, 2025
6999382
Remove unneded emsdk_env_file
perseoGI Dec 4, 2025
d7c2a3d
Move env generation to package method
perseoGI Dec 4, 2025
d674496
Use finalize and remove unneded environment script
perseoGI Dec 5, 2025
f90c65c
Removed all CI related changes simplifying PR
perseoGI Dec 19, 2025
568e1c9
Bump to v4.0.22
perseoGI Dec 19, 2025
067adf5
Move confusing local profile inside the README
perseoGI Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions conan_config/profiles/emsdk/.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[settings]
build_type=Release
compiler=emcc
compiler.cppstd=17
compiler.libcxx=libc++
# Choose between both types of multithreading support (or none)
# compiler.threads=<posix|wasm_workers>
compiler.version=4.0.22
os=Emscripten

[tool_requires]
ninja/[*]
emsdk/4.0.22

[conf]
tools.build:exelinkflags+=['-sALLOW_MEMORY_GROWTH=1']
tools.build:sharedlinkflags+=['-sALLOW_MEMORY_GROWTH=1']

# Set Ninja as default generator as it will avoid Windows issues
tools.cmake.cmaketoolchain:generator=Ninja

# Distinguish between architectures
tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch']
150 changes: 150 additions & 0 deletions conan_config/profiles/emsdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# EMSDK - Emscripten Compiler Profiles for Conan

This repository provides a collection of pre-built Conan profiles that can be
used as a reference for **cross-compiling projects to WebAssembly (WASM)**.

📘 For detailed information and usage examples, refer to the official [Conan
documentation](https://docs.conan.io/2/examples/cross_build/emscripten.html#setting-up-conan-profile-for-webassembly-wasm).

💡 Issues and suggestions are welcome via the `conan-toolchains` repository.
Contributions are encouraged!


## 🧭 Introduction

As of **Conan 2.18**, the `emcc` compiler (from
[Emscripten](https://emscripten.org/docs/)) is natively supported. This allows
accurate modeling of the compiler’s built-in features.

The current `emcc` compiler model includes the following settings:

```
emcc:
version: [ANY]
libcxx: [null, libstdc++, libstdc++11, libc++]
threads: [null, posix, wasm_workers]
cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23, 26, gnu26]
cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
```

> ℹ️ Note: `emcc` is a front-end for the `clang` compiler, so it shares many of the same settings and options.

However, there are a few **important caveats** to consider when using Emscripten with Conan.


## ⚠️ Caveats

### 🔄 ABI Incompatibility

There is **no ABI compatibility guarantee** between different `emcc` versions.
Refer to the [Emscripten Changelog](https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md) for details.

In Conan v2, the `compiler.version` setting mostly acts as **syntactic sugar**. While it doesn't influence compilation directly, it:

- Enables Conan to apply version-specific flags, compile and link flags.
- Ensures distinct **package IDs** for different compiler versions.

To prevent ABI issues, make sure your **Conan profile's `compiler.version`**
matches the **actual `emsdk` version** used. This ensures consistent builds and
automatic recompilation when updating `emsdk`.



### 🧵 Multithreading

The `emcc` compiler can produce incompatible binaries depending on whether threading is enabled.

Refer to the Emscripten documentation on [pthreads](https://emscripten.org/docs/porting/pthreads.html#compiling-with-pthreads-enabled)
and [wasm workers](https://emscripten.org/docs/api_reference/wasm_workers.html).

Emscripten supports two threading models:

- **POSIX Threads (Pthreads)**
- **Wasm Workers**

These are **mutually incompatible**, and this distinction must be reflected in the Conan profile to avoid mixing binaries.

Enable threading in your profile by setting:

```ini
compiler.threads=posix # For Pthreads
compiler.threads=wasm_workers # For Wasm Workers
```

Conan will automatically inject the necessary linker flags.

## 📂 Profile Overview

This repository includes the following profiles:
- `wasm32`: WebAssembly 32-bit target (default).
- `wasm64`: Experimental 64-bit WebAssembly target (for projects needing >4 GB dynamic memory).


⚠️ WASM64 caveats:

The latest node version `emsdk/4.0.22` installs is the `node/22.16.0`, which can not run directly wasm64 binaries.
Also, it is not valid to compile wasm64 with `-sMIN_NODE_VERSION=221600`, see following error:
```
em++: warning: MIN_NODE_VERSION=221600 is not compatible with MEMORY64 (230000 or above required) [-Wcompatibility]
```
Thus, to be able to run an `wasm64` binary you will need to download at least `node/23` manually in your system.

### 🛠️ Local emsdk installation

If you wish to use your locally installed emsdk instead of the Conan-managed one, here is the proposed emsdk profile

```
include(./.base)

[platform_tool_requires]
emsdk/[*]

[conf]
tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'}
# Add local Emscripten toolchain
# tools.cmake.cmaketoolchain:user_toolchain=["/path/to/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"]

[buildenv]
CC=emcc
CXX=em++
AR=emar
NM=emnm
RANLIB=emranlib
STRIP=emstrip
```

The `platform_tool_requires` will tell Conan to not use the `emsdk` recipe but your system installed binary

- Ensure `emcc`, `em++`, `emar`, etc. are available in your `PATH`.
- Check the `[buildenv]` section in the profile.
- Optionally, provide your own `Emscripten.cmake` via the `user_toolchains` setting.
- Define the arch setting: `wasm`, `wasm64`, or the mostly deprecated `asm.js`.


## ▶️ Usage

After installing the profiles (TBD), you can build your project like this:

```
$ conan build <path> -pr emsdk/wasm32
```

### 🧠 Dynamic Memory Allocation

By default, WebAssembly does not allow dynamic memory growth. To enable it, you must set the following linker flag:

`-s ALLOW_MEMORY_GROWTH=1`

Our base profiles enable this flag by default to simplify usage.

If you want to disable memory growth, simply remove or comment out the relevant line in the profile.

🔎 The Conan docs explain the dynamic memory limits for each architecture.
These are also preconfigured in the `wasm32` and `wasm64` profiles and can be
customized as needed, including the `INITIAL_MEMORY` setting.


### 🙌 Contribute

Feel free to open issues or pull requests in the `conan-toolchains` repository.
Contributions to extend or improve these profiles are welcome!
8 changes: 8 additions & 0 deletions conan_config/profiles/emsdk/wasm32
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
include(./.base)

[settings]
arch=wasm

[conf]
tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB']
12 changes: 12 additions & 0 deletions conan_config/profiles/emsdk/wasm64
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
include(./.base)

[settings]
arch=wasm64

[conf]
# In this early stage of wasm64, ALLOW_MEMORY_GROWTH is not having effect. Also it may not be the most efficient solution.
Comment thread
perseoGI marked this conversation as resolved.
# wasm64 for now needs to declare INITIAL_MEMORY as the maximum memory
tools.build:exelinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB', '-sASSERTIONS']
tools.build:sharedlinkflags+=['-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB', '-sASSERTIONS']

# Node version from emsdk/4.0.22 can not run a wasm64 binary. See more details in README
4 changes: 4 additions & 0 deletions recipes/emsdk/all/conandata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sources:
"4.0.22":
url: "https://github.com/emscripten-core/emsdk/archive/4.0.22.tar.gz"
sha256: "8f5e26de5c103c41ae04411e1be1ffc17983fb368343b15102a1859b3aa64626"
107 changes: 107 additions & 0 deletions recipes/emsdk/all/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import os
from pathlib import Path

from conan import ConanFile
from conan.tools.env import VirtualBuildEnv
from conan.tools.files import chdir, copy, get
from conan.tools.layout import basic_layout

required_conan_version = ">=2.1"


class EmSDKConan(ConanFile):
name = "emsdk"
description = "Emscripten SDK. Emscripten is an Open Source LLVM to JavaScript compiler"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://github.com/kripken/emscripten"
topics = ("emsdk", "emscripten", "sdk", "emcc", "em++", "nodejs")
license = "MIT"
package_type = "application"
settings = "os", "arch"

def layout(self):
basic_layout(self, src_folder="src")

def source(self):
get(self, **self.conan_data["sources"][self.version], destination=self.source_folder, strip_root=True)

@property
def _emscripten(self):
return os.path.join(self.package_folder, "bin", "upstream", "emscripten")

@property
def _node_path(self):
subfolders = [path for path in (Path(self.package_folder) / "bin" / "node").iterdir() if path.is_dir()]
if len(subfolders) != 1:
return None
return os.path.join("bin", "node", subfolders[0].name, "bin")

def generate(self):
# To avoid issues when cross-compiling or with not common arch in profiles we need to set EMSDK_ARCH
# This is important for the emsdk install command
env = VirtualBuildEnv(self)
# Special consideration for armv8 as emsdk expects "arm64"
arch = "arm64" if str(self.settings.arch) == "armv8" else str(self.settings.arch)
env.environment().define("EMSDK_ARCH", arch)
env.generate()

def build(self):
with chdir(self, self.source_folder):
emsdk = "emsdk.bat" if self.settings_build.os == "Windows" else "./emsdk"
self.run(f"{emsdk} install latest")
self.run(f"{emsdk} activate latest")

def package(self):
copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
copy(self, "*", src=self.source_folder, dst=os.path.join(self.package_folder, "bin"))

def finalize(self):
copy(self, "*", src=self.immutable_package_folder, dst=self.package_folder)
embuilder = os.path.join(
self._emscripten, "embuilder" if self.info.settings.os != "Windows" else "embuilder.bat"
)
self.run(f"{embuilder} build MINIMAL")

def _define_tool_var(self, value):
suffix = ".bat" if self.settings.os == "Windows" else ""
path = os.path.join(self._emscripten, f"{value}{suffix}")
return path

def package_info(self):
self.cpp_info.bindirs = ["bin", os.path.join("bin", "upstream", "emscripten"), self._node_path]
self.cpp_info.includedirs = []
self.cpp_info.libdirs = []
self.cpp_info.resdirs = []

# If we are not building for Emscripten, probably we don't want to inject following environment variables,
# but it might be legit use cases... until we find them, let's be conservative.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way to be conservative is raise an error. If the recipe is used in some way we are not aware of, for example as a regular requires (without settings_target), then better raise an error, get the user report and learn about the use cases that moving forward and we will never know about it (until it is too late, and they are stuck and we can't unblock)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⬆️

if not hasattr(self, "settings_target") or self.settings_target is None:
return

if self.settings_target.os != "Emscripten":
self.output.warning(
f"You've added {self.name}/{self.version} as a build requirement, while os={self.settings_target.os} != Emscripten"
)
return

toolchain = os.path.join(
self.package_folder, "bin", "upstream", "emscripten", "cmake", "Modules", "Platform", "Emscripten.cmake"
)
self.conf_info.prepend("tools.cmake.cmaketoolchain:user_toolchain", toolchain)

self.buildenv_info.define_path("EMSDK", os.path.join(self.package_folder, "bin"))
self.buildenv_info.define_path("EMSCRIPTEN", self._emscripten)
self.buildenv_info.define_path("EM_CONFIG", os.path.join(self.package_folder, "bin", ".emscripten"))
self.buildenv_info.define_path("EM_CACHE", os.path.join(self.package_folder, "bin", ".emscripten_cache"))

compiler_executables = {
"c": self._define_tool_var("emcc"),
"cpp": self._define_tool_var("em++"),
}
self.conf_info.update("tools.build:compiler_executables", compiler_executables)
self.buildenv_info.define_path("CC", compiler_executables["c"])
self.buildenv_info.define_path("CXX", compiler_executables["cpp"])
self.buildenv_info.define_path("AR", self._define_tool_var("emar"))
self.buildenv_info.define_path("NM", self._define_tool_var("emnm"))
self.buildenv_info.define_path("RANLIB", self._define_tool_var("emranlib"))
self.buildenv_info.define_path("STRIP", self._define_tool_var("emstrip"))
16 changes: 16 additions & 0 deletions recipes/emsdk/all/test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from conan import ConanFile
from conan.tools.build import can_run


class TestPackageConan(ConanFile):
settings = "os", "arch", "compiler", "build_type"

def requirements(self):
self.requires(self.tested_reference_str)

def test(self):
# Check the package provides working binaries
if can_run(self):
self.run("emcc -v", env="conanrun")
self.run("em++ -v", env="conanrun")
self.run("node -v", env="conanrun")
3 changes: 3 additions & 0 deletions recipes/emsdk/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
versions:
"4.0.22":
folder: all