Merge "Use explicit flatbuffer IDs in y2017 and newer."
diff --git a/WORKSPACE b/WORKSPACE
index 4d2f666..bcdbf4f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -582,40 +582,6 @@
     url = "https://github.com/bazelbuild/rules_typescript/archive/0.21.0.zip",
 )
 
-emscripten_version = "1.38.31"
-
-http_archive(
-    name = "emscripten_toolchain",
-    build_file_content = """
-filegroup(
-    name = 'all',
-    visibility = ['//visibility:public'],
-    srcs = glob(['**']),
-)
-""",
-    # TODO(james): Once a functioning release contains this patch, convert
-    # to that. See https://github.com/emscripten-core/emscripten/pull/9048
-    patches = ["@//debian:emscripten_toolchain.patch"],
-    patch_args = ["-p1"],
-    sha256 = "c87e42cb6a104094e7daf2b7e61ac835f83674ac0168f533455838a1129cc764",
-    strip_prefix = "emscripten-" + emscripten_version,
-    urls = ["https://github.com/emscripten-core/emscripten/archive/" + emscripten_version + ".tar.gz"],
-)
-
-http_archive(
-    name = "emscripten_clang",
-    build_file_content = """
-filegroup(
-    name = 'all',
-    visibility = ['//visibility:public'],
-    srcs = glob(['**']),
-)
-""",
-    sha256 = "a0c2f2c5a897577f40af0fdf68dcf3cf65557ff20c081df26678c066a4fed4b1",
-    strip_prefix = "emscripten-llvm-e" + emscripten_version,
-    url = "https://www.frc971.org/Build-Dependencies/emscripten-llvm-e" + emscripten_version + ".tar.gz",
-)
-
 http_archive(
     name = "webrtc_x64",
     build_file = "@//debian:webrtc.BUILD",
diff --git a/build_tests/BUILD b/build_tests/BUILD
index e837390..11ad448 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -1,49 +1,6 @@
 load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library")
-load("//tools/cpp/emscripten:defs.bzl", "emcc_binary")
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_py_library")
 
-emcc_binary(
-    name = "helloworld.html",
-    srcs = ["helloworld.cc"],
-)
-
-emcc_binary(
-    name = "webgl.html",
-    srcs = ["webgl_draw_triangle.c"],
-)
-
-emcc_binary(
-    name = "webgl2.html",
-    srcs = ["webgl2_benchmark.cc"],
-    html_shell = "minimal_shell.html",
-    # Enable WEBGL2 (-s is used by the emscripten
-    # compiler to specify sundry options).
-    linkopts = [
-        "-s",
-        "USE_WEBGL2=1",
-        "-s",
-        "TOTAL_MEMORY=" + repr(256 * 1024 * 1024),
-    ],
-)
-
-emcc_binary(
-    name = "plotter.html",
-    srcs = ["webgl2_plot_test.cc"],
-    html_shell = "minimal_shell.html",
-    linkopts = [
-        "-s",
-        "USE_WEBGL2=1",
-        "-s",
-        "FULL_ES3=1",
-        "-s",
-        "TOTAL_MEMORY=" + repr(256 * 1024 * 1024),
-    ],
-    deps = [
-        "//frc971/analysis/plotting:webgl2_animator",
-        "//frc971/analysis/plotting:webgl2_plotter",
-    ],
-)
-
 cc_test(
     name = "gflags_build_test",
     size = "small",
diff --git a/build_tests/minimal_shell.html b/build_tests/minimal_shell.html
deleted file mode 100644
index c9d2b86..0000000
--- a/build_tests/minimal_shell.html
+++ /dev/null
@@ -1,142 +0,0 @@
-<!doctype html>
-<!--This file is adapted from https://github.com/emscripten-core/emscripten/blob/incoming/src/shell_minimal.html-->
-<html lang="en-us">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <title>Emscripten-Generated Code</title>
-    <style>
-      .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
-      div.emscripten { text-align: center; }
-      /* the canvas *must not* have any border or padding, or mouse coords will be wrong */
-      canvas.emscripten {
-        border: 0px none;
-        background-color: black;
-      }
-
-      .spinner {
-        height: 50px;
-        width: 50px;
-        margin: 0px auto;
-        -webkit-animation: rotation .8s linear infinite;
-        -moz-animation: rotation .8s linear infinite;
-        -o-animation: rotation .8s linear infinite;
-        animation: rotation 0.8s linear infinite;
-        border-left: 10px solid rgb(0,150,240);
-        border-right: 10px solid rgb(0,150,240);
-        border-bottom: 10px solid rgb(0,150,240);
-        border-top: 10px solid rgb(100,0,200);
-        border-radius: 100%;
-        background-color: rgb(200,100,250);
-      }
-      @-webkit-keyframes rotation {
-        from {-webkit-transform: rotate(0deg);}
-        to {-webkit-transform: rotate(360deg);}
-      }
-      @-moz-keyframes rotation {
-        from {-moz-transform: rotate(0deg);}
-        to {-moz-transform: rotate(360deg);}
-      }
-      @-o-keyframes rotation {
-        from {-o-transform: rotate(0deg);}
-        to {-o-transform: rotate(360deg);}
-      }
-      @keyframes rotation {
-        from {transform: rotate(0deg);}
-        to {transform: rotate(360deg);}
-      }
-
-    </style>
-  </head>
-  <body>
-    <figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
-    <div class="emscripten" id="status">Downloading...</div>
-    <div class="emscripten">
-      <progress value="0" max="100" id="progress" hidden=1></progress>
-    </div>
-    <!--The width and height values in the canvas specify the pixel width/height for the WebGL canvas.
-        The actual on-screen size is controlled by the stylesheet.-->
-    <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" width=1200 height=600></canvas>
-
-    <div>
-      General help information:<br>
-      <ul>
-        <li>Double-click to reset zoom.</li>
-        <li>Left-click to print mouse position (within plot) to console.</li>
-        <li>Ctrl-Z to undo zoom actions.</li>
-        <li>Right-click and drag to pan.</li>
-        <li>Left-click and drag zooms to the dragged area. If you press Escape while dragging, it will cancel the zoom.</li>
-        <li>Scroll up/down will zoom in and out.</li>
-        <li>Holding down "x" and "y" will restrict any
-            movement to the x and y axes respectively.</li>
-      </ul>
-    </div>
-
-    <script type='text/javascript'>
-      var statusElement = document.getElementById('status');
-      var progressElement = document.getElementById('progress');
-      var spinnerElement = document.getElementById('spinner');
-
-      var Module = {
-        preRun: [],
-        postRun: [],
-        print: (function() {
-          return function(text) {
-            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
-            console.log(text);
-          };
-        })(),
-        printErr: function(text) {
-          if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
-          console.error(text);
-        },
-        canvas: (function() {
-          var canvas = document.getElementById('canvas');
-
-          // As a default initial behavior, pop up an alert when webgl context is lost. To make your
-          // application robust, you may want to override this behavior before shipping!
-          // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
-          canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
-
-          return canvas;
-        })(),
-        setStatus: function(text) {
-          if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
-          if (text === Module.setStatus.last.text) return;
-          var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
-          var now = Date.now();
-          if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
-          Module.setStatus.last.time = now;
-          Module.setStatus.last.text = text;
-          if (m) {
-            text = m[1];
-            progressElement.value = parseInt(m[2])*100;
-            progressElement.max = parseInt(m[4])*100;
-            progressElement.hidden = false;
-            spinnerElement.hidden = false;
-          } else {
-            progressElement.value = null;
-            progressElement.max = null;
-            progressElement.hidden = true;
-            if (!text) spinnerElement.hidden = true;
-          }
-          statusElement.innerHTML = text;
-        },
-        totalDependencies: 0,
-        monitorRunDependencies: function(left) {
-          this.totalDependencies = Math.max(this.totalDependencies, left);
-          Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
-        }
-      };
-      Module.setStatus('Downloading...');
-      window.onerror = function() {
-        Module.setStatus('Exception thrown, see JavaScript console');
-        spinnerElement.style.display = 'none';
-        Module.setStatus = function(text) {
-          if (text) Module.printErr('[post-exception status] ' + text);
-        };
-      };
-    </script>
-    {{{ SCRIPT }}}
-  </body>
-</html>
diff --git a/build_tests/webgl2_benchmark.cc b/build_tests/webgl2_benchmark.cc
deleted file mode 100644
index f9f15fc..0000000
--- a/build_tests/webgl2_benchmark.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <cmath>
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-#include <iostream>
-#include <unistd.h>
-#include <GLES3/gl3.h>
-
-namespace {
-constexpr int kNPoints = 10 * 1000 * 1000;
-}  // namespace
-
-// Shader and program construction taken from examples at
-// https://github.com/emscripten-core/emscripten/blob/incoming/tests/webgl2_draw_packed_triangle.c
-GLuint compile_shader(GLenum shaderType, const char *src) {
-  GLuint shader = glCreateShader(shaderType);
-  glShaderSource(shader, 1, &src, NULL);
-  glCompileShader(shader);
-
-  GLint isCompiled = 0;
-  glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
-  if (!isCompiled) {
-    GLint maxLength = 0;
-    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
-    char *buf = (char *)malloc(maxLength + 1);
-    glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
-    printf("%s\n", buf);
-    free(buf);
-    return 0;
-  }
-
-  return shader;
-}
-
-GLuint create_program(GLuint vertexShader, GLuint fragmentShader) {
-   GLuint program = glCreateProgram();
-   glAttachShader(program, vertexShader);
-   glAttachShader(program, fragmentShader);
-   glBindAttribLocation(program, 0, "apos");
-   glBindAttribLocation(program, 1, "acolor");
-   glLinkProgram(program);
-   return program;
-}
-
-struct Vector {
-  int x;
-  int y;
-  int z;
-  int w;
-};
-
-// Packs a vector for use with GL_INT_2_10_10_10_REV.
-uint32_t PackVector(const Vector &vec) {
-  uint32_t retval = 0;
-  retval = vec.w;
-  retval <<= 10;
-  retval |= vec.z & 0x3FF;
-  retval <<= 10;
-  retval |= vec.y & 0x3FF;
-  retval <<= 10;
-  retval |= vec.x & 0x3FF;
-  return retval;
-}
-
-struct AnimationState {
-  // The time, in seconds, at which the last animation frame occurred.
-  double last_animation_time = 0.0;
-  // The location for the "scale" uniform to modify on each animation call.
-  GLint scale_uniform_location;
-};
-
-// This function modifies the "scale" uniform to vary from 0.5->1.0->0.5
-// in a cycle, and redraws the points on each iteration.
-int Redraw(double time, void *data) {
-  AnimationState *state = reinterpret_cast<AnimationState*>(data);
-  time /= 1000.0;
-  const double difftime = time - state->last_animation_time;
-  const double wrap_time = std::fmod(time, 1.0);
-  const double offset = wrap_time > 0.5 ? 1.0 - wrap_time : wrap_time;
-  glUniform1f(state->scale_uniform_location, std::min(0.5 + offset, 100.0));
-  std::cout << 1.0 / difftime  << "fps\n";
-  glDrawArrays(GL_POINTS, 0, kNPoints);
-  state->last_animation_time = time;
-  assert(glGetError() == GL_NO_ERROR && "glDrawArray failed");
-  return 1;
-}
-
-int main() {
-  EmscriptenWebGLContextAttributes attr;
-  emscripten_webgl_init_context_attributes(&attr);
-  attr.majorVersion = 2;
-  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
-  assert(ctx && "Failed to create WebGL2 context");
-  emscripten_webgl_make_context_current(ctx);
-
-  static const char vertex_shader[] =
-    "#version 100\n"
-    "attribute vec4 apos;"
-    "attribute vec4 acolor;"
-    "varying vec4 color;"
-    "uniform float scale;"
-    "void main() {"
-      "color = acolor;"
-      "gl_Position = apos;"
-      "gl_Position.x = apos.x * scale;"
-      "gl_Position.y = apos.y * 0.5 / scale;"
-      "gl_PointSize = 1.0;"
-    "}";
-  GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
-
-  static const char fragment_shader[] =
-    "#version 100\n"
-    "precision lowp float;"
-    "varying vec4 color;"
-    "void main() {"
-      "gl_FragColor = color;"
-    "}";
-  GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
-
-  GLuint program = create_program(vs, fs);
-  glUseProgram(program);
-
-  // Go through and generate randomly located and colored points.
-  static uint32_t pos_and_color[kNPoints * 2];
-  for (int ii = 0; ii < kNPoints; ++ii) {
-    uint16_t w = 1;
-    uint16_t z = 0;
-    uint16_t y = rand() % 1024;
-    uint16_t x = rand() % 1024;
-    uint32_t wzyx = PackVector({x, y, z, w});
-    uint16_t a = 3;
-    uint16_t b = rand() % 1024;
-    uint16_t g = rand() % 1024;
-    uint16_t r = rand() % 1024;
-    uint32_t argb = PackVector({r, g, b, a});
-    pos_and_color[2 * ii] = wzyx;
-    pos_and_color[2 * ii + 1] = argb;
-  }
-
-  GLuint vbo;
-  glGenBuffers(1, &vbo);
-  glBindBuffer(GL_ARRAY_BUFFER, vbo);
-  glBufferData(GL_ARRAY_BUFFER, sizeof(pos_and_color), pos_and_color, GL_STATIC_DRAW);
-  glVertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 8, 0);
-  assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_INT_2_10_10_10_REV failed");
-  glVertexAttribPointer(1, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 8, (void*)4);
-  assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_UNSIGNED_INT_2_10_10_10_REV failed");
-
-  glEnableVertexAttribArray(0);
-  glEnableVertexAttribArray(1);
-
-  // Note that the animation_state must last until Redraw stops being called,
-  // which we cannot provide any bound on. As such, we don't currently destroy
-  // the memory until the webpage is closed.
-  AnimationState *animation_state = new AnimationState();
-  animation_state->scale_uniform_location =
-      glGetUniformLocation(program, "scale");
-  emscripten_request_animation_frame_loop(&Redraw, animation_state);
-}
diff --git a/build_tests/webgl2_plot_test.cc b/build_tests/webgl2_plot_test.cc
deleted file mode 100644
index 2baefa9..0000000
--- a/build_tests/webgl2_plot_test.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-
-#include <iostream>
-
-#include "frc971/analysis/plotting/webgl2_plotter.h"
-#include "frc971/analysis/plotting/webgl2_animator.h"
-
-float rand1() {
-  return static_cast<float>(rand()) / RAND_MAX;
-}
-
-int main() {
-  // Note that the animation_state must last until Redraw stops being called,
-  // which we cannot provide any bound on. As such, we don't currently destroy
-  // the memory until the webpage is closed.
-  frc971::plotting::Animator *animation_state =
-      new frc971::plotting::Animator("#canvas");
-  // Generate a bunch of lines with random y-values and evenly spaced x-values,
-  // such that each line takes up a set amount of space in the y-space. If
-  // that's unclear, then try running this and seeing what it looks like.
-  constexpr size_t kNLines = 30;
-  for (int jj = 0; jj < kNLines; ++jj) {
-    frc971::plotting::Line *line = animation_state->plotter()->AddLine();
-    // Randomly generate a color to use; each of r/g/b are between 0 and 1.
-    line->SetColor({.r = rand1(), .g = rand1(), .b = rand1()});
-    std::vector<Eigen::Vector2d> points;
-    constexpr size_t kNPoints = 100000;
-    for (int ii = 0; ii < kNPoints; ++ii) {
-      const float x = static_cast<float>(ii) / kNPoints;
-      points.emplace_back(x, std::sin(x + jj));
-    }
-    line->SetPoints(points);
-  }
-}
diff --git a/build_tests/webgl_draw_triangle.c b/build_tests/webgl_draw_triangle.c
deleted file mode 100644
index be88173..0000000
--- a/build_tests/webgl_draw_triangle.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2018 The Emscripten Authors.  All rights reserved.
- * Emscripten is available under two separate licenses, the MIT license and the
- * University of Illinois/NCSA Open Source License.  Both these licenses can be
- * found in the LICENSE file.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-#include <GLES2/gl2.h>
-
-GLuint compile_shader(GLenum shaderType, const char *src)
-{
-  GLuint shader = glCreateShader(shaderType);
-  glShaderSource(shader, 1, &src, NULL);
-  glCompileShader(shader);
-
-  GLint isCompiled = 0;
-  glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
-  if (!isCompiled)
-  {
-    GLint maxLength = 0;
-    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
-    char *buf = (char*)malloc(maxLength+1);
-    glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
-    printf("%s\n", buf);
-    free(buf);
-    return 0;
-  }
-
-   return shader;
-}
-
-GLuint create_program(GLuint vertexShader, GLuint fragmentShader)
-{
-   GLuint program = glCreateProgram();
-   glAttachShader(program, vertexShader);
-   glAttachShader(program, fragmentShader);
-   glBindAttribLocation(program, 0, "apos");
-   glBindAttribLocation(program, 1, "acolor");
-   glLinkProgram(program);
-   return program;
-}
-
-int main()
-{
-  EmscriptenWebGLContextAttributes attr;
-  emscripten_webgl_init_context_attributes(&attr);
-#ifdef EXPLICIT_SWAP
-  attr.explicitSwapControl = 1;
-#endif
-#ifdef DRAW_FROM_CLIENT_MEMORY
-  // This test verifies that drawing from client-side memory when enableExtensionsByDefault==false works.
-  attr.enableExtensionsByDefault = 0;
-#endif
-
-  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
-  emscripten_webgl_make_context_current(ctx);
-
-  static const char vertex_shader[] =
-    "attribute vec4 apos;"
-    "attribute vec4 acolor;"
-    "varying vec4 color;"
-    "void main() {"
-      "color = acolor;"
-      "gl_Position = apos;"
-    "}";
-  GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
-
-  static const char fragment_shader[] =
-    "precision lowp float;"
-    "varying vec4 color;"
-    "void main() {"
-      "gl_FragColor = color;"
-    "}";
-  GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
-
-  GLuint program = create_program(vs, fs);
-  glUseProgram(program);
-
-  static const float pos_and_color[] = {
-  //     x,     y, r, g, b
-     -0.6f, -0.6f, 1, 0, 0,
-      0.6f, -0.6f, 0, 1, 0,
-      0.f,   0.6f, 0, 0, 1,
-  };
-
-#ifdef DRAW_FROM_CLIENT_MEMORY
-  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 20, pos_and_color);
-  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 20, (void*)(pos_and_color+2));
-#else
-  GLuint vbo;
-  glGenBuffers(1, &vbo);
-  glBindBuffer(GL_ARRAY_BUFFER, vbo);
-  glBufferData(GL_ARRAY_BUFFER, sizeof(pos_and_color), pos_and_color, GL_STATIC_DRAW);
-  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 20, 0);
-  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 20, (void*)8);
-#endif
-  glEnableVertexAttribArray(0);
-  glEnableVertexAttribArray(1);
-
-  glClearColor(0.3f,0.3f,0.3f,1);
-  glClear(GL_COLOR_BUFFER_BIT);
-  glDrawArrays(GL_TRIANGLES, 0, 3);
-
-#ifdef EXPLICIT_SWAP
-  emscripten_webgl_commit_frame();
-#endif
-
-#ifdef REPORT_RESULT
-  REPORT_RESULT(0);
-#endif
-}
diff --git a/debian/emscripten_toolchain.patch b/debian/emscripten_toolchain.patch
deleted file mode 100644
index f6c6b51..0000000
--- a/debian/emscripten_toolchain.patch
+++ /dev/null
@@ -1,103 +0,0 @@
-diff --git a/emcc.py b/emcc.py
-index bdf788ef2..7eba3e011 100755
---- a/emcc.py
-+++ b/emcc.py
-@@ -206,6 +206,9 @@ class EmccOptions(object):
-     # Defaults to using the native EOL on each platform (\r\n on Windows, \n on
-     # Linux & MacOS)
-     self.output_eol = os.linesep
-+    # Whether we will expand the full path of any input files to remove any
-+    # symlinks.
-+    self.expand_symlinks = True
- 
- 
- def use_source_map(options):
-@@ -859,7 +862,9 @@ There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR P
-                     '-current_version', '-I', '-L', '-include-pch'):
-           continue # ignore this gcc-style argument
- 
--      if os.path.islink(arg) and get_file_suffix(os.path.realpath(arg)) in SOURCE_ENDINGS + BITCODE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS:
-+      if (options.expand_symlinks
-+          and os.path.islink(arg)
-+          and get_file_suffix(os.path.realpath(arg)) in SOURCE_ENDINGS + BITCODE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS):
-         arg = os.path.realpath(arg)
- 
-       if not arg.startswith('-'):
-@@ -2516,6 +2521,8 @@ def parse_args(newargs):
-       settings_changes.append('SIMD=1')
-     elif newargs[i] == '-mno-simd128':
-       settings_changes.append('SIMD=0')
-+    elif newargs[i] == '-no-canonical-prefixes':
-+      options.expand_symlinks = False
- 
-   if should_exit:
-     sys.exit(0)
-diff --git a/tools/gen_struct_info.py b/tools/gen_struct_info.py
-index f6368ecff..7af844a2b 100755
---- a/tools/gen_struct_info.py
-+++ b/tools/gen_struct_info.py
-@@ -387,6 +387,14 @@ def inspect_code(headers, cpp_opts, structs, defines):
-   info = []
-   # Compile the program.
-   show('Compiling generated code...')
-+  cpp_opts += ['-isystem',
-+               shared.path_from_root('system', 'include'),
-+               '-isystem',
-+               shared.path_from_root('system', 'include', 'libcxx'),
-+               '-isystem',
-+               shared.path_from_root('system', 'lib', 'libc', 'musl', 'arch', 'emscripten'),
-+               '-isystem',
-+               shared.path_from_root('system', 'include', 'libc')]
-   # -Oz optimizes enough to avoid warnings on code size/num locals
-   cmd = [shared.PYTHON, shared.EMCC] + cpp_opts + ['-o', js_file[1], src_file[1], '-s', 'BOOTSTRAPPING_STRUCT_INFO=1', '-s', 'WARN_ON_UNDEFINED_SYMBOLS=0', '-O0', '--js-opts', '0', '--memory-init-file', '0', '-s', 'SINGLE_FILE=1', '-s', 'WASM=0', '-Wno-format']
-   if shared.Settings.WASM_OBJECT_FILES:
-diff --git a/tools/system_libs.py b/tools/system_libs.py
-index 61a17d2cf..4de22e706 100755
---- a/tools/system_libs.py
-+++ b/tools/system_libs.py
-@@ -91,7 +91,17 @@ def calculate(temp_files, in_temp, stdout_, stderr_, forced=[]):
- 
-       return shared.Building.parse_symbols(content).defs
- 
--  default_opts = ['-Werror']
-+  default_opts = ['-Werror',
-+                  '-isystem',
-+                  shared.path_from_root('system', 'include'),
-+                  '-isystem',
-+                  shared.path_from_root('system', 'include', 'libcxx'),
-+                  '-isystem',
-+                  shared.path_from_root('system', 'lib', 'libc', 'musl', 'arch', 'emscripten'),
-+                  '-isystem',
-+                  shared.path_from_root('system', 'include', 'compat'),
-+                  '-isystem',
-+                  shared.path_from_root('system', 'include', 'libc')]
- 
-   # XXX We also need to add libc symbols that use malloc, for example strdup. It's very rare to use just them and not
-   #     a normal malloc symbol (like free, after calling strdup), so we haven't hit this yet, but it is possible.
-@@ -425,7 +435,7 @@ def calculate(temp_files, in_temp, stdout_, stderr_, forced=[]):
-   # al
-   def create_al(libname): # libname is ignored, this is just one .o file
-     o = in_temp('al.o')
--    check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'al.c'), '-o', o, '-Os'] + get_cflags())
-+    check_call([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', 'al.c'), '-o', o, '-Os'] + get_cflags() + default_opts)
-     return o
- 
-   def create_html5(libname):
-@@ -444,7 +454,7 @@ def calculate(temp_files, in_temp, stdout_, stderr_, forced=[]):
-     commands = []
-     for src in files:
-       o = in_temp(os.path.basename(src) + '.o')
--      commands.append([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', src), '-O2', '-o', o] + get_cflags())
-+      commands.append([shared.PYTHON, shared.EMCC, shared.path_from_root('system', 'lib', src), '-O2', '-o', o] + get_cflags() + default_opts)
-       o_s.append(o)
-     run_commands(commands)
-     shared.Building.emar('cr', in_temp(libname), o_s)
-@@ -497,7 +507,7 @@ def calculate(temp_files, in_temp, stdout_, stderr_, forced=[]):
- 
-   def create_malloc(out_name):
-     o = in_temp(out_name)
--    cflags = ['-O2', '-fno-builtin']
-+    cflags = default_opts + ['-O2', '-fno-builtin']
-     if shared.Settings.USE_PTHREADS:
-       cflags += ['-s', 'USE_PTHREADS=1']
-     if shared.Settings.EMSCRIPTEN_TRACING:
diff --git a/frc971/analysis/plotting/BUILD b/frc971/analysis/plotting/BUILD
deleted file mode 100644
index eb817ba..0000000
--- a/frc971/analysis/plotting/BUILD
+++ /dev/null
@@ -1,30 +0,0 @@
-cc_library(
-    name = "webgl2_plotter",
-    srcs = ["webgl2_plotter.cc"],
-    hdrs = ["webgl2_plotter.h"],
-    linkopts = [
-        "-s",
-        "USE_WEBGL2=1",
-        "-s",
-        "FULL_ES3=1",
-    ],
-    restricted_to = ["//tools:web"],
-    visibility = ["//visibility:public"],
-    deps = ["@org_tuxfamily_eigen//:eigen"],
-)
-
-cc_library(
-    name = "webgl2_animator",
-    srcs = ["webgl2_animator.cc"],
-    hdrs = ["webgl2_animator.h"],
-    linkopts = [
-        "-s",
-        "USE_WEBGL2=1",
-    ],
-    restricted_to = ["//tools:web"],
-    visibility = ["//visibility:public"],
-    deps = [
-        ":webgl2_plotter",
-        "@org_tuxfamily_eigen//:eigen",
-    ],
-)
diff --git a/frc971/analysis/plotting/webgl2_animator.cc b/frc971/analysis/plotting/webgl2_animator.cc
deleted file mode 100644
index 148245e..0000000
--- a/frc971/analysis/plotting/webgl2_animator.cc
+++ /dev/null
@@ -1,283 +0,0 @@
-#include "frc971/analysis/plotting/webgl2_animator.h"
-
-namespace frc971 {
-namespace plotting {
-
-namespace {
-struct Button {
-  bool IsTransition(const EmscriptenMouseEvent &mouse_event) {
-    return mouse_event.button == transition_number_;
-  }
-  bool IsPressed(const EmscriptenMouseEvent &mouse_event) {
-    return mouse_event.buttons & (1 << pressed_index_);
-  }
-  const size_t transition_number_;
-  const size_t pressed_index_;
-};
-constexpr Button kLeftButton() { return {0, 0}; }
-//constexpr Button kMiddleButton() { return {1, 2}; }
-constexpr Button kRightButton() { return {2, 1}; }
-
-constexpr Button kPanButton() { return kLeftButton(); }
-constexpr Button kZoomButton() { return kRightButton(); }
-}  // namespace
-
-Animator::Animator(const char *canvas_target) : plotter_(canvas_target) {
-  // TODO(james): Write a proper CHECK macro or figure out how to import glog.
-  // Importing glog is a bit of a pain, since it seems to assume all sorts of
-  // things that don't really apply on the web.
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_get_canvas_element_size(canvas_target, &canvas_width_,
-                                            &canvas_height_));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_mousemove_callback("#canvas", this, true,
-                                           &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_click_callback("#canvas", this, true,
-                                       &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_mousedown_callback("#canvas", this, true,
-                                           &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_mouseup_callback("#canvas", this, true,
-                                         &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_mouseleave_callback("#canvas", this, true,
-                                            &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_mouseenter_callback("#canvas", this, true,
-                                            &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_dblclick_callback("#canvas", this, true,
-                                          &Animator::MouseCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_wheel_callback("#canvas", this, true,
-                                       &Animator::WheelCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_keypress_callback("#document", this, true,
-                                          &Animator::KeyboardCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_keydown_callback("#document", this, true,
-                                         &Animator::KeyboardCallback));
-  assert(EMSCRIPTEN_RESULT_SUCCESS ==
-         emscripten_set_keyup_callback("#document", this, true,
-                                       &Animator::KeyboardCallback));
-  emscripten_request_animation_frame_loop(&Animator::Redraw, this);
-}
-
-Eigen::Vector2d Animator::MouseCanvasLocation(
-    const EmscriptenMouseEvent &mouse_event) {
-  return {mouse_event.canvasX * 2.0 / canvas_width_ - 1.0,
-          -mouse_event.canvasY * 2.0 / canvas_height_ + 1.0};
-}
-
-Eigen::Vector2d Animator::CanvasToPlotLocation(
-    const Eigen::Vector2d &canvas_loc) {
-  return (canvas_loc - plotter_.GetOffset()).cwiseQuotient(plotter_.GetScale());
-}
-
-void Animator::PrintZoom() {
-  const Eigen::Vector2d upper_right = CanvasToPlotLocation({1.0, 1.0});
-  const Eigen::Vector2d lower_left = CanvasToPlotLocation({-1.0, -1.0});
-  printf("X range is [%f, %f]; Y range is [%f, %f]\n", lower_left.x(),
-         upper_right.x(), lower_left.y(), upper_right.y());
-}
-
-void Animator::PrintPosition(const EmscriptenMouseEvent &mouse_event) {
-  const Eigen::Vector2d mouse_pos =
-      CanvasToPlotLocation(MouseCanvasLocation(mouse_event));
-  printf("Mouse position: (%f, %f)\n", mouse_pos.x(), mouse_pos.y());
-}
-
-void Animator::HandleMouseUp(const EmscriptenMouseEvent &mouse_event) {
-  if (!kZoomButton().IsTransition(mouse_event)) {
-    return;
-  }
-  // We aborted the zoom early for some reason and so shouldn't execute on it:
-  if (!doing_rectangle_zoom_) {
-    return;
-  }
-  const Eigen::Vector2d mouse_up_location = MouseCanvasLocation(mouse_event);
-  doing_rectangle_zoom_ = false;
-  plotter_.ClearZoomRectangle();
-  // The user probably didn't mean to zoom on that click...
-  if ((mouse_up_location - mouse_down_location_).cwiseAbs().minCoeff() < 1e-3) {
-    return;
-  }
-  const Eigen::Vector2d mouse_up_plot_location =
-      CanvasToPlotLocation(mouse_up_location);
-  const Eigen::Vector2d mouse_down_plot_location =
-      CanvasToPlotLocation(mouse_down_location_);
-  SetZoomCorners(mouse_down_plot_location, mouse_up_plot_location);
-}
-
-void Animator::HandleMouseDown(const EmscriptenMouseEvent &mouse_event) {
-  if (kZoomButton().IsTransition(mouse_event)) {
-    mouse_down_location_ = MouseCanvasLocation(mouse_event);
-    doing_rectangle_zoom_ = true;
-  } else if (kPanButton().IsTransition(mouse_event)) {
-    last_pan_mouse_location_ = MouseCanvasLocation(mouse_event);
-  }
-}
-
-void Animator::HandleMouseMove(const EmscriptenMouseEvent &mouse_event) {
-  const Eigen::Vector2d mouse_location = MouseCanvasLocation(mouse_event);
-  if (kPanButton().IsPressed(mouse_event)) {
-    SetFilteredZoom(plotter_.GetScale(), plotter_.GetOffset() + mouse_location -
-                                            last_pan_mouse_location_);
-    last_pan_mouse_location_ = mouse_location;
-  }
-  if (doing_rectangle_zoom_) {
-    Eigen::Vector2d c1 = CanvasToPlotLocation(mouse_down_location_);
-    Eigen::Vector2d c2 = CanvasToPlotLocation(mouse_location);
-    const Eigen::Vector2d upper_right = CanvasToPlotLocation({1.0, 1.0});
-    const Eigen::Vector2d bottom_left = CanvasToPlotLocation({-1.0, -1.0});
-    if (x_pressed_ && !y_pressed_) {
-      c1.y() = upper_right.y();
-      c2.y() = bottom_left.y();
-    }
-    if (y_pressed_ && !x_pressed_) {
-      c1.x() = upper_right.x();
-      c2.x() = bottom_left.x();
-    }
-    plotter_.SetZoomRectangle(c1, c2);
-  }
-}
-
-void Animator::HandleMouseEnter(const EmscriptenMouseEvent &mouse_event) {
-  // If the zoom button is unclicked and we were zooming, instantly finish the
-  // rectangle zoom.
-  if (doing_rectangle_zoom_ && !kZoomButton().IsPressed(mouse_event)) {
-    plotter_.ClearZoomRectangle();
-    doing_rectangle_zoom_ = false;
-    // Round the current mouse location to the nearest of the four corners.
-    // This is to ensure that a zoom that occurs when the use goes of the edge
-    // of the screen actually goes right up to the edge of the canvas.
-    // Technically, cwiseSign will return zero if you get the mouse enter
-    // event to trigger with the mouse at the center of the screen, but that
-    // seems unlikely.
-    const Eigen::Vector2d canvas_corner =
-        MouseCanvasLocation(mouse_event).cwiseSign();
-    SetZoomCorners(CanvasToPlotLocation(mouse_down_location_),
-                   CanvasToPlotLocation(canvas_corner));
-  }
-}
-
-void Animator::SetZoomCorners(const Eigen::Vector2d &c1,
-                              const Eigen::Vector2d &c2) {
-  const Eigen::Vector2d scale = ((c2 - c1).cwiseAbs() / 2.0).cwiseInverse();
-  const Eigen::Vector2d offset =
-      Eigen::Vector2d::Ones() - scale.cwiseProduct(c2.cwiseMax(c1));
-  SetFilteredZoom(scale, offset);
-}
-
-void Animator::SetFilteredZoom(Eigen::Vector2d scale, Eigen::Vector2d offset) {
-  if (!x_pressed_ && y_pressed_) {
-    scale.x() = plotter_.GetScale().x();
-    offset.x() = plotter_.GetOffset().x();
-  }
-  if (!y_pressed_ && x_pressed_) {
-    scale.y() = plotter_.GetScale().y();
-    offset.y() = plotter_.GetOffset().y();
-  }
-  plotter_.RecordState();
-  plotter_.SetScale(scale);
-  plotter_.SetOffset(offset);
-  PrintZoom();
-}
-
-void Animator::ResetView() {
-  SetZoomCorners(plotter_.MinValues(), plotter_.MaxValues());
-}
-
-int Animator::Redraw(double time_ms, void *data) {
-  Animator *state = reinterpret_cast<Animator *>(data);
-  state->plotter_.Redraw();
-  return 1;
-}
-
-int Animator::KeyboardCallback(int event_type,
-                               const EmscriptenKeyboardEvent *key_event,
-                               void *data) {
-  Animator *state = reinterpret_cast<Animator *>(data);
-  const bool key_is_pressed = event_type == EMSCRIPTEN_EVENT_KEYDOWN;
-  if (strncmp(key_event->key, "x", 2) == 0) {
-    state->x_pressed_ = key_is_pressed;
-    return true;
-  } else if (strncmp(key_event->key, "y", 2) == 0) {
-    state->y_pressed_ = key_is_pressed;
-    return true;
-  } else if (strncmp(key_event->key, "z", 2) == 0 && key_event->ctrlKey) {
-    if (event_type == EMSCRIPTEN_EVENT_KEYUP) {
-      state->plotter_.Undo();
-      state->PrintZoom();
-    }
-    return true;
-  } else if (strncmp(key_event->key, "Escape", 7) == 0) {
-    state->doing_rectangle_zoom_ = false;
-    state->plotter_.ClearZoomRectangle();
-    return true;
-  }
-  return false;
-}
-
-int Animator::WheelCallback(int event_type,
-                            const EmscriptenWheelEvent *wheel_event,
-                            void *data) {
-  Animator *state = reinterpret_cast<Animator *>(data);
-  assert(event_type == EMSCRIPTEN_EVENT_WHEEL);
-  if (wheel_event->deltaMode == DOM_DELTA_PIXEL) {
-    const Eigen::Vector2d canvas_pos =
-        state->MouseCanvasLocation(wheel_event->mouse);
-    constexpr double kWheelTuningScalar = 3.0;
-    const double zoom =
-        -kWheelTuningScalar * wheel_event->deltaY / state->canvas_height_;
-    double zoom_scalar = 1.0 + std::abs(zoom);
-    if (zoom < 0) {
-      zoom_scalar = 1.0 / zoom_scalar;
-    }
-    const Eigen::Vector2d scale = state->plotter_.GetScale() * zoom_scalar;
-    const Eigen::Vector2d offset = (1.0 - zoom_scalar) * canvas_pos +
-                                   zoom_scalar * state->plotter_.GetOffset();
-    state->SetFilteredZoom(scale, offset);
-    return true;
-  }
-  return false;
-}
-
-int Animator::MouseCallback(int event_type,
-                            const EmscriptenMouseEvent *mouse_event,
-                            void *data) {
-  Animator *state = reinterpret_cast<Animator *>(data);
-  switch (event_type) {
-    case EMSCRIPTEN_EVENT_CLICK:
-      state->PrintZoom();
-      state->PrintPosition(*mouse_event);
-      return true;
-      break;
-    case EMSCRIPTEN_EVENT_DBLCLICK:
-      state->ResetView();
-      return true;
-      break;
-    case EMSCRIPTEN_EVENT_MOUSEDOWN:
-      state->HandleMouseDown(*mouse_event);
-      return true;
-      break;
-    case EMSCRIPTEN_EVENT_MOUSEUP:
-      state->HandleMouseUp(*mouse_event);
-      return true;
-      break;
-    case EMSCRIPTEN_EVENT_MOUSEMOVE:
-      state->HandleMouseMove(*mouse_event);
-      return true;
-      break;
-    case EMSCRIPTEN_EVENT_MOUSEENTER:
-      state->HandleMouseEnter(*mouse_event);
-      return true;
-      break;
-  }
-  return false;
-}
-
-}  // namespace plotting
-}  // namespace frc971
diff --git a/frc971/analysis/plotting/webgl2_animator.h b/frc971/analysis/plotting/webgl2_animator.h
deleted file mode 100644
index 4674bc0..0000000
--- a/frc971/analysis/plotting/webgl2_animator.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef FRC971_ANALYSIS_PLOTTING_WEBGL2_ANIMATOR_H_
-#define FRC971_ANALYSIS_PLOTTING_WEBGL2_ANIMATOR_H_
-
-#include <Eigen/Dense>
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-
-#include "frc971/analysis/plotting/webgl2_plotter.h"
-
-namespace frc971 {
-namespace plotting {
-
-// TODO(james): Write some tests for this class. It shouldn't be too hard to
-// abstract out all the direct emscripten calls. Mostly it's just some
-// initialization at the moment.
-class Animator {
- public:
-  Animator(const char *canvas_target);
-
-  Plotter *plotter() { return &plotter_; }
-
- private:
-  Eigen::Vector2d MouseCanvasLocation(const EmscriptenMouseEvent &mouse_event);
-
-  Eigen::Vector2d CanvasToPlotLocation(const Eigen::Vector2d &canvas_loc);
-
-  void PrintZoom();
-
-  void PrintPosition(const EmscriptenMouseEvent &mouse_event);
-
-  void HandleMouseUp(const EmscriptenMouseEvent &mouse_event);
-
-  void HandleMouseDown(const EmscriptenMouseEvent &mouse_event);
-
-  void HandleMouseMove(const EmscriptenMouseEvent &mouse_event);
-
-  void HandleMouseEnter(const EmscriptenMouseEvent &mouse_event);
-
-  void SetZoomCorners(const Eigen::Vector2d &c1, const Eigen::Vector2d &c2);
-
-  void SetFilteredZoom(Eigen::Vector2d scale, Eigen::Vector2d offset);
-
-  void ResetView();
-
-  static int Redraw(double time_ms, void *data);
-  static int KeyboardCallback(int event_type,
-                              const EmscriptenKeyboardEvent *key_event,
-                              void *data);
-  static int WheelCallback(int event_type,
-                           const EmscriptenWheelEvent *wheel_event, void *data);
-  static int MouseCallback(int event_type,
-                           const EmscriptenMouseEvent *mouse_event, void *data);
-
-  int canvas_width_ = 0.0;
-  int canvas_height_ = 0.0;
-
-  // Location, in canvas coordinates of the last left click mouse-down event.
-  Eigen::Vector2d mouse_down_location_{0, 0};
-
-  // True if the user is currently dragging their mouse to zoom to a rectangle.
-  // This is used to (a) determine whether we should subsequently execute the
-  // zoom when the user releases the mouse and (b) to know when to draw a
-  // rectangle indicating where the user is zooming to.
-  bool doing_rectangle_zoom_ = false;
-
-  // The last location of the mouse when panning, so that we can calculate
-  // exactly how much the mouse has moved since the last mouse-move callback.
-  Eigen::Vector2d last_pan_mouse_location_{0, 0};
-
-  // Whether the "x" or "y" key is currently pressed on the keyboard.
-  bool x_pressed_ = false;
-  bool y_pressed_ = false;
-
-  WebglCanvasPlotter plotter_;
-};
-
-}  // namespace plotting
-}  // namespace frc971
-#endif  // FRC971_ANALYSIS_PLOTTING_WEBGL2_ANIMATOR_H_
diff --git a/frc971/analysis/plotting/webgl2_plotter.cc b/frc971/analysis/plotting/webgl2_plotter.cc
deleted file mode 100644
index b10a1de..0000000
--- a/frc971/analysis/plotting/webgl2_plotter.cc
+++ /dev/null
@@ -1,277 +0,0 @@
-#include "frc971/analysis/plotting/webgl2_plotter.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include <iostream>
-
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-
-namespace frc971 {
-namespace plotting {
-
-namespace {
-// Shader and program construction taken from examples at
-// https://github.com/emscripten-core/emscripten/blob/incoming/tests/webgl2_draw_packed_triangle.c
-GLuint compile_shader(GLenum shaderType, const char *src) {
-  GLuint shader = glCreateShader(shaderType);
-  glShaderSource(shader, 1, &src, nullptr);
-  glCompileShader(shader);
-
-  GLint isCompiled = 0;
-  glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
-  if (!isCompiled) {
-    GLint maxLength = 0;
-    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
-    char *buf = (char *)malloc(maxLength + 1);
-    glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
-    printf("%s\n", buf);
-    free(buf);
-    return 0;
-  }
-
-  return shader;
-}
-
-GLuint create_program(GLuint vertexShader, GLuint fragmentShader,
-                      GLuint attribute_location) {
-  GLuint program = glCreateProgram();
-  glAttachShader(program, vertexShader);
-  glAttachShader(program, fragmentShader);
-  glBindAttribLocation(program, attribute_location, "apos");
-  glLinkProgram(program);
-  return program;
-}
-
-GLuint CreateLineProgram(GLuint attribute_location) {
-  // Create a shader program which will take in:
-  // -A series of points to plot
-  // -Scale/offset parameters for choosing how to zoom/pan
-  // -Point size/color information
-  //
-  // The vertex shader then takes in the series of points (apos) and
-  // transforms the points by the scale/offset to determine their on-screen
-  // position. We aren't doing any funny with 3D or perspective, so we leave
-  // the z and w components untouched.
-  //
-  // We set the color of the line in the fragment shader.
-  const char vertex_shader[] =
-    "#version 100\n"
-    "attribute vec2 apos;"
-    "uniform vec2 scale;"
-    "uniform vec2 offset;"
-    "uniform float point_size;"
-    "void main() {"
-      "gl_Position.xy = apos.xy * scale.xy + offset.xy;"
-      "gl_Position.z = 0.0;"
-      "gl_Position.w = 1.0;"
-      "gl_PointSize = point_size;"
-    "}";
-  GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
-
-  const char fragment_shader[] =
-    "#version 100\n"
-    "precision lowp float;"
-    "uniform vec4 color;"
-    "void main() {"
-      "gl_FragColor = color;"
-    "}";
-  GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
-  return create_program(vs, fs, attribute_location);
-}
-
-const Eigen::Vector2d Vector2dInfinity() {
-  return Eigen::Vector2d::Ones() * std::numeric_limits<double>::infinity();
-}
-
-}  // namespace
-
-class WebglLine : public Line {
- public:
-  WebglLine(GLuint program, size_t buffer_size = 1000000)
-      : color_uniform_location_(glGetUniformLocation(program, "color")),
-        point_size_uniform_location_(
-            glGetUniformLocation(program, "point_size")),
-        line_size_(0) {}
-  virtual ~WebglLine() {}
-  void SetPoints(const std::vector<Eigen::Vector2d> &pts) override {
-    updated_ = true;
-    max_values_ = -Vector2dInfinity();
-    min_values_ = Vector2dInfinity();
-    line_size_ = 0;
-    buffer_.clear();
-    for (const auto &pt : pts) {
-      buffer_.push_back(pt.x());
-      buffer_.push_back(pt.y());
-      max_values_ = max_values_.cwiseMax(pt);
-      min_values_ = min_values_.cwiseMin(pt);
-      ++line_size_;
-    }
-  }
-  void Draw() override {
-    updated_ = false;
-    if (buffer_.empty()) {
-      return;
-    }
-    // TODO(james): Flushing and rewriting the buffer on every line draw seems
-    // like it should be inefficient, but in practice it seems to actually be
-    // fine for the amounts of data that we are dealing with (i.e., doing a few
-    // tens of MB of copies at the most is not actually that expensive).
-    glBufferData(GL_ARRAY_BUFFER, buffer_.size() * sizeof(float),
-                 buffer_.data(), GL_STATIC_DRAW);
-    glUniform4f(color_uniform_location_, color_.r, color_.g, color_.b, 1.0);
-    glUniform1f(point_size_uniform_location_, point_size_);
-    if (point_size_ != 0) {
-      glDrawArrays(GL_POINTS, 0, line_size_);
-    }
-    if (line_width_ != 0) {
-      glDrawArrays(GL_LINE_STRIP, 0, line_size_);
-    }
-    assert(GL_NO_ERROR == glGetError() && "glDrawArray failed");
-  }
-  void SetColor(const Color &color) override {
-    updated_ = true;
-    color_ = color;
-  }
-
-  Eigen::Vector2d MaxValues() const override { return max_values_; }
-  Eigen::Vector2d MinValues() const override { return min_values_; }
-
-  void SetLineWidth(const float width) override {
-    updated_ = true;
-    line_width_ = width;
-  }
-  void SetPointSize(const float point_size) override {
-    updated_ = true;
-    point_size_ = point_size;
-  }
-
-  bool HasUpdate() override { return updated_; }
-
- private:
-  const GLuint color_uniform_location_;
-  const GLuint point_size_uniform_location_;
-  std::vector<float> buffer_;
-  size_t line_size_;
-  Color color_;
-  Eigen::Vector2d max_values_ = -Vector2dInfinity();
-  Eigen::Vector2d min_values_ = Vector2dInfinity();
-  float line_width_ = 1.0;
-  float point_size_ = 3.0;
-  bool updated_ = true;
-};
-
-WebglCanvasPlotter::WebglCanvasPlotter(const std::string &canvas_id,
-                                       GLuint attribute_location)  {
-  EmscriptenWebGLContextAttributes attr;
-  emscripten_webgl_init_context_attributes(&attr);
-  assert(attr.antialias && "Antialiasing should be enabled by default.");
-  attr.majorVersion = 2;
-  EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
-  assert(ctx && "Failed to create WebGL2 context");
-  emscripten_webgl_make_context_current(ctx);
-
-  program_ = CreateLineProgram(attribute_location);
-  scale_uniform_location_ = glGetUniformLocation(program_, "scale");
-  offset_uniform_location_ = glGetUniformLocation(program_, "offset");
-
-  glGenBuffers(1, &gl_buffer_);
-  glUseProgram(program_);
-  glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_);
-  glVertexAttribPointer(attribute_location, 2, GL_FLOAT, GL_FALSE, 8, 0);
-  glEnableVertexAttribArray(attribute_location);
-
-  zoom_rectangle_ = std::make_unique<WebglLine>(program_);
-  zoom_rectangle_->SetColor({.r = 1.0, .g = 1.0, .b = 1.0});
-  zoom_rectangle_->SetLineWidth(2.0);
-  zoom_rectangle_->SetPointSize(0.0);
-}
-
-Line *WebglCanvasPlotter::AddLine() {
-  lines_.push_back(std::make_unique<WebglLine>(program_));
-  return lines_.back().get();
-}
-void WebglCanvasPlotter::Undo() {
-  if (old_scales_.empty() || old_offsets_.empty()) {
-    return;
-  }
-  scale_ = old_scales_.back();
-  old_scales_.pop_back();
-  offset_ = old_offsets_.back();
-  old_offsets_.pop_back();
-}
-
-void WebglCanvasPlotter::RecordState() {
-  old_scales_.push_back(scale_);
-  old_offsets_ .push_back(offset_);
-}
-
-void WebglCanvasPlotter::SetScale(const Eigen::Vector2d &scale) {
-  scale_ = scale;
-}
-
-Eigen::Vector2d WebglCanvasPlotter::GetScale() const {
-  return scale_;
-}
-
-void WebglCanvasPlotter::SetOffset(const Eigen::Vector2d &offset) {
-  offset_ = offset;
-}
-
-Eigen::Vector2d WebglCanvasPlotter::GetOffset() const {
-  return offset_;
-}
-
-void WebglCanvasPlotter::Redraw() {
-  const bool scaling_update = last_scale_ != scale_ || last_offset_ != offset_;
-  bool data_update = zoom_rectangle_->HasUpdate();
-  for (const auto &line : lines_) {
-    data_update = line->HasUpdate() || data_update;
-  }
-  if (!scaling_update && !data_update) {
-    return;
-  }
-  glUseProgram(program_);
-  glBindBuffer(GL_ARRAY_BUFFER, gl_buffer_);
-  glUniform2f(scale_uniform_location_, scale_.x(), scale_.y());
-  glUniform2f(offset_uniform_location_, offset_.x(), offset_.y());
-  for (const auto &line : lines_) {
-    line->Draw();
-  }
-  zoom_rectangle_->Draw();
-  last_scale_ = scale_;
-  last_offset_ = offset_;
-}
-
-Eigen::Vector2d WebglCanvasPlotter::MaxValues() const {
-  Eigen::Vector2d max = -Vector2dInfinity();
-  for (const auto &line : lines_) {
-    max = max.cwiseMax(line->MaxValues());
-  }
-  return max;
-}
-Eigen::Vector2d WebglCanvasPlotter::MinValues() const {
-  Eigen::Vector2d min = Vector2dInfinity();
-  for (const auto &line : lines_) {
-    min = min.cwiseMin(line->MinValues());
-  }
-  return min;
-}
-
-void WebglCanvasPlotter::ClearZoomRectangle() {
-  zoom_rectangle_->SetPoints({});
-}
-
-
-void WebglCanvasPlotter::SetZoomRectangle(const Eigen::Vector2d &corner1,
-                                          const Eigen::Vector2d &corner2) {
-  zoom_rectangle_->SetPoints({corner1,
-                              {corner1.x(), corner2.y()},
-                              corner2,
-                              {corner2.x(), corner1.y()},
-                              corner1});
-}
-
-}  // namespace plotting
-}  // namespace frc971
diff --git a/frc971/analysis/plotting/webgl2_plotter.h b/frc971/analysis/plotting/webgl2_plotter.h
deleted file mode 100644
index 6da086c..0000000
--- a/frc971/analysis/plotting/webgl2_plotter.h
+++ /dev/null
@@ -1,88 +0,0 @@
-#ifndef FRC971_ANALYSIS_PLOTTING_WEBGL2_PLOTTER_H_
-#define FRC971_ANALYSIS_PLOTTING_WEBGL2_PLOTTER_H_
-
-#include <vector>
-
-#include <Eigen/Dense>
-#define GL_GLEXT_PROTOTYPES
-#include <GLES3/gl3.h>
-#include <GLES3/gl2ext.h>
-#include <GLES3/gl32.h>
-
-namespace frc971 {
-namespace plotting {
-
-struct Color {
-  float r;
-  float g;
-  float b;
-};
-
-class Line {
- public:
-  virtual ~Line() {}
-  virtual void SetPoints(const std::vector<Eigen::Vector2d> &pts) = 0;
-  virtual void SetColor(const Color &color) = 0;
-  virtual void Draw() = 0;
-  virtual Eigen::Vector2d MaxValues() const = 0;
-  virtual Eigen::Vector2d MinValues() const = 0;
-  virtual void SetLineWidth(const float width) = 0;
-  virtual void SetPointSize(const float point_size) = 0;
-  virtual bool HasUpdate() = 0;
-};
-
-// TODO(james): Actually do something with this interface; originally, I'd meant
-// to look at writing some tests, but right now it's just extra boilerplate.
-class Plotter {
- public:
-  virtual Line *AddLine() = 0;
-  virtual void SetScale(const Eigen::Vector2d &scale) = 0;
-  virtual Eigen::Vector2d GetScale() const = 0;
-  virtual void SetOffset(const Eigen::Vector2d &offset) = 0;
-  virtual Eigen::Vector2d GetOffset() const = 0;
-  virtual void Redraw() = 0;
-  virtual Eigen::Vector2d MaxValues() const = 0;
-  virtual Eigen::Vector2d MinValues() const = 0;
-  virtual void ClearZoomRectangle() = 0;
-  virtual void SetZoomRectangle(const Eigen::Vector2d &corner1,
-                                const Eigen::Vector2d &corner2) = 0;
-  virtual void RecordState() = 0;
-  virtual void Undo() = 0;
-};
-
-class WebglCanvasPlotter : public Plotter {
- public:
-  WebglCanvasPlotter(const std::string &canvas_id,
-                     GLuint attribute_location = 0);
-  Line *AddLine() override;
-  void SetScale(const Eigen::Vector2d &scale) override;
-  Eigen::Vector2d GetScale() const override;
-  void SetOffset(const Eigen::Vector2d &offset) override;
-  Eigen::Vector2d GetOffset() const override;
-  void Redraw() override;
-  Eigen::Vector2d MaxValues() const override;
-  Eigen::Vector2d MinValues() const override;
-  void ClearZoomRectangle() override;
-  void SetZoomRectangle(const Eigen::Vector2d &corner1,
-                        const Eigen::Vector2d &corner2) override;
-  void RecordState() override;
-  void Undo() override;
-
- private:
-  std::vector<std::unique_ptr<Line>> lines_;
-  std::unique_ptr<Line> zoom_rectangle_;
-  Eigen::Vector2d scale_{1.0, 1.0};
-  Eigen::Vector2d offset_{0.0, 0.0};
-  std::vector<Eigen::Vector2d> old_scales_;
-  std::vector<Eigen::Vector2d> old_offsets_;
-  Eigen::Vector2d last_scale_{1.0, 1.0};
-  Eigen::Vector2d last_offset_{0.0, 0.0};
-  GLuint program_;
-  GLuint scale_uniform_location_;
-  GLuint offset_uniform_location_;
-  GLuint gl_buffer_;
-};
-
-}  // namespace plotting
-}  // namespace frc971
-#endif  // FRC971_ANALYSIS_PLOTTING_WEBGL2_PLOTTER_H_
diff --git a/third_party/eigen/BUILD b/third_party/eigen/BUILD
index f3036f2..54c5cd1 100644
--- a/third_party/eigen/BUILD
+++ b/third_party/eigen/BUILD
@@ -20,7 +20,7 @@
     ) + ["unsupported/Eigen/MatrixFunctions"] + glob([
         "unsupported/Eigen/src/MatrixFunctions/*.h",
     ]),
-    compatible_with = mcu_cpus + ["@//tools:web"],
+    compatible_with = mcu_cpus,
     includes = ["."],
     visibility = ["//visibility:public"],
 )
diff --git a/third_party/gmp/BUILD b/third_party/gmp/BUILD
index 0ac8edc..42c04d6 100644
--- a/third_party/gmp/BUILD
+++ b/third_party/gmp/BUILD
@@ -17,10 +17,6 @@
         "x86_64",
         "generic",
     ],
-    "@//tools:cpu_web": [
-        "x86_64",
-        "generic",
-    ],
     "@//tools:cpu_roborio": [
         "arm/v7a/cora9",
         "arm/v6t2",
diff --git a/tools/BUILD b/tools/BUILD
index 9bf2931..50ba0bb 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -14,21 +14,11 @@
 )
 
 config_setting(
-    name = "compiler_emscripten",
-    values = {"compiler": "emscripten"},
-)
-
-config_setting(
     name = "cpu_k8",
     values = {"cpu": "k8"},
 )
 
 config_setting(
-    name = "cpu_web",
-    values = {"cpu": "web"},
-)
-
-config_setting(
     name = "cpu_roborio",
     values = {"cpu": "roborio"},
 )
@@ -74,8 +64,6 @@
 
 environment(name = "roborio")
 
-environment(name = "web")
-
 environment(name = "cortex-m4f")
 
 environment(name = "cortex-m4f-k22")
@@ -93,7 +81,6 @@
     environments = [
         ":k8",
         ":roborio",
-        ":web",
         ":armhf-debian",
         ":cortex-m4f",
         ":cortex-m4f-k22",
diff --git a/tools/build_rules/select.bzl b/tools/build_rules/select.bzl
index 3483360..1c48eb7 100644
--- a/tools/build_rules/select.bzl
+++ b/tools/build_rules/select.bzl
@@ -3,11 +3,6 @@
 # quickly find issues where something new isn't handled.
 # It will also make adding ORs when it makes sense easy to do nicely.
 
-# TODO(james): Decide what to do about webassembly/emscripten CPU and
-# compiler configurations. Bazel does not seem to handle the fact that a select
-# statement may not logically need to be evaluated for certain configurations
-# (e.g., most targets can't be build for --cpu=web, so handling "web" in the
-# cpu_select should notionally be unnecessary).
 all_cpus = [
     "amd64",
     "roborio",
@@ -52,7 +47,6 @@
         "@//tools:cpu_armhf": values["armhf"],
         "@//tools:cpu_cortex_m4f": values["cortex-m"],
         "@//tools:cpu_cortex_m4f_k22": values["cortex-m"],
-        "@//tools:cpu_web": None,
     })
 
 """A select wrapper for address space sizes.
@@ -73,7 +67,6 @@
         "@//tools:cpu_armhf": values["32"],
         "@//tools:cpu_cortex_m4f": values["32"],
         "@//tools:cpu_cortex_m4f_k22": values["32"],
-        "@//tools:cpu_web": None,
     })
 
 """A select wrapper for compilers.
@@ -92,5 +85,4 @@
     return select({
         "@//tools:compiler_gcc": values["gcc"],
         "@//tools:compiler_clang": values["clang"],
-        "@//tools:compiler_emscripten": None,
     })
diff --git a/tools/ci/run-tests.sh b/tools/ci/run-tests.sh
index 1a948f1..918377c 100755
--- a/tools/ci/run-tests.sh
+++ b/tools/ci/run-tests.sh
@@ -34,8 +34,3 @@
     ${COMMON} \
     --cpu=cortex-m4f \
     ${M4F_TARGETS}
-
-bazel --output_base=../web_output_base build \
-    ${COMMON} \
-    --cpu=web \
-    ${TARGETS}
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index c0d0b38..fd41261 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -12,7 +12,6 @@
         "//tools:has_tsan": [],
         "//tools:cpu_cortex_m4f": [],
         "//tools:cpu_cortex_m4f_k22": [],
-        "//tools:cpu_web": [],
         "//conditions:default": ["//third_party/gperftools:tcmalloc"],
     }),
 )
@@ -35,7 +34,6 @@
         "armhf-debian|clang": "cc-compiler-armhf-debian",
         "cortex-m4f|gcc": "cc-compiler-cortex-m4f",
         "cortex-m4f-k22|gcc": "cc-compiler-cortex-m4f-k22",
-        "web|emscripten": "//tools/cpp/emscripten:cc_emscripten",
     },
 )
 
diff --git a/tools/cpp/CROSSTOOL b/tools/cpp/CROSSTOOL
index 5ebddd4..c050873 100644
--- a/tools/cpp/CROSSTOOL
+++ b/tools/cpp/CROSSTOOL
@@ -16,10 +16,6 @@
   toolchain_identifier: "stub_armeabi-v7a"
 }
 default_toolchain {
-  cpu: "web"
-  toolchain_identifier: "emscripten_toolchain"
-}
-default_toolchain {
   cpu: "armhf-debian"
   toolchain_identifier: "clang_linux_armhf"
 }
@@ -324,153 +320,6 @@
   }
 }
 toolchain {
-  toolchain_identifier: "emscripten_toolchain"
-  host_system_name: "web"
-  target_system_name: "emscripten-unknown-emscripten"
-  target_cpu: "web"
-  target_libc: "unknown"
-  compiler: "emscripten"
-  abi_version: "unknown"
-  abi_libc_version: "unknown"
-  tool_path {
-    name: "gcc"
-    path: "emscripten/emcc.sh"
-  }
-  tool_path {
-    name: "ld"
-    path: "emscripten/emcc.sh"
-  }
-  tool_path {
-    name: "ar"
-    path: "emscripten/emar.sh"
-  }
-  tool_path {
-    name: "cpp"
-    path: "/bin/false"
-  }
-  tool_path {
-    name: "gcov"
-    path: "/bin/false"
-  }
-  tool_path {
-    name: "nm"
-    path: "/bin/false"
-  }
-  tool_path {
-    name: "objdump"
-    path: "/bin/false"
-  }
-  tool_path {
-    name: "objcopy"
-    path: "/bin/false"
-  }
-  tool_path {
-    name: "strip"
-    path: "/bin/false"
-  }
-  compiler_flag: "-isystem"
-  compiler_flag: "external/emscripten_toolchain/system/include"
-  compiler_flag: "-fno-exceptions"
-  compiler_flag: "-fdiagnostics-color=always"
-  compiler_flag: "-Wall"
-  compiler_flag: "-Werror"
-  compiler_flag: "-ffunction-sections"
-  compiler_flag: "-fdata-sections"
-  linker_flag: "-no-canonical-prefixes"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/libcxx"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/lib/libcxxabi/include"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/compat"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/include"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/SSE"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/libc"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/lib/libc/musl/arch/emscripten"
-  unfiltered_cxx_flag: "-isystem"
-  unfiltered_cxx_flag: "external/emscripten_toolchain/system/local/include"
-  unfiltered_cxx_flag: "-no-canonical-prefixes"
-  unfiltered_cxx_flag: "-Wno-builtin-macro-redefined"
-  unfiltered_cxx_flag: "-D__DATE__=\"redacted\""
-  unfiltered_cxx_flag: "-D__TIMESTAMP__=\"redacted\""
-  unfiltered_cxx_flag: "-D__TIME__=\"redacted\""
-  feature {
-    name: "opt"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=0"
-        flag: "-O2"
-        flag: "--closure"
-        flag: "1"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "dbg"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=1"
-      }
-      flag_group {
-        flag: "-fno-omit-frame-pointer"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "fastbuild"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=0"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "all_modes"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "assemble"
-      action: "c-compile"
-      flag_group {
-        flag: "-std=gnu99"
-      }
-    }
-    flag_set {
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-std=gnu++1y"
-      }
-    }
-  }
-}
-toolchain {
   toolchain_identifier: "roborio_linux"
   host_system_name: "roborio"
   target_system_name: "roborio"
diff --git a/tools/cpp/emscripten/BUILD b/tools/cpp/emscripten/BUILD
deleted file mode 100644
index 6a67e4f..0000000
--- a/tools/cpp/emscripten/BUILD
+++ /dev/null
@@ -1,77 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-_minimum_fileset = [
-    "emar.sh",
-    "emcc.sh",
-    "@emscripten_clang//:all",
-    "@emscripten_toolchain//:all",
-    "@nodejs//:bin/node",
-]
-
-filegroup(
-    name = "minimum_files",
-    srcs = _minimum_fileset,
-)
-
-filegroup(
-    name = "all",
-    srcs = _minimum_fileset + [
-        ":emscripten_cache_content",
-    ],
-)
-
-# A list of all the cached libraries generad and used by emscripten.
-_libs = \
-    ["emscripten_cache/asmjs/" + lib for lib in [
-        "generated_struct_info.json",
-        "libc.bc",
-        "libcompiler_rt.a",
-        "libc-wasm.bc",
-        "libpthreads_stub.bc",
-        "libhtml5.bc",
-        "libdlmalloc.bc",
-        "libal.bc",
-        "libc++_noexcept.a",
-        "libc++abi.bc",
-        "libgl-webgl2.bc",
-        "libgl.bc",
-        "libc-extras.bc",
-    ]]
-
-filegroup(
-    name = "emscripten_cache_content",
-    srcs = glob(["emscripten_cache/**/*"]) + _libs,
-)
-
-cc_toolchain(
-    name = "cc_emscripten",
-    all_files = ":all",
-    compiler_files = ":all",
-    cpu = "web",
-    dwp_files = ":empty",
-    dynamic_runtime_libs = [":empty"],
-    linker_files = ":all",
-    objcopy_files = ":empty",
-    static_runtime_libs = [":empty"],
-    strip_files = ":empty",
-    supports_param_files = 0,
-)
-
-# TODO(james): Currently, this gets built with the host configuration.
-# Currently, that doesn't actually impact the build since there's nothing that
-# affects how the genrule is run. However, that also means that changing
-# the configuration (and thus the flags that may be passed to the C++
-# compiler) will not change how these cache files are generated.
-genrule(
-    name = "gencache",
-    # Note that foo.o is just some arbitrary .o file. I had trouble getting
-    # emscripten to work properly when pointed at a literally empty file, but
-    # the exact contents of the .o aren't particularly important.
-    srcs = [":foo.o"],
-    outs = _libs,
-    cmd = "$(location gencache.sh) $(OUTS)",
-    tools = [
-        ":gencache.sh",
-        ":minimum_files",
-    ],
-)
diff --git a/tools/cpp/emscripten/defs.bzl b/tools/cpp/emscripten/defs.bzl
deleted file mode 100644
index e63d67e..0000000
--- a/tools/cpp/emscripten/defs.bzl
+++ /dev/null
@@ -1,105 +0,0 @@
-def _emcc_expand_files_impl(ctx):
-    tarfile = ctx.file.tarfile
-    html_shell = ctx.file.html_shell
-    basename = ctx.attr.name
-    html_out = ctx.actions.declare_file(basename + ".html")
-    tar_outs = [
-        ctx.actions.declare_file(basename + "." + extension)
-        for extension in ["js", "wasm"]
-    ]
-    if html_shell:
-        ctx.actions.expand_template(
-            output = html_out,
-            template = html_shell,
-            substitutions = {
-                "{{{ SCRIPT }}}": "<script async type=\"text/javascript\" src=\"" + basename +
-                                  ".js\"></script>",
-            },
-        )
-    else:
-        tar_outs.append(html_out)
-
-    ctx.actions.run_shell(
-        outputs = tar_outs,
-        inputs = [tarfile],
-        command = "tar xf " + tarfile.path + " -C \"" + html_out.dirname + "\"",
-    )
-
-    return [DefaultInfo(files = depset(tar_outs + [html_out]))]
-
-emcc_expand_files = rule(
-    attrs = {
-        "html_shell": attr.label(
-            mandatory = False,
-            allow_single_file = True,
-        ),
-        "tarfile": attr.label(
-            mandatory = True,
-            allow_single_file = True,
-        ),
-    },
-    doc = """
-    Handles the intermediate processing to extra files from a tarball
-    for emcc_binary. See emcc_binary for more detail.""",
-    implementation = _emcc_expand_files_impl,
-)
-
-def emcc_binary(name, srcs = [], linkopts = [], html_shell = None, **kwargs):
-    """Produces a deployable set of WebAssembly files.
-
-    Depending on the settings, the exact format of the output varies.
-    The output will be a a .js, .wasm, and optional .html file, all sharing the
-    same basename. The .js file is the script that should be included in any
-    webpage, and will handle calling the code compiled to the .wasm file.
-
-    The optional .html file uses some existing template html file and adds the
-    necessary <script> statement to import the .js script. This html file will
-    be generated if the name of the rule ends with ".html"; if the html_shell
-    argument is specified, then the provided html file is used to generate the
-    output html. The only change made to the template html is to replace any
-    instances of "{{{ SCRIPT }}}" with the appropriate <script> tags. This is
-    consistent with how the "--shell-file" flag works in emscripten. However, we
-    can't use the builtin flag with the script in its current form, because
-    that would require making an html file an input to a cc_library rule,
-    which bazel gets obnoxious about.
-
-    This macro also defines a rule with a name equal to the basename of
-    the name argument (e.g., if name = "foo.html", basename = "foo"). This rule
-    is the rule that actually outputs the required files.
-
-    Internally, this rule works by:
-    1) Generating a tarball that contains the .js and .wasm files, using
-       a cc_binary that calls the emscripten compiler.
-    2) Extracting said tarball.
-    3) [if necessary] Generating the output html from the html shell template.
-    """
-    includehtml = False
-    linkopts = list(linkopts)
-    srcs = list(srcs)
-    if name.endswith(".html"):
-        basename = name[:-5]
-        includehtml = True
-    elif name.endswith(".js"):
-        basename = name[:-3]
-    outputs = []
-    outputs.append(basename + ".js")
-    outputs.append(basename + ".wasm")
-
-    if includehtml and not html_shell:
-        outputs.append(basename + ".html")
-    tarfile = name + ".tar"
-    if html_shell:
-        tarfile = basename + ".js.tar"
-    native.cc_binary(
-        name = tarfile,
-        srcs = srcs,
-        linkopts = linkopts,
-        restricted_to = ["//tools:web"],
-        **kwargs
-    )
-    emcc_expand_files(
-        name = basename,
-        html_shell = html_shell,
-        tarfile = tarfile,
-        restricted_to = ["//tools:web"],
-    )
diff --git a/tools/cpp/emscripten/emar.sh b/tools/cpp/emscripten/emar.sh
deleted file mode 100755
index a94fb0b..0000000
--- a/tools/cpp/emscripten/emar.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-# Originally sourced from https://github.com/ribrdb/rules_emscripten
-set -euo pipefail
-EM_CONFIG="LLVM_ROOT='external/emscripten_clang';"
-EM_CONFIG+="EMSCRIPTEN_NATIVE_OPTIMIZER='external/emscripten_clang/optimizer';"
-EM_CONFIG+="BINARYEN_ROOT='external/emscripten_clang/binaryen';"
-EM_CONFIG+="NODE_JS='external/nodejs/bin/node';"
-EM_CONFIG+="EMSCRIPTEN_ROOT='external/emscripten_toolchain';"
-EM_CONFIG+="SPIDERMONKEY_ENGINE='';"
-EM_CONFIG+="V8_ENGINE='';"
-EM_CONFIG+="TEMP_DIR='tmp';"
-EM_CONFIG+="COMPILER_ENGINE=NODE_JS;"
-EM_CONFIG+="JS_ENGINES=[NODE_JS];"
-export EM_CONFIG
-
-export EM_EXCLUSIVE_CACHE_ACCESS=1
-export EMCC_SKIP_SANITY_CHECK=1
-export EMCC_WASM_BACKEND=0
-export EMMAKEN_NO_SDK=1
-
-python external/emscripten_toolchain/emar.py "$@"
diff --git a/tools/cpp/emscripten/emcc.sh b/tools/cpp/emscripten/emcc.sh
deleted file mode 100755
index 814cd04..0000000
--- a/tools/cpp/emscripten/emcc.sh
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/bash
-# Originally sourced from https://github.com/ribrdb/rules_emscripten
-set -euo pipefail
-EM_CONFIG="LLVM_ROOT='$(pwd -P)/external/emscripten_clang';"
-EM_CONFIG+="EMSCRIPTEN_NATIVE_OPTIMIZER='external/emscripten_clang/optimizer';"
-EM_CONFIG+="BINARYEN_ROOT='external/emscripten_clang/binaryen';"
-EM_CONFIG+="NODE_JS='$(pwd -P)/external/nodejs/bin/node';"
-EM_CONFIG+="EMSCRIPTEN_ROOT='external/emscripten_toolchain';"
-EM_CONFIG+="SPIDERMONKEY_ENGINE='';"
-EM_CONFIG+="V8_ENGINE='';"
-EM_CONFIG+="TEMP_DIR='tmp';"
-EM_CONFIG+="COMPILER_ENGINE=NODE_JS;"
-EM_CONFIG+="JS_ENGINES=[NODE_JS];"
-export EM_CONFIG
-
-export EM_EXCLUSIVE_CACHE_ACCESS=1
-export EMCC_SKIP_SANITY_CHECK=1
-# export EMCC_DEBUG=2
-export EMCC_WASM_BACKEND=0
-export EMMAKEN_NO_SDK=1
-
-mkdir -p "tmp/emscripten_cache"
-export EM_CACHE="$(pwd -P)/tmp/emscripten_cache"
-export EMCC_TEMP_DIR="$(pwd -P)/tmp"
-
-# Prepare the cache content so emscripten doesn't try to rebuild it all the time
-cache_source=tools/cpp/emscripten/emscripten_cache
-# TODO(james): How do I avoid hardcoding this path? This is needed to make
-# gencache.sh work properly and to have it put the files in the correct spot.
-if [ -d bazel-out/host/bin/tools/cpp/emscripten/emscripten_cache ]; then
-  cache_source=bazel-out/host/bin/tools/cpp/emscripten/emscripten_cache
-elif [ -d external/rules_emscripten/toolchain/emscripten_cache ]; then
-  cache_source=external/rules_emscripten/toolchain/emscripten_cache
-fi
-(
-  cd tmp/emscripten_cache;
-  for n in "../../$cache_source"/*;do
-    ln -f -s "$n"
-  done
-)
-
-argv=("$@")
-tarfile=
-# Find the -o option, and strip the .tar from it.
-for (( i=0; i<$#; i++ )); do
-  if [[ "x${argv[i]}" == x-o ]]; then
-    arg=${argv[$((i+1))]}
-    if [[ "x$arg" == x*.tar ]];then
-        tarfile="$(cd $(dirname "$arg"); pwd -P)/$(basename "$arg")"
-        emfile="$(dirname "$arg")/$(basename $arg .tar)"
-        basearg="$(basename "$(basename "$(basename "$emfile" .js)" .html)" .wasm)"
-        baseout="$(dirname "$arg")/$basearg"
-        argv[$((i+1))]="$emfile"
-    fi
-  fi
-done
-python external/emscripten_toolchain/emcc.py "${argv[@]}"
-# Now create the tarfile
-shopt -s extglob
-if [ "x$tarfile" != x ]; then
-  outdir="$(dirname "$baseout")"
-  outbase="$(basename "$baseout")"
-  (
-      cd "$outdir";
-      tar cf "$tarfile" "$outbase."?(html|js|wasm|mem|data|worker.js)
-  )
-fi
diff --git a/tools/cpp/emscripten/foo.o b/tools/cpp/emscripten/foo.o
deleted file mode 100755
index 28c06de..0000000
--- a/tools/cpp/emscripten/foo.o
+++ /dev/null
Binary files differ
diff --git a/tools/cpp/emscripten/gencache.sh b/tools/cpp/emscripten/gencache.sh
deleted file mode 100755
index d8391d8..0000000
--- a/tools/cpp/emscripten/gencache.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-# This script forces generation of all the necessary cache files from emscripten.
-export EMCC_FORCE_STDLIBS=1
-# Run with WEBGL2 enabled and not, as the compiler will only generate one of the
-# webgl libraries at once.
-tools/cpp/emscripten/emcc.sh -o foo.html.tar tools/cpp/emscripten/foo.o -s 'USE_WEBGL2=1' -no-canonical-prefixes
-tools/cpp/emscripten/emcc.sh -o foo.html.tar tools/cpp/emscripten/foo.o -s 'USE_WEBGL2=0' -no-canonical-prefixes
-for OUTPUT in $@
-do
-  if [ ! -f ${OUTPUT} ]; then
-    cp -f tmp/emscripten_cache/asmjs/$(basename ${OUTPUT}) ${OUTPUT}
-  fi
-done
diff --git a/tools/cpp/static_crosstool.pb b/tools/cpp/static_crosstool.pb
index 242485b..dc650d9 100644
--- a/tools/cpp/static_crosstool.pb
+++ b/tools/cpp/static_crosstool.pb
@@ -18,11 +18,6 @@
 }
 
 default_toolchain {
-  cpu: "web"
-  toolchain_identifier: "emscripten_toolchain"
-}
-
-default_toolchain {
   cpu: "armhf-debian"
   toolchain_identifier: "clang_linux_armhf"
 }
@@ -322,166 +317,6 @@
 }
 
 toolchain {
-    # This toolchain was initially sourced from https://github.com/ribrdb/rules_emscripten
-    toolchain_identifier: "emscripten_toolchain"
-    host_system_name: "web"
-    target_system_name: "emscripten-unknown-emscripten"
-    target_cpu: "web"
-    target_libc: "unknown"
-    compiler: "emscripten"
-    abi_version: "unknown"
-    abi_libc_version: "unknown"
-    tool_path {
-      name: "gcc"
-      path: "emscripten/emcc.sh"
-    }
-    tool_path {
-        name: "ld"
-        path: "emscripten/emcc.sh"
-    }
-    tool_path {
-        name: "ar"
-        path: "emscripten/emar.sh"
-    }
-    tool_path {
-        name: "cpp"
-        path: "/bin/false"
-    }
-    tool_path {
-        name: "gcov"
-        path: "/bin/false"
-    }
-    tool_path {
-        name: "nm"
-        path: "/bin/false"
-    }
-    tool_path {
-        name: "objdump"
-        path: "/bin/false"
-    }
-    tool_path {
-      name: "objcopy"
-      path: "/bin/false"
-    }
-    tool_path {
-        name: "strip"
-        path: "/bin/false"
-    }
-
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/libcxx"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/lib/libcxxabi/include"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/compat"
-    compiler_flag: "-isystem"
-    compiler_flag: "external/emscripten_toolchain/system/include"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/include"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/SSE"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/include/libc"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/lib/libc/musl/arch/emscripten"
-    unfiltered_cxx_flag: "-isystem"
-    unfiltered_cxx_flag: "external/emscripten_toolchain/system/local/include"
-
-    # Turn off exceptions since emscripten has issues catching them
-    compiler_flag: "-fno-exceptions"
-
-    unfiltered_cxx_flag: "-no-canonical-prefixes"
-    linker_flag: "-no-canonical-prefixes"
-
-    # Make C++ compilation deterministic. Use linkstamping instead of these
-    # compiler symbols.
-    unfiltered_cxx_flag: "-Wno-builtin-macro-redefined"
-    unfiltered_cxx_flag: "-D__DATE__=\"redacted\""
-    unfiltered_cxx_flag: "-D__TIMESTAMP__=\"redacted\""
-    unfiltered_cxx_flag: "-D__TIME__=\"redacted\""
-    # Enable coloring even if there's no attached terminal. Bazel removes the
-    # escape sequences if --nocolor is specified.
-    compiler_flag: "-fdiagnostics-color=always"
-    compiler_flag: "-Wall"
-    compiler_flag: "-Werror"
-
-    compiler_flag: "-ffunction-sections"
-    compiler_flag: "-fdata-sections"
-
-  feature {
-    name: "opt"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=0"
-        flag: "-O2"
-        flag: "--closure"
-        flag: "1"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "dbg"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=1"
-      }
-      flag_group {
-        flag: "-fno-omit-frame-pointer"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "fastbuild"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "c-compile"
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-DAOS_DEBUG=0"
-      }
-    }
-    implies: "all_modes"
-  }
-  feature {
-    name: "all_modes"
-    flag_set {
-      action: "preprocess-assemble"
-      action: "assemble"
-      action: "c-compile"
-      flag_group {
-        flag: "-std=gnu99"
-      }
-    }
-    flag_set {
-      action: "c++-compile"
-      action: "c++-header-parsing"
-      action: "c++-header-preprocessing"
-      action: "c++-module-compile"
-      flag_group {
-        flag: "-std=gnu++1y"
-      }
-    }
-  }
-}
-
-toolchain {
   toolchain_identifier: "roborio_linux"
   host_system_name: "roborio"
   target_system_name: "roborio"