Use emsdk as external bazel dependency (#766)

* Makes provided bazel rules look up @emsdk workspace instead of local workspace
* Uses system-specific emscripten binaries instead of defaulting to linux
* Provides macros for loading emsdk dependencies (nodejs and emscripten binaries)
* Unhardcodes paths in bazel rules and .sh wrappers
* `update_bazel_workspace.sh` now updates `revisions.bzl`
* `emscripten_deps()` can be fed with specific emscripten version
* Adds external usage test

Addresses #650 and #696
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 20288af..6ec789d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -166,7 +166,7 @@
             docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
             make -C ./docker version=${CIRCLE_TAG} alias=latest push
 
-  test-bazel:
+  test-bazel-linux:
     executor: bionic
     steps:
       - checkout
@@ -186,6 +186,21 @@
             apt-get install -q -y bazel
       - run: test/test_bazel.sh
 
+  test-bazel-mac:
+    macos:
+      xcode: "12.2.0"
+    environment:
+      EMSDK_NOTTY: "1"
+      HOMEBREW_NO_AUTO_UPDATE: "1"
+    steps:
+      - checkout
+      - run: brew install grep
+      - run:
+          name: install bazel
+          command: |
+            brew install bazel
+      - run: test/test_bazel_mac.sh
+
 workflows:
   flake8:
     jobs:
@@ -208,6 +223,9 @@
               ignore: /.*/
             tags:
               only: /.*/
-  test-bazel:
+  test-bazel-linux:
     jobs:
-      - test-bazel
+      - test-bazel-linux
+  test-bazel-mac:
+    jobs:
+      - test-bazel-mac
diff --git a/bazel/BUILD b/bazel/BUILD
new file mode 100644
index 0000000..9aa46d9
--- /dev/null
+++ b/bazel/BUILD
@@ -0,0 +1,44 @@
+package(default_visibility = ['//visibility:public'])
+
+config_setting(
+    name = "linux",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+config_setting(
+    name = "macos",
+    constraint_values = [
+        "@platforms//os:macos",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+config_setting(
+    name = "windows",
+    constraint_values = [
+        "@platforms//os:windows",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+alias(
+    name = "binaries",
+    actual = select({
+        ":linux": "@emscripten_bin_linux//:all",
+        ":macos": "@emscripten_bin_mac//:all",
+        ":windows": "@emscripten_bin_win//:all",
+    }),
+)
+
+alias(
+    name = "node_modules",
+    actual = select({
+        ":linux": "@emscripten_npm_linux//:node_modules",
+        ":macos": "@emscripten_npm_mac//:node_modules",
+        ":windows": "@emscripten_npm_win//:node_modules",
+    }),
+)
+
diff --git a/bazel/WORKSPACE b/bazel/WORKSPACE
index d2e8629..22311ff 100644
--- a/bazel/WORKSPACE
+++ b/bazel/WORKSPACE
@@ -1,25 +1,7 @@
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+workspace(name = "emsdk")
 
-http_archive(
-    name = "build_bazel_rules_nodejs",
-    sha256 = "0f2de53628e848c1691e5729b515022f5a77369c76a09fbe55611e12731c90e3",
-    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.0.1/rules_nodejs-2.0.1.tar.gz"],
-)
+load(":deps.bzl", "deps")
+deps()
 
-load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
-
-# emscripten 2.0.15
-http_archive(
-    name = "emscripten",
-    sha256 = "7ff49fc63adf29970f6e7af1df445d7f554bdbbb2606db1cb5d3567ce69df1db",
-    strip_prefix = "install",
-    url = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/89202930a98fe7f9ed59b574469a9471b0bda7dd/wasm-binaries.tbz2",
-    build_file = "//emscripten_toolchain:emscripten.BUILD",
-    type = "tar.bz2",
-)
-
-npm_install(
-    name = "npm",
-    package_json = "@emscripten//:emscripten/package.json",
-    package_lock_json = "@emscripten//:emscripten/package-lock.json",
-)
+load(":emscripten_deps.bzl", "emscripten_deps")
+emscripten_deps()
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
new file mode 100644
index 0000000..0b37e1f
--- /dev/null
+++ b/bazel/deps.bzl
@@ -0,0 +1,11 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+def deps():
+    excludes = native.existing_rules().keys()
+
+    if "build_bazel_rules_nodejs" not in excludes:
+        http_archive(
+            name = "build_bazel_rules_nodejs",
+            sha256 = "0f2de53628e848c1691e5729b515022f5a77369c76a09fbe55611e12731c90e3",
+            urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.0.1/rules_nodejs-2.0.1.tar.gz"],
+        )
diff --git a/bazel/emscripten_deps.bzl b/bazel/emscripten_deps.bzl
new file mode 100644
index 0000000..e6a30be
--- /dev/null
+++ b/bazel/emscripten_deps.bzl
@@ -0,0 +1,76 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@build_bazel_rules_nodejs//:index.bzl", "npm_install")
+load(":revisions.bzl", "EMSCRIPTEN_TAGS")
+
+def _parse_version(v):
+  return [int(u) for u in v.split(".")]
+
+def emscripten_deps(emscripten_version = "latest"):
+    version = emscripten_version
+
+    if version == "latest":
+        version = reversed(sorted(EMSCRIPTEN_TAGS.keys(), key=_parse_version))[0]
+
+    if version not in EMSCRIPTEN_TAGS.keys():
+        error_msg = "Emscripten version {} not found.".format(version)
+        error_msg += " Look at @emsdk//:revisions.bzl for the list "
+        error_msg += "of currently supported versions."
+        fail(error_msg)
+
+    revision = EMSCRIPTEN_TAGS[version]
+
+    emscripten_url = "https://storage.googleapis.com/webassembly/emscripten-releases-builds/{}/{}/wasm-binaries.tbz2"
+
+    # This could potentially backfire for projects with multiple emscripten
+    # dependencies that use different emscripten versions
+    excludes = native.existing_rules().keys()
+    if "emscripten_bin_linux" not in excludes:
+        http_archive(
+            name = "emscripten_bin_linux",
+            strip_prefix = "install",
+            url = emscripten_url.format("linux", revision.hash),
+            sha256 = revision.sha_linux,
+            build_file = "@emsdk//emscripten_toolchain:emscripten.BUILD",
+            type = "tar.bz2",
+        )
+
+    if "emscripten_bin_mac" not in excludes:
+        http_archive(
+            name = "emscripten_bin_mac",
+            strip_prefix = "install",
+            url = emscripten_url.format("mac", revision.hash),
+            sha256 = revision.sha_mac,
+            build_file = "@emsdk//emscripten_toolchain:emscripten.BUILD",
+            type = "tar.bz2",
+        )
+
+    if "emscripten_bin_win" not in excludes:
+        http_archive(
+            name = "emscripten_bin_win",
+            strip_prefix = "install",
+            url = emscripten_url.format("win", revision.hash),
+            sha256 = revision.sha_win,
+            build_file = "@emsdk//emscripten_toolchain:emscripten.BUILD",
+            type = "tar.bz2",
+        )
+
+    if "emscripten_npm_linux" not in excludes:
+        npm_install(
+            name = "emscripten_npm_linux",
+            package_json = "@emscripten_bin_linux//:emscripten/package.json",
+            package_lock_json = "@emscripten_bin_linux//:emscripten/package-lock.json",
+        )
+
+    if "emscripten_npm_mac" not in excludes:
+        npm_install(
+            name = "emscripten_npm_mac",
+            package_json = "@emscripten_bin_mac//:emscripten/package.json",
+            package_lock_json = "@emscripten_bin_mac//:emscripten/package-lock.json",
+        )
+
+    if "emscripten_npm_win" not in excludes:
+        npm_install(
+            name = "emscripten_npm_win",
+            package_json = "@emscripten_bin_win//:emscripten/package.json",
+            package_lock_json = "@emscripten_bin_win//:emscripten/package-lock.json",
+        )
diff --git a/bazel/emscripten_toolchain/BUILD.bazel b/bazel/emscripten_toolchain/BUILD.bazel
index 89fc320..488da5a 100644
--- a/bazel/emscripten_toolchain/BUILD.bazel
+++ b/bazel/emscripten_toolchain/BUILD.bazel
@@ -9,9 +9,9 @@
         "emcc.sh",
         "emscripten_config",
         "env.sh",
-        "@emscripten//:all",
         "@nodejs//:node_files",
-        "@npm//:node_modules",
+        "@emsdk//:binaries",
+        "@emsdk//:node_modules",
     ],
 )
 
@@ -26,7 +26,7 @@
         "emcc_link.sh",
         "link_wrapper.py",
         ":common-script-includes",
-        "@emscripten//:all",
+        "@emsdk//:binaries",
         "@nodejs//:node_files",
     ],
 )
@@ -36,7 +36,7 @@
     srcs = [
         ":compile-emscripten",
         ":link-emscripten",
-        "@emscripten//:all",
+        "@emsdk//:binaries",
         "@nodejs//:node_files",
     ],
 )
@@ -49,7 +49,8 @@
 emscripten_cc_toolchain_config_rule(
     name = "wasm",
     cpu = "wasm",
-    emscripten_version = "emscripten",
+    em_config = "emscripten_config",
+    emscripten_binaries = "@emsdk//:binaries",
 )
 
 cc_toolchain(
diff --git a/bazel/emscripten_toolchain/crosstool.bzl b/bazel/emscripten_toolchain/crosstool.bzl
index 10c15cb..0672ef7 100644
--- a/bazel/emscripten_toolchain/crosstool.bzl
+++ b/bazel/emscripten_toolchain/crosstool.bzl
@@ -69,9 +69,10 @@
     abi_libc_version = "default"
 
     cc_target_os = "emscripten"
-    emscripten_version = ctx.attr.emscripten_version
 
-    builtin_sysroot = "external/emscripten/emscripten/cache/sysroot"
+    emscripten_dir = ctx.attr.emscripten_binaries.label.workspace_root
+
+    builtin_sysroot = emscripten_dir + "/emscripten/cache/sysroot"
 
     ################################################################
     # Tools
@@ -909,7 +910,7 @@
                 "-iwithsysroot" + "/include/c++/v1",
                 "-iwithsysroot" + "/include/compat",
                 "-iwithsysroot" + "/include",
-                "-isystem", "external/emscripten/lib/clang/13.0.0/include",
+                "-isystem", emscripten_dir + "/lib/clang/13.0.0/include",
             ],
         ),
         # Inputs and outputs
@@ -1016,6 +1017,22 @@
     ]
 
     crosstool_default_env_sets = [
+        # Globals
+        env_set(
+            actions = all_compile_actions +
+                      all_link_actions +
+                      [ACTION_NAMES.cpp_link_static_library],
+            env_entries = [
+                env_entry(
+                    key = "EM_BIN_PATH",
+                    value = emscripten_dir,
+                ),
+                env_entry(
+                    key = "EM_CONFIG_PATH",
+                    value = ctx.file.em_config.path,
+                ),
+            ],
+        ),
         # Use llvm backend.  Off by default, enabled via --features=llvm_backend
         env_set(
             actions = all_compile_actions +
@@ -1052,49 +1069,42 @@
     features.append(crosstool_default_flags_feature)
 
     cxx_builtin_include_directories = [
-        "external/emscripten/emscripten/cache/sysroot/include/c++/v1",
-        "external/emscripten/emscripten/cache/sysroot/include/compat",
-        "external/emscripten/emscripten/cache/sysroot/include",
-        "external/emscripten/lib/clang/13.0.0/include",
+        emscripten_dir + "/emscripten/cache/sysroot/include/c++/v1",
+        emscripten_dir + "/emscripten/cache/sysroot/include/compat",
+        emscripten_dir + "/emscripten/cache/sysroot/include",
+        emscripten_dir + "/lib/clang/13.0.0/include",
     ]
 
     artifact_name_patterns = []
 
     make_variables = []
 
-    out = ctx.actions.declare_file(ctx.label.name)
-    ctx.actions.write(out, "Fake executable")
-    return [
-        cc_common.create_cc_toolchain_config_info(
-            ctx = ctx,
-            features = features,
-            action_configs = action_configs,
-            artifact_name_patterns = artifact_name_patterns,
-            cxx_builtin_include_directories = cxx_builtin_include_directories,
-            toolchain_identifier = toolchain_identifier,
-            host_system_name = host_system_name,
-            target_system_name = target_system_name,
-            target_cpu = target_cpu,
-            target_libc = target_libc,
-            compiler = compiler,
-            abi_version = abi_version,
-            abi_libc_version = abi_libc_version,
-            tool_paths = tool_paths,
-            make_variables = make_variables,
-            builtin_sysroot = builtin_sysroot,
-            cc_target_os = cc_target_os,
-        ),
-        DefaultInfo(
-            executable = out,
-        ),
-    ]
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = toolchain_identifier,
+        host_system_name = host_system_name,
+        target_system_name = target_system_name,
+        target_cpu = target_cpu,
+        target_libc = target_libc,
+        compiler = compiler,
+        abi_version = abi_version,
+        abi_libc_version = abi_libc_version,
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = builtin_sysroot,
+        cc_target_os = cc_target_os,
+    )
 
 emscripten_cc_toolchain_config_rule = rule(
     implementation = _impl,
     attrs = {
         "cpu": attr.string(mandatory = True, values = ["asmjs", "wasm"]),
-        "emscripten_version": attr.string(mandatory = True),
+        "em_config": attr.label(mandatory = True, allow_single_file=True),
+        "emscripten_binaries": attr.label(mandatory = True),
     },
     provides = [CcToolchainConfigInfo],
-    executable = True,
 )
diff --git a/bazel/emscripten_toolchain/emar.sh b/bazel/emscripten_toolchain/emar.sh
index 965442e..e4279f1 100755
--- a/bazel/emscripten_toolchain/emar.sh
+++ b/bazel/emscripten_toolchain/emar.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
 
-source emscripten_toolchain/env.sh
+source external/emsdk/emscripten_toolchain/env.sh
 
 exec python3 $EMSCRIPTEN/emar.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc.sh b/bazel/emscripten_toolchain/emcc.sh
index 66d00b6..7f3699b 100755
--- a/bazel/emscripten_toolchain/emcc.sh
+++ b/bazel/emscripten_toolchain/emcc.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
 
-source emscripten_toolchain/env.sh
+source external/emsdk/emscripten_toolchain/env.sh
 
-exec python3 external/emscripten/emscripten/emcc.py "$@"
+exec python3 $EMSCRIPTEN/emcc.py "$@"
diff --git a/bazel/emscripten_toolchain/emcc_link.sh b/bazel/emscripten_toolchain/emcc_link.sh
index 9d0f8e2..24d806d 100755
--- a/bazel/emscripten_toolchain/emcc_link.sh
+++ b/bazel/emscripten_toolchain/emcc_link.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
 
-source emscripten_toolchain/env.sh
+source external/emsdk/emscripten_toolchain/env.sh
 
-exec python3 emscripten_toolchain/link_wrapper.py "$@"
+exec python3 external/emsdk/emscripten_toolchain/link_wrapper.py "$@"
diff --git a/bazel/emscripten_toolchain/emscripten_config b/bazel/emscripten_toolchain/emscripten_config
index b7948ac..d1275ff 100644
--- a/bazel/emscripten_toolchain/emscripten_config
+++ b/bazel/emscripten_toolchain/emscripten_config
@@ -3,8 +3,8 @@
 
 ROOT_DIR = os.environ["ROOT_DIR"]
 EMSCRIPTEN_ROOT = os.environ["EMSCRIPTEN"]
-LLVM_ROOT = ROOT_DIR + "/external/emscripten/bin"
-BINARYEN_ROOT = ROOT_DIR + "/external/emscripten"
+BINARYEN_ROOT = ROOT_DIR + "/" + os.environ["EM_BIN_PATH"]
+LLVM_ROOT = BINARYEN_ROOT + "/bin"
 FROZEN_CACHE = True
 
 system = platform.system()
diff --git a/bazel/emscripten_toolchain/env.sh b/bazel/emscripten_toolchain/env.sh
index dfb4ddc..d66a3a1 100755
--- a/bazel/emscripten_toolchain/env.sh
+++ b/bazel/emscripten_toolchain/env.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
 
 export ROOT_DIR=`(pwd -P)`
-export EMSCRIPTEN=${ROOT_DIR}/external/emscripten/emscripten
-export EM_CONFIG=${ROOT_DIR}/emscripten_toolchain/emscripten_config
+export EMSCRIPTEN=$ROOT_DIR/$EM_BIN_PATH/emscripten
+export EM_CONFIG=$ROOT_DIR/$EM_CONFIG_PATH
diff --git a/bazel/emscripten_toolchain/wasm_cc_binary.bzl b/bazel/emscripten_toolchain/wasm_cc_binary.bzl
index 9128b7a..9d68041 100644
--- a/bazel/emscripten_toolchain/wasm_cc_binary.bzl
+++ b/bazel/emscripten_toolchain/wasm_cc_binary.bzl
@@ -27,13 +27,13 @@
 
     return {
         "//command_line_option:compiler": "emscripten",
-        "//command_line_option:crosstool_top": "//emscripten_toolchain:everything",
+        "//command_line_option:crosstool_top": "@emsdk//emscripten_toolchain:everything",
         "//command_line_option:cpu": "wasm",
         "//command_line_option:features": features,
         "//command_line_option:dynamic_mode": "off",
         "//command_line_option:linkopt": linkopts,
         "//command_line_option:platforms": [],
-        "//command_line_option:custom_malloc": "//emscripten_toolchain:malloc",
+        "//command_line_option:custom_malloc": "@emsdk//emscripten_toolchain:malloc",
     }
 
 _wasm_transition = transition(
@@ -145,7 +145,7 @@
             executable = True,
             allow_files = True,
             cfg = "exec",
-            default = Label("//emscripten_toolchain:wasm_binary"),
+            default = Label("@emsdk//emscripten_toolchain:wasm_binary"),
         ),
     },
     outputs = _wasm_binary_outputs,
diff --git a/bazel/emscripten_toolchain/wasm_rules.bzl b/bazel/emscripten_toolchain/wasm_rules.bzl
index 1c1c409..f8dce22 100644
--- a/bazel/emscripten_toolchain/wasm_rules.bzl
+++ b/bazel/emscripten_toolchain/wasm_rules.bzl
@@ -1,6 +1,6 @@
 """Rules related to C++ and WebAssembly.
 """
 
-load("//emscripten_toolchain:wasm_cc_binary.bzl", _wasm_cc_binary = "wasm_cc_binary")
+load(":wasm_cc_binary.bzl", _wasm_cc_binary = "wasm_cc_binary")
 
 wasm_cc_binary = _wasm_cc_binary
diff --git a/bazel/revisions.bzl b/bazel/revisions.bzl
new file mode 100644
index 0000000..7688dd5
--- /dev/null
+++ b/bazel/revisions.bzl
@@ -0,0 +1,23 @@
+# This file is automatically updated by emsdk/scripts/update_bazel_workspace.sh
+# DO NOT MODIFY
+
+EMSCRIPTEN_TAGS = {
+   "2.0.15": struct(
+        hash = "89202930a98fe7f9ed59b574469a9471b0bda7dd",
+        sha_linux = "7ff49fc63adf29970f6e7af1df445d7f554bdbbb2606db1cb5d3567ce69df1db",
+        sha_mac = "e35cced1514ad0da40584f8dd6f76aabf847ce0fa82c6dc8dd9442fb74ed6d0d",
+        sha_win = "31d5f8107c87833cea57edc57613bba4b36b16152772f744c5ad204594b4e666",
+    ),
+   "2.0.14": struct(
+        hash = "fc5562126762ab26c4757147a3b4c24e85a7289e",
+        sha_linux = "e466cd47ddd4bf0acd645412fdf08eda6d232484e48e5a2643e08062a7a4cf56",
+        sha_mac = "1c554c08459b7025638ca4eddba0d35babe8c26b202a70a74e9442d577896211",
+        sha_win = "428bc6094671937af96f26d803871fc5cd83d4d2b1c1df45fa6873a9bc5cac51",
+    ),
+   "2.0.13": struct(
+        hash = "ce0e4a4d1cab395ee5082a60ebb4f3891a94b256",
+        sha_linux = "8986ed886e111c661099c5147126b8a379a4040aab6a1f572fe01f0f9b99a343",
+        sha_mac = "88c91332c8c76fed14ebf0edc9a08f586012f54f04ad61e5b1b6d02bf96bdeab",
+        sha_win = "9fb3b945b7bd56e34d17ec04de4cce475f26c49d161aee9d9c0b8b1434591f88",
+    ),
+}
diff --git a/bazel/test_external/BUILD b/bazel/test_external/BUILD
new file mode 100644
index 0000000..73568cf
--- /dev/null
+++ b/bazel/test_external/BUILD
@@ -0,0 +1,12 @@
+load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
+
+cc_binary(
+    name = "hello-world",
+    srcs = ["hello-world.cc"],
+)
+
+wasm_cc_binary(
+    name = "hello-world-wasm",
+    cc_target = ":hello-world",
+)
+
diff --git a/bazel/test_external/WORKSPACE b/bazel/test_external/WORKSPACE
new file mode 100644
index 0000000..f0a446c
--- /dev/null
+++ b/bazel/test_external/WORKSPACE
@@ -0,0 +1,10 @@
+local_repository(
+    name = "emsdk",
+    path = "..",
+)
+
+load("@emsdk//:deps.bzl", "deps")
+deps()
+
+load("@emsdk//:emscripten_deps.bzl", "emscripten_deps")
+emscripten_deps()
diff --git a/bazel/test_external/hello-world.cc b/bazel/test_external/hello-world.cc
new file mode 100644
index 0000000..ee72c53
--- /dev/null
+++ b/bazel/test_external/hello-world.cc
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main(int argc, char** argv) {
+  std::cout << "hello world!" << std::endl;
+  return 0;
+}
diff --git a/scripts/update_bazel_workspace.sh b/scripts/update_bazel_workspace.sh
index fb58f04..b9b7bd2 100755
--- a/scripts/update_bazel_workspace.sh
+++ b/scripts/update_bazel_workspace.sh
@@ -33,31 +33,40 @@
   exit 1
 fi
 
-URL1=https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/
+URL1=https://storage.googleapis.com/webassembly/emscripten-releases-builds/
 URL2=/wasm-binaries.tbz2
 
+# Get commit hash for $1 version
+get_hash () {
+  echo $(grep "$1" emscripten-releases-tags.txt | grep -v latest | cut -f4 -d\")
+}
+
+# Get sha256 for $1 os $2 hash
+get_sha () {
+  echo $(curl "${URL1}$1/$2${URL2}" 2>/dev/null | sha256sum | awk '{print $1}')
+}
+
+# Assemble dictionary line
+revisions_item () {
+  hash=$(get_hash $1)
+  echo \
+      "\   \"$1\": struct(\n" \
+      "\       hash = \"$(get_hash ${hash})\",\n" \
+      "\       sha_linux = \"$(get_sha linux ${hash})\",\n" \
+      "\       sha_mac = \"$(get_sha mac ${hash})\",\n" \
+      "\       sha_win = \"$(get_sha win ${hash})\",\n" \
+      "\   ),"
+}
+
+append_revision () {
+  sed -i "5 i $(revisions_item $1)" bazel/revisions.bzl
+}
+
 # Get the latest version number from emscripten-releases-tag.txt.
 VER=$(grep -oP '(?<=latest\": \")([\d\.]+)(?=\")' \
         emscripten-releases-tags.txt \
       | sed --expression "s/\./\\\./g")
-# Based on the latest version number, get the commit hash for that version.
-HASH=$(grep "${VER}" emscripten-releases-tags.txt \
-      | grep -v latest \
-      | cut -f4 -d\")
-# Download and compute the sha256sum for the archive with the prebuilts.
-SHA=$(curl "${URL1}${HASH}${URL2}" 2>/dev/null \
-      | sha256sum \
-      | awk '{print $1}')
-# Get the line number on which the sha256 sum lives for emscripten.
-# This will always be one line after the name of the rule.
-SHALINE=$(($(grep -n 'name = "emscripten"' bazel/WORKSPACE \
-      | sed 's/^\([[:digit:]]*\).*$/\1/')+1))
 
-# Insert the new commit hash into the url.
-sed -i "s!\(${URL1}\)\([[:alnum:]]*\)\(${URL2}\)!\1${HASH}\3!" bazel/WORKSPACE
-# Insert the new version number.
-sed -i "s!\(# emscripten \)\(.*\)!\1${VER}!" bazel/WORKSPACE
-# Insert the new sha256 sum.
-sed -i "${SHALINE}s!\"[[:alnum:]]*\"!\"${SHA}\"!" bazel/WORKSPACE
+append_revision ${VER}
 
 echo "Done!"
diff --git a/test/test_bazel.sh b/test/test_bazel.sh
index 6a2c98d..6ae69f9 100755
--- a/test/test_bazel.sh
+++ b/test/test_bazel.sh
@@ -17,9 +17,12 @@
 FAILMSG="!!! scripts/update_bazel_toolchain.sh needs to be run !!!"
 
 # Ensure the WORKSPACE file is up to date with the latest version.
-grep ${VER} bazel/WORKSPACE || (echo ${FAILMSG} && false)
-grep ${HASH} bazel/WORKSPACE || (echo ${FAILMSG} && false)
+grep ${VER} bazel/revisions.bzl || (echo ${FAILMSG} && false)
+grep ${HASH} bazel/revisions.bzl || (echo ${FAILMSG} && false)
 
 cd bazel
 bazel build //hello-world:hello-world-wasm
 bazel build //hello-world:hello-world-wasm-simd
+
+cd test_external
+bazel build //:hello-world-wasm
diff --git a/test/test_bazel_mac.sh b/test/test_bazel_mac.sh
new file mode 100755
index 0000000..851ec5f
--- /dev/null
+++ b/test/test_bazel_mac.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+echo "test bazel"
+
+set -x
+set -e
+
+# Get the latest version number from emscripten-releases-tag.txt.
+VER=$(ggrep -oP '(?<=latest\": \")([\d\.]+)(?=\")' \
+        emscripten-releases-tags.txt \
+      | sed "s/\./\\\./g")
+# Based on the latest version number, get the commit hash for that version.
+HASH=$(grep "${VER}" emscripten-releases-tags.txt \
+      | grep -v latest \
+      | cut -f4 -d\")
+
+FAILMSG="!!! scripts/update_bazel_toolchain.sh needs to be run !!!"
+
+# Ensure the WORKSPACE file is up to date with the latest version.
+grep ${VER} bazel/revisions.bzl || (echo ${FAILMSG} && false)
+grep ${HASH} bazel/revisions.bzl || (echo ${FAILMSG} && false)
+
+cd bazel
+bazel build //hello-world:hello-world-wasm
+bazel build //hello-world:hello-world-wasm-simd
+
+cd test_external
+bazel build //:hello-world-wasm