Skip to content

Instantly share code, notes, and snippets.

@jeremy-code
Last active May 2, 2025 04:14
Show Gist options
  • Save jeremy-code/825752985d55d0b8ea86f58cd502a109 to your computer and use it in GitHub Desktop.
Save jeremy-code/825752985d55d0b8ea86f58cd502a109 to your computer and use it in GitHub Desktop.
VSCode C/C++ Emscripten Set up

Suppose you want to set up VSCode with Emscripten. In my case, I am using devcontainers with Docker and Emscripten's official Docker image (emscripten/emsdk), but any VSCode installation should do.

Run emconfigure env and you should get an output similar to this:

...
EM_CONFIG=/emsdk/.emscripten
CC=/emsdk/upstream/emscripten/emcc
CXX=/emsdk/upstream/emscripten/em++
AR=/emsdk/upstream/emscripten/emar
LD=/emsdk/upstream/emscripten/emcc
NM=/emsdk/upstream/bin/llvm-nm
LDSHARED=/emsdk/upstream/emscripten/emcc
RANLIB=/emsdk/upstream/emscripten/emranlib
EMSCRIPTEN_TOOLS=/emsdk/upstream/emscripten/tools
HOST_CC=/emsdk/upstream/bin/clang
HOST_CXX=/emsdk/upstream/bin/clang++
HOST_CFLAGS=-W
HOST_CXXFLAGS=-W
PKG_CONFIG_LIBDIR=/emsdk/upstream/emscripten/cache/sysroot/local/lib/pkgconfig:/emsdk/upstream/emscripten/cache/sysroot/lib/pkgconfig
PKG_CONFIG_PATH=
EMSCRIPTEN=/emsdk/upstream/emscripten
ACLOCAL_PATH=/emsdk/upstream/emscripten/cache/sysroot/share/aclocal
CROSS_COMPILE=/emsdk/upstream/emscripten/em
EMMAKEN_JUST_CONFIGURE=1

The specific environment variables you're looking for is HOST_CC or HOST_CXX. By default, you will be using HOST_CXX even when compiling only C, since DEFAULT_TO_CXX is enabled by default.

The reason for this is we need to set up the Compiler Path for the C/C++ VSCode extension. The thing is, our typical entrypoint emcc won't work since it's set up to only work with MSVC, gcc, or Clang, (See emscripten-core/emscripten#11163, microsoft/vscode-cpptools#4269). You could install a different extension for this (e.g. ms-vscode.cmake-tools) for additional configurations, but there is no official Emscripten VSCode extension unfortunately.

If you run emcc --cflags, you'll see output like this:

emcc --cflags
-target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=/emsdk/upstream/emscripten/cache/sysroot -DEMSCRIPTEN -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat

If you check out the emcc documentation, it says:

--cflags [other] Prints out the flags emcc would pass to clang to compile source code to object form. You can use this to invoke clang yourself, and then run emcc on those outputs just for the final linking+conversion to JS.

Somewhat annoyingly, though, compilerArgs in VSCode must be an array of strings and can't be a string. So, using jq (which comes pre-installed in macOS Sequoia), we can do something like this:

jq --arg cflags "$(emcc --cflags)" '.env.EMSCRIPTEN_CFLAGS = ($cflags | split(" "))' << EOF > .vscode/c_cpp_properties.json
{
  "configurations": [
    {
      "name": "Emscripten",
      "compilerPath": "/emsdk/upstream/bin/clang++",
      "compilerArgs": ["\${EMSCRIPTEN_CFLAGS}"],
      "intelliSenseMode": "linux-clang-x64",
      "includePath": ["\${workspaceFolder}/local/include"]
    }
  ],
  "version": 4,
  "enableConfigurationSquiggles": true
}
EOF

which will output this JSON to .vscode/c_cpp_properties.json and update the VSCode C/C++ settings:

{
  "env": {
    "EMSCRIPTEN_CFLAGS": [
      "-target",
      "wasm32-unknown-emscripten",
      "-fignore-exceptions",
      "-mllvm",
      "-combiner-global-alias-analysis=false",
      "-mllvm",
      "-enable-emscripten-sjlj",
      "-mllvm",
      "-disable-lsr",
      "--sysroot=/emsdk/upstream/emscripten/cache/sysroot",
      "-DEMSCRIPTEN",
      "-Xclang",
      "-iwithsysroot/include/fakesdl",
      "-Xclang",
      "-iwithsysroot/include/compat"
    ]
  }
  "configurations": [
    {
      "name": "Emscripten",
      "compilerPath": "/emsdk/upstream/bin/clang++",
      "compilerArgs": ["${EMSCRIPTEN_CFLAGS}"],
      "intelliSenseMode": "linux-clang-x64",
      "includePath": ["${workspaceFolder}/local/include"]
    }
  ],
  "version": 4,
  "enableConfigurationSquiggles": true
}

where configurations[0].compilerPath is $HOST_CXX and intelliSenseMode is your OS (windows, linux, macos) -clang-x64 (Interestingly, when I tried -clang-arm64 it kept rejecting it and switching to x64 even on an ARM system).

You would only have to run this command once. There's probably a way to make it always up-to-date using Go templates or using the aforementioned devcontainers, but I can't imagine the Emscripten changes C flags incredibly frequently.

I included an includePath to my ${workspaceFolder}/local directory in case you wanted to do that, but if all your libraries headers' are all in the system include directories (e.g. /usr/include, /usr/local/include), it's not necessary.

That's all. There's probably a couple of different ways to do this such as using CMake (Emscripten includes an Emscripten.cmake file) or just listing out Emscripten's include directories in includePath (e.g. /emsdk/upstream/emscripten/cache/sysroot/include) but IMO this way is probably as close as you'll get to replicating the behavior of emcc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment