-
Notifications
You must be signed in to change notification settings - Fork 5
emsdk: new recipe #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
695ba52
ac1c0d2
8f27571
12820bd
1768ae6
8bfa242
240b479
e01773a
4c44611
e62adda
334f376
48823f6
06b9bd6
43ab52f
ecb363a
fa69614
d08d241
44f11d9
c080b28
81f7789
6ff3792
c5746ce
3c475c9
9736f29
9ec67da
073931e
64ef64c
b1fe0cb
6e335fb
cbc1e53
a5ed161
c6aae81
dfe8b47
8dc22f5
92be15d
516d065
25e9b71
6999382
d7c2a3d
d674496
f90c65c
568e1c9
067adf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'] |
| 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! |
| 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'] |
| 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. | ||
| # 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 | ||
| 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" |
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")) | ||
| 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") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| versions: | ||
| "4.0.22": | ||
| folder: all |
Uh oh!
There was an error while loading. Please reload this page.