smhk

Setting up Emscripten with CMake in Git Bash on Windows 10

These notes cover in detail how to set up Emscripten with CMake in Git Bash on Windows 10.

Install prerequisites §

Install Python 3.10 for Windows.

Open Git Bash (included with Git for Windows).

Create alias for python and python3 following steps from here, i.e.:

echo "alias python='winpty python.exe'" >> ~/.bashrc
echo "alias python3='winpty python.exe'" >> ~/.bashrc

Then restart Git Bash so it automatically loads the .bashrc.

(Note that if you’ve not done this before, Git Bash may give a warning next time you start to tell you it has automatically created a ~/.bash_profile which loads the .bashrc for you).

In Windows, go to Settings → Manage app execution aliases. Disable “App installer” for python.exe and python3.exe, to prevent the Windows Store from popping up every time you run python or python3 in Git Bash.

Follow Emscripten instructions using Git Bash §

Follow along with the official instructions, inside Git Bash.

Clone the emsdk repo:

git clone https://github.com/emscripten-core/emsdk.git

(Or, add it as a git submodule to your existing repo):

git submodule add https://github.com/emscripten-core/emsdk.git

Enter the emsdk directory:

cd emsdk

Run ./emsdk install latest:

$ ./emsdk install latest
Resolving SDK alias 'latest' to '3.1.8'
Resolving SDK version '3.1.8' to 'sdk-releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'
Installing SDK 'sdk-releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'..
Installing tool 'node-14.18.2-64bit'..
Downloading: C:/MyProject/emsdk/zips/node-v14.18.2-win-x64.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v14.18.2-win-x64.zip, 30570907 Bytes
 [----------------------------------------------------------------------------]
Unpacking 'C:/MyProject/emsdk/zips/node-v14.18.2-win-x64.zip' to 'C:/MyProject/emsdk/node/14.18.2_64bit'
Done installing tool 'node-14.18.2-64bit'.
Installing tool 'python-3.9.2-nuget-64bit'..
Downloading: C:/MyProject/emsdk/zips/python-3.9.2-4-amd64+pywin32.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/python-3.9.2-4-amd64+pywin32.zip, 14413267 Bytes
 [----------------------------------------------------------------------------]
Unpacking 'C:/MyProject/emsdk/zips/python-3.9.2-4-amd64+pywin32.zip' to 'C:/MyProject/emsdk/python/3.9.2-nuget_64bit'
Done installing tool 'python-3.9.2-nuget-64bit'.
Installing tool 'java-8.152-64bit'..
Downloading: C:/MyProject/emsdk/zips/portable_jre_8_update_152_64bit.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/portable_jre_8_update_152_64bit.zip, 69241499 Bytes
 [----------------------------------------------------------------------------]
Unpacking 'C:/MyProject/emsdk/zips/portable_jre_8_update_152_64bit.zip' to 'C:/MyProject/emsdk/java/8.152_64bit'
Done installing tool 'java-8.152-64bit'.
Installing tool 'releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'..
Downloading: C:/MyProject/emsdk/zips/8c9e0a76ebed2c5e88a718d43e8b62452def3771-wasm-binaries.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/win/8c9e0a76ebed2c5e88a718d43e8b62452def3771/wasm-binaries.zip, 414615769 Bytes
 [----------------------------------------------------------------------------]
Unpacking 'C:/MyProject/emsdk/zips/8c9e0a76ebed2c5e88a718d43e8b62452def3771-wasm-binaries.zip' to 'C:/MyProject/emsdk/upstream'
Done installing tool 'releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'.
Done installing SDK 'sdk-releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'.

Run ./emsdk activate latest:

$ ./emsdk activate latest
The changes made to environment variables only apply to the currently running shell instance. Use the 'emsdk_env.bat' to re-enter this environment later, or if you'd like to register this environment permanently, rerun this command with the option --permanent.
Resolving SDK alias 'latest' to '3.1.8'
Resolving SDK version '3.1.8' to 'sdk-releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit'
Setting the following tools as active:
   node-14.18.2-64bit
   python-3.9.2-nuget-64bit
   java-8.152-64bit
   releases-upstream-8c9e0a76ebed2c5e88a718d43e8b62452def3771-64bit

Run source ./emsdk_env.sh:

$ source ./emsdk_env.sh
Adding directories to PATH:
PATH += /c/MyProject/emsdk
PATH += /c/MyProject/emsdk/upstream/emscripten
PATH += /c/MyProject/emsdk/node/14.18.2_64bit/bin

Setting environment variables:
PATH = /c/MyProject/emsdk:/c/MyProject/emsdk/upstream/emscripten:/c/MyProject/emsdk/node/14.18.2_64bit/bin:/c/Program Files/Git/mingw64/bin:/c/Program Files/Git/usr/local/bin:/c/Program Files/Git/usr/bin:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0:/c/Program Files/Java/jre7/bin:/c/Program Files (x86)/Windows Live/Shared:/c/WINDOWS/System32/OpenSSH:/c/Program Files/Git/cmd:/c/Program Files (x86)/CMake/bin
EMSDK = C:/MyProject/emsdk
EM_CONFIG = C:\MyProject\emsdk\.emscripten
EMSDK_NODE = C:/MyProject/emsdk/node/14.18.2_64bit/bin/node.exe
EMSDK_PYTHON = C:/MyProject/emsdk/python/3.9.2-nuget_64bit/python.exe
JAVA_HOME = C:/MyProject/emsdk/java/8.152_64bit

Tutorial §

Now follow through the Emscripten tutorial.

$ cd upstream/emscripten
$ ./emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.8 (3ff7eff373d31d8c0179895165462019221f192e)
clang version 15.0.0 (https://github.com/llvm/llvm-project 80ec0ebfdc5692a58e0832125f2c6a991df9d63f)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: C:\MyProject\emsdk\upstream\bin

Command line “hello world” §

Build the included hello world:

$ ./emcc tests/hello_world.c
shared:INFO: (Emscripten: Running sanity checks)

Run it:

$ node a.out.js
hello, world!

HTML “hello world” §

Build as HTML:

$ ./emcc tests/hello_world.c -o hello.html

Note that on most browsers you can’t just open the hello.html because they do not support file:// XHR requests. As a workaround, instead run python -m http.server in the build directory to start a Python3 webserver. Then go in the browser to: http://localhost:8000/hello.html

Setting up SDL §

SDL1 vs SDL2 in Emscripten §

SDL1 is included in Emscripten but SDL2 is not. However, SDL2 is included in ports.

Here is an SDL2 example.

  • Note that use of -sUSE_SDL=2 to enable #include "SDL2/SDL.h" for using SDL2.
  • Note that SDL2_image and SDL2_ttf are separate ports.

SDL2 in Emscripten ports §

Run ./emcc --show-ports to see all available ports:

$ ./emcc --show-ports
Available ports:
    Boost headers v1.70.0 (USE_BOOST_HEADERS=1; Boost license)
    bullet (USE_BULLET=1; zlib license)
    bzip2 (USE_BZIP2=1; BSD license)
    cocos2d
    freetype (USE_FREETYPE=1; freetype license)
    giflib (USE_GIFLIB=1; MIT license)
    harfbuzz (USE_HARFBUZZ=1; MIT license)
    icu (USE_ICU=1; Unicode License)
    libjpeg (USE_LIBJPEG=1; BSD license)
    libmodplug (USE_MODPLUG=1; public domain)
    libpng (USE_LIBPNG=1; zlib license)
    mpg123 (USE_MPG123=1; zlib license)
    ogg (USE_OGG=1; zlib license)
    regal (USE_REGAL=1; Regal license)
    SDL2 (USE_SDL=2; zlib license)
    SDL2_gfx (zlib license)
    SDL2_image (USE_SDL_IMAGE=2; zlib license)
    SDL2_mixer (USE_SDL_MIXER=2; zlib license)
    SDL2_net (zlib license)
    SDL2_ttf (USE_SDL_TTF=2; zlib license)
    vorbis (USE_VORBIS=1; zlib license)
    zlib (USE_ZLIB=1; zlib license)

If using cmake, need to add extra options to enable the ports, e.g. -sUSE_SDL=2:

Something like:

if( ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
    set(USE_FLAGS "-s USE_SDL=2 -s USE_FREETYPE=1")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS}")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${USE_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${USE_FLAGS}")
    set(CMAKE_EXECUTABLE_SUFFIX .html)
else()
    find_package(SDL2 REQUIRED)
    find_package(Freetype REQUIRED)
endif()

Note the use of the if statement for the flags that only apply to Emscripten.

Building §

Overview §

First, for comparison, if you’re not using Emscripten you might be used to building by running the following commands:

cmake -S . -B build
cmake --build build

(Note that this way is preferred over the mkdir build && cd build && cmake .. && cmake --build method that seems to be most commonly used).

By comparison, for Emscripten we do:

emcmake cmake -S . -B build-em
cmake --build build-em

(Note that it is not necessary to use build-em as the directory name, but I prefer using it as a clear distinction from the build directory commonly used for native builds).

What does emcmake do? §

The emcmake command is a very simple wrapper, which adds some additional options to the subsequent call to cmake:

  • -G "MinGW Makefiles", which is necessary for generating the right makefiles for Emscripten.
  • -DCMAKE_TOOLCHAIN_FILE=C:\MyProject\emsdk\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake.
  • -DCMAKE_CROSSCOMPILING_EMULATOR=C:/MyProject/emsdk/node/14.18.2_64bit/bin/node.exe;--experimental-wasm-threads

There is no need to specify the location of SDL2 (e.g. by passing in -DSDL2_PATH="C:\mingw_dev_lib_32"), because emcmake automatically finds SDL2 since we have -sUSE_SDL=2 enabled.

If you do leave the -DSDL2_PATH option in, it will generate the warning:

CMake Warning:
  Manually-specified variables were not used by the project:

    SDL2_PATH

How not to build §

The emcmake cmake -S . -B build-em command only needs to have been called once. Then you can just build as normal with:

cmake --build build-em

Note that you should not use emcmake for this command, else you will get the error:

$ emcmake cmake --build build-em
configure: cmake --build build-em -DCMAKE_TOOLCHAIN_FILE=C:\MyProject\emsdk\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=C:/MyProject/emsdk/node/14.18.2_64bit/bin/node.exe;--experimental-wasm-threads -G "MinGW Makefiles"
Unknown argument -DCMAKE_TOOLCHAIN_FILE=C:\MyProject\emsdk\upstream\emscripten\cmake\Modules\Platform\Emscripten.cmake

Recap §

  • There is some tweaking required to get Emscripten to play nicely under Git Bash, but it’s quite straight-forward.
  • Emscripten is installed via git, either by cloning or via a submodule.
  • The first time you install Emscripten, you must ./emsdk install latest and ./emsdk activate latest.
  • Any time you open a new shell, you must source ./emsdk_env.sh for the Emscripten commands to work.
  • SDL1 is included in Emscripten, but SDL2 is included in Emscripten ports, which requires putting additional options in your CMakeLists.txt file.
  • Use emcmake to wrap the cmake call when generating the make files.
  • Do not use emcmake to wrap the cmake call when performing a build.