[emscripten] Add emsdk to DEPS for Wasm builds

This can be used (manually) to build with CMake.
It is also planned to be used (automatically) with gn.

activate-emsdk comes from here, but modifications and new code:
https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/bin/activate-emsdk

Split out from Loko's original CL, with a few updates.
https://dawn-review.googlesource.com/c/dawn/+/227136

Building with .emscripten.emsdk_original works on both Mac and Windows.
Building with the overridden .emscripten has the following caveats:
- CMake on Mac doesn't work because llvm-build is missing llvm-ranlib
  (which is supposed to be a symlink to llvm-ar, but it must be invoked
  with the correct executable name; adding a symlink makes it work).
- CMake (and probably gn) on Win doesn't work because llvm-build is
  missing clang.exe (though this can be fixed by copying clang-cl.exe),
  llvm-ar.exe, and presumably others.
- CMake (and possibly gn) on Win only works with a native Ninja install
  (ninja.exe), not depot_tools (ninja.bat).

Bug: 371024051
Co-authored-by: Lokbondo Kung <lokokung@google.com>
Change-Id: I901cc814835e182f95ce73927cf151bc1ab7b07f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/228554
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/.gitmodules b/.gitmodules
index ace6328..09cb170 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,23 +4,27 @@
 	gclient-condition = dawn_standalone
 [submodule "third_party/clang-format/script"]
 	path = third_party/clang-format/script
-	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format
+	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/depot_tools"]
 	path = third_party/depot_tools
-	url = https://chromium.googlesource.com/chromium/tools/depot_tools
+	url = https://chromium.googlesource.com/chromium/tools/depot_tools.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/libc++/src"]
 	path = third_party/libc++/src
-	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx
+	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/libc++abi/src"]
 	path = third_party/libc++abi/src
-	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi
+	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git
+	gclient-condition = dawn_standalone
+[submodule "third_party/llvm-libc/src"]
+	path = third_party/llvm-libc/src
+	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/libdrm/src"]
 	path = third_party/libdrm/src
-	url = https://chromium.googlesource.com/chromiumos/third_party/libdrm
+	url = https://chromium.googlesource.com/chromiumos/third_party/libdrm.git
 	gclient-condition = dawn_standalone and host_os == "linux"
 [submodule "build"]
 	path = build
@@ -40,7 +44,7 @@
 	gclient-condition = dawn_standalone
 [submodule "third_party/libFuzzer/src"]
 	path = third_party/libFuzzer/src
-	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/compiler-rt/lib/fuzzer
+	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/compiler-rt/lib/fuzzer.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/googletest"]
 	path = third_party/googletest
@@ -48,11 +52,11 @@
 	gclient-condition = dawn_standalone
 [submodule "third_party/catapult"]
 	path = third_party/catapult
-	url = https://chromium.googlesource.com/catapult
+	url = https://chromium.googlesource.com/catapult.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/google_benchmark/src"]
 	path = third_party/google_benchmark/src
-	url = https://chromium.googlesource.com/external/github.com/google/benchmark
+	url = https://chromium.googlesource.com/external/github.com/google/benchmark.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/jinja2"]
 	path = third_party/jinja2
@@ -142,17 +146,21 @@
 	path = third_party/webgpu-cts
 	url = https://chromium.googlesource.com/external/github.com/gpuweb/cts
 	gclient-condition = build_with_chromium
+[submodule "third_party/emsdk"]
+	path = third_party/emsdk
+	url = https://github.com/emscripten-core/emsdk.git
+	gclient-condition = dawn_wasm
 [submodule "third_party/node-api-headers"]
 	path = third_party/node-api-headers
-	url = https://github.com/nodejs/node-api-headers
+	url = https://github.com/nodejs/node-api-headers.git
 	gclient-condition = dawn_node
 [submodule "third_party/node-addon-api"]
 	path = third_party/node-addon-api
-	url = https://github.com/nodejs/node-addon-api
+	url = https://github.com/nodejs/node-addon-api.git
 	gclient-condition = dawn_node
 [submodule "third_party/gpuweb"]
 	path = third_party/gpuweb
-	url = https://github.com/gpuweb/gpuweb
+	url = https://github.com/gpuweb/gpuweb.git
 	gclient-condition = dawn_node
 [submodule "third_party/protobuf"]
 	path = third_party/protobuf
@@ -164,20 +172,17 @@
 	gclient-condition = dawn_standalone
 [submodule "third_party/libprotobuf-mutator/src"]
 	path = third_party/libprotobuf-mutator/src
-	url = https://chromium.googlesource.com/external/github.com/google/libprotobuf-mutator
+	url = https://chromium.googlesource.com/external/github.com/google/libprotobuf-mutator.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/jsoncpp"]
 	path = third_party/jsoncpp
-	url = https://github.com/open-source-parsers/jsoncpp
+	url = https://github.com/open-source-parsers/jsoncpp.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/langsvr"]
 	path = third_party/langsvr
-	url = https://github.com/google/langsvr
+	url = https://github.com/google/langsvr.git
 	gclient-condition = dawn_standalone
 [submodule "third_party/partition_alloc"]
 	path = third_party/partition_alloc
-	url = https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator
+	url = https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator.git
 	gclient-condition = dawn_standalone
-[submodule "third_party/llvm-libc/src"]
-	path = third_party/llvm-libc/src
-	url = https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git
diff --git a/DEPS b/DEPS
index 137bd37..3a1546f 100644
--- a/DEPS
+++ b/DEPS
@@ -16,6 +16,7 @@
 
   'dawn_standalone': True,
   'dawn_node': False, # Also fetches dependencies required for building NodeJS bindings.
+  'dawn_wasm': False, # Also fetches dependencies required for building WebAssembly.
   'dawn_cmake_version': 'version:2@3.23.3',
   'dawn_cmake_win32_sha1': 'b106d66bcdc8a71ea2cdf5446091327bfdb1bcd7',
   'dawn_gn_version': 'git_revision:182a6eb05d15cc76d2302f7928fdb4f645d52c53',
@@ -367,6 +368,12 @@
     'condition': 'build_with_chromium',
   },
 
+  # Dependencies required to build / run WebAssembly bindings
+  'third_party/emsdk': {
+    'url': '{github_git}/emscripten-core/emsdk.git@127ce42cd5f0aabe2d9b5d636041ccef7c66d165',
+    'condition': 'dawn_wasm',
+  },
+
   # Dependencies required to build / run Dawn NodeJS bindings
   'third_party/node-api-headers': {
     'url': '{github_git}/nodejs/node-api-headers.git@d5cfe19da8b974ca35764dd1c73b91d57cd3c4ce',
@@ -619,11 +626,11 @@
                '-o', 'build/util/LASTCHANGE'],
   },
 
-  # Node binaries, when dawn_node is enabled
+  # Node binaries, when dawn_node or dawn_wasm is enabled
   {
-    'name': 'node_linux64',
+    'name': 'node_linux',
     'pattern': '.',
-    'condition': 'dawn_node and host_os == "linux"',
+    'condition': '(dawn_node or dawn_wasm) and host_os == "linux"',
     'action': [ 'python3',
                 'third_party/depot_tools/download_from_google_storage.py',
                 '--no_resume',
@@ -635,9 +642,9 @@
     ],
   },
   {
-    'name': 'node_mac',
+    'name': 'node_mac_x64',
     'pattern': '.',
-    'condition': 'dawn_node and host_os == "mac"',
+    'condition': '(dawn_node or dawn_wasm) and host_os == "mac" and host_cpu == "x64"',
     'action': [ 'python3',
                 'third_party/depot_tools/download_from_google_storage.py',
                 '--no_resume',
@@ -651,7 +658,7 @@
   {
     'name': 'node_mac_arm64',
     'pattern': '.',
-    'condition': 'dawn_node and host_os == "mac"',
+    'condition': '(dawn_node or dawn_wasm) and host_os == "mac" and host_cpu == "arm64"',
     'action': [ 'python3',
                 'third_party/depot_tools/download_from_google_storage.py',
                 '--no_resume',
@@ -665,7 +672,7 @@
   {
     'name': 'node_win',
     'pattern': '.',
-    'condition': 'dawn_node and host_os == "win"',
+    'condition': '(dawn_node or dawn_wasm) and host_os == "win"',
     'action': [ 'python3',
                 'third_party/depot_tools/download_from_google_storage.py',
                 '--no_resume',
@@ -676,6 +683,48 @@
     ],
   },
 
+  # Activate emsdk for WebAssembly builds
+  {
+    'name': 'activate_emsdk_linux',
+    'pattern': '.',
+    'condition': 'dawn_wasm and host_os == "linux"',
+    'action': [ 'python3',
+                'tools/activate-emsdk',
+                '--node', 'third_party/node/node-linux-x64/bin/node',
+                '--llvm', 'third_party/llvm-build/Release+Asserts/bin'
+    ],
+  },
+  {
+    'name': 'activate_emsdk_mac_x64',
+    'pattern': '.',
+    'condition': 'dawn_wasm and host_os == "mac" and host_cpu == "x64"',
+    'action': [ 'python3',
+                'tools/activate-emsdk',
+                '--node', 'third_party/node/node-darwin-x64/bin/node',
+                '--llvm', 'third_party/llvm-build/Release+Asserts/bin'
+    ],
+  },
+  {
+    'name': 'activate_emsdk_mac_arm64',
+    'pattern': '.',
+    'condition': 'dawn_wasm and host_os == "mac" and host_cpu == "arm64"',
+    'action': [ 'python3',
+                'tools/activate-emsdk',
+                '--node', 'third_party/node/node-darwin-arm64/bin/node',
+                '--llvm', 'third_party/llvm-build/Release+Asserts/bin'
+    ],
+  },
+  {
+    'name': 'activate_emsdk_win',
+    'pattern': '.',
+    'condition': 'dawn_wasm and host_os == "win"',
+    'action': [ 'python3',
+                'tools/activate-emsdk',
+                '--node', 'third_party/node/node.exe',
+                '--llvm', 'third_party/llvm-build/Release+Asserts/bin'
+    ],
+  },
+
   # Download remote exec cfg files
   {
     'name': 'fetch_reclient_cfgs',
diff --git a/OWNERS b/OWNERS
index 1e93f87..892baf4 100644
--- a/OWNERS
+++ b/OWNERS
@@ -27,6 +27,7 @@
 per-file third_party/depot_tools=*
 per-file third_party/dxc=*
 per-file third_party/dxheaders=*
+per-file third_party/emsdk=*
 per-file third_party/glfw=*
 per-file third_party/glslang/src=*
 per-file third_party/google_benchmark/src=*
diff --git a/docs/quickstart-cmake.md b/docs/quickstart-cmake.md
index 1f8f6b7..fc9513d 100644
--- a/docs/quickstart-cmake.md
+++ b/docs/quickstart-cmake.md
@@ -8,7 +8,7 @@
 - A compatible platform (e.g. Windows, macOS, Linux, etc.). Most platforms are fully supported.
 - A compatible C++ compiler supporting at least C++17. Most major compilers are supported.
 - Git for interacting with the Dawn source code repository.
-- CMake for building your project and Dawn. Dawn supports CMake 3.13+.
+- CMake for building your project and Dawn. Dawn supports CMake 3.16+.
 
 ## Getting the Dawn Code
 
diff --git a/scripts/standalone-with-wasm.gclient b/scripts/standalone-with-wasm.gclient
new file mode 100644
index 0000000..47976e1
--- /dev/null
+++ b/scripts/standalone-with-wasm.gclient
@@ -0,0 +1,13 @@
+# Copy this file to <dawn clone dir>/.gclient to bootstrap gclient in a
+# standalone checkout of Dawn for building emdawnwebgpu using emsdk.
+
+solutions = [
+  { "name"        : ".",
+    "url"         : "https://dawn.googlesource.com/dawn",
+    "deps_file"   : "DEPS",
+    "managed"     : False,
+    "custom_vars" : {
+      "dawn_wasm" : True,
+    }
+  },
+]
diff --git a/third_party/emsdk b/third_party/emsdk
new file mode 160000
index 0000000..127ce42
--- /dev/null
+++ b/third_party/emsdk
@@ -0,0 +1 @@
+Subproject commit 127ce42cd5f0aabe2d9b5d636041ccef7c66d165
diff --git a/tools/activate-emsdk b/tools/activate-emsdk
new file mode 100644
index 0000000..3d1b927
--- /dev/null
+++ b/tools/activate-emsdk
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# Copyright 2025 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import subprocess
+import sys
+import sysconfig
+import textwrap
+
+EMSDK_ROOT = os.path.join('third_party', 'emsdk')
+
+EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+EMSDK_CONFIG_PATH = os.path.join(EMSDK_ROOT, '.emscripten')
+
+EMSDK_VERSION = '4.0.3'
+
+def main(args):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--node', help='Path to nodejs binary.')
+    parser.add_argument('--llvm', help='Path to LLVM binaries.')
+    options = parser.parse_args();
+
+    # Install and activate the default dependencies for emsdk.
+    try:
+        subprocess.check_call([sys.executable, EMSDK_PATH, 'install', EMSDK_VERSION])
+    except subprocess.CalledProcessError:
+        print ('Failed to install emsdk')
+        return 1
+    try:
+        subprocess.check_call([sys.executable, EMSDK_PATH, 'activate', EMSDK_VERSION])
+    except subprocess.CalledProcessError:
+        print ('Failed to activate emsdk')
+        return 1
+
+    # Back up the .emscripten file generated by emsdk.
+    os.replace(EMSDK_CONFIG_PATH, EMSDK_CONFIG_PATH + ".emsdk_original")
+    # Override the default generated .emscripten file for certain binaries.
+    em_config = textwrap.dedent(f"""\
+        import os
+        emsdk_path = os.path.dirname(os.getenv('EM_CONFIG')).replace('\\\\', '/')
+        NODE_JS = emsdk_path + '/../../{options.node}'
+        LLVM_ROOT = emsdk_path + '/../../{options.llvm}'
+        BINARYEN_ROOT = emsdk_path + '/upstream'
+        EMSCRIPTEN_ROOT = emsdk_path + '/upstream/emscripten'
+        """)
+    with open(EMSDK_CONFIG_PATH, 'w') as f:
+        f.write(em_config)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))