Add CanvasKit build

Building CanvasKit uses very similar logic to PathKit, so there
was a fair amount of copy/paste/customize.

Fixes the name of skia.js/wasm -> canvaskit.js/wasm and
adds a package.json to formally track versions.

Also move PathKit helper scripts to align better.

Docs-Preview: https://skia.org/?cl=160463
Bug: skia:
Change-Id: Ie75b30592dcc4d520dca41f6f5579006aaa8849b
Reviewed-on: https://skia-review.googlesource.com/c/160463
Reviewed-by: Eric Boren <borenet@google.com>
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
index dc76224..65764fb 100644
--- a/experimental/canvaskit/Makefile
+++ b/experimental/canvaskit/Makefile
@@ -7,15 +7,15 @@
 	# Does an incremental build where possible.
 	./compile.sh
 	mkdir -p ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/skia.js   ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin
 
 debug:
 	# Does an incremental build where possible.
 	./compile.sh debug
 	mkdir -p ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/skia.js   ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin
 
 local-example:
 	rm -rf node_modules/canvas-kit
diff --git a/experimental/canvaskit/canvas-kit/example.html b/experimental/canvaskit/canvas-kit/example.html
index 85aeaa7..8b2fb51 100644
--- a/experimental/canvaskit/canvas-kit/example.html
+++ b/experimental/canvaskit/canvas-kit/example.html
@@ -30,7 +30,7 @@
 <!-- Doesn't work yet. -->
 <button id=lego_btn>Take a picture of the legos</button>
 
-<script type="text/javascript" src="/node_modules/canvas-kit/bin/skia.js"></script>
+<script type="text/javascript" src="/node_modules/canvas-kit/bin/canvaskit.js"></script>
 
 <script type="text/javascript" charset="utf-8">
 
diff --git a/experimental/canvaskit/canvas-kit/package.json b/experimental/canvaskit/canvas-kit/package.json
new file mode 100644
index 0000000..76dd8e9
--- /dev/null
+++ b/experimental/canvaskit/canvas-kit/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "canvaskit-wasm",
+  "version": "0.1.0",
+  "description": "A WASM version of Skia's Canvas API",
+  "main": "bin/canvaskit.js",
+  "homepage": "https://github.com/google/skia/tree/master/experimental/canvaskit",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "license": "Apache-2.0"
+}
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index 7dabb10..f309e20 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -14,11 +14,13 @@
 fi
 
 BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
-
+mkdir -p $BUILD_DIR
 # Navigate to SKIA_HOME from where this file is located.
 pushd $BASE_DIR/../..
 
 source $EMSDK/emsdk_env.sh
+EMCC=`which emcc`
+EMCXX=`which em++`
 
 RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE"
 EXTRA_CFLAGS="\"-DSK_RELEASE\""
@@ -28,12 +30,18 @@
   RELEASE_CONF="-O0 --js-opts 0 -s SAFE_HEAP=1 -s ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG"
 fi
 
+# Turn off exiting while we check for ninja (which may not be on PATH)
+set +e
+NINJA=`which ninja`
+if [[ -z $NINJA ]]; then
+  git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
+  NINJA=$BUILD_DIR/depot_tools/ninja
+fi
+# Re-enable error checking
+set -e
 
 echo "Compiling bitcode"
 
-EMCC=`which emcc`
-EMCXX=`which em++`
-
 # Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
 ./bin/gn gen ${BUILD_DIR} \
   --args="cc=\"${EMCC}\" \
@@ -68,7 +76,7 @@
   skia_enable_fontmgr_empty=false \
   skia_enable_pdf=false"
 
-ninja -C ${BUILD_DIR} libskia.a
+${NINJA} -C ${BUILD_DIR} libskia.a
 
 export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
 
@@ -132,4 +140,4 @@
     -s USE_FREETYPE=1 \
     -s USE_LIBPNG=1 \
     -s WASM=1 \
-    -o $BUILD_DIR/skia.js
+    -o $BUILD_DIR/canvaskit.js
diff --git a/experimental/canvaskit/package.json b/experimental/canvaskit/package.json
new file mode 100644
index 0000000..9986143
--- /dev/null
+++ b/experimental/canvaskit/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "canvaskit-local",
+  "version": "0.0.0",
+  "description": "private",
+  "private": true,
+  "main": "index.js",
+  "dependencies": {
+  },
+  "devDependencies": {
+    "is-docker": "^1.1.0",
+    "jasmine-core": "^3.1.0",
+    "karma": "^2.0.5",
+    "karma-chrome-launcher": "^2.2.0",
+    "karma-jasmine": "^1.1.2",
+    "requirejs": "^2.3.5"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index e2705f6..a9c3452 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -312,7 +312,7 @@
 		Dependencies: []string{BUNDLE_RECIPES_NAME},
 		Dimensions:   dimensions,
 		EnvPrefixes: map[string][]string{
-			"PATH":                    []string{"cipd_bin_packages", "cipd_bin_packages/bin"},
+			"PATH": []string{"cipd_bin_packages", "cipd_bin_packages/bin"},
 			"VPYTHON_VIRTUALENV_ROOT": []string{"cache/vpython"},
 		},
 		ExtraTags: map[string]string{
@@ -401,6 +401,9 @@
 		if strings.Contains(jobName, "PathKit") {
 			ec = []string{"PathKit"}
 		}
+		if strings.Contains(jobName, "CanvasKit") {
+			ec = []string{"CanvasKit"}
+		}
 		if len(ec) > 0 {
 			jobNameMap["extra_config"] = strings.Join(ec, "_")
 		}
@@ -608,7 +611,7 @@
 	} else {
 		d["gpu"] = "none"
 		if d["os"] == DEFAULT_OS_DEBIAN {
-			if strings.Contains(parts["extra_config"], "PathKit") {
+			if strings.Contains(parts["extra_config"], "PathKit") || strings.Contains(parts["extra_config"], "CanvasKit") {
 				// The build isn't really parallelized for pathkit, so
 				// the bulky machines don't buy us much. All we really need is
 				// docker, which was manually installed on the MEDIUM and LARGE
@@ -1077,6 +1080,8 @@
 		recipe = "compute_test"
 	} else if strings.Contains(name, "PathKit") {
 		recipe = "test_pathkit"
+	} else if strings.Contains(name, "CanvasKit") {
+		recipe = "test_canvaskit"
 	} else if strings.Contains(name, "LottieWeb") {
 		recipe = "test_lottie_web"
 	}
@@ -1088,7 +1093,7 @@
 		extraProps["internal_hardware_label"] = strconv.Itoa(*iid)
 	}
 	isolate := "test_skia_bundled.isolate"
-	if strings.Contains(name, "PathKit") || strings.Contains(name, "LottieWeb") || strings.Contains(name, "Emulator") {
+	if strings.Contains(name, "CanvasKit") || strings.Contains(name, "Emulator") || strings.Contains(name, "LottieWeb") || strings.Contains(name, "PathKit") {
 		isolate = "swarm_recipe.isolate"
 	}
 	task := kitchenTask(name, recipe, isolate, "", swarmDimensions(parts), extraProps, OUTPUT_TEST)
@@ -1363,7 +1368,7 @@
 	if strings.Contains(name, "ProcDump") {
 		pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("procdump_win"))
 	}
-	if strings.Contains(name, "PathKit") || strings.Contains(name, "LottieWeb") {
+	if strings.Contains(name, "CanvasKit") || strings.Contains(name, "LottieWeb") || strings.Contains(name, "PathKit") {
 		// Docker-based tests that don't need the standard CIPD assets
 		pkgs = []*specs.CipdPackage{}
 	}
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index 118eca1..2f5e297 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -56,7 +56,9 @@
   "Build-Debian9-Clang-x86_64-Release-Vulkan",
   "Build-Debian9-EMCC-asmjs-Debug-PathKit",
   "Build-Debian9-EMCC-asmjs-Release-PathKit",
+  "Build-Debian9-EMCC-wasm-Debug-CanvasKit",
   "Build-Debian9-EMCC-wasm-Debug-PathKit",
+  "Build-Debian9-EMCC-wasm-Release-CanvasKit",
   "Build-Debian9-EMCC-wasm-Release-PathKit",
   "Build-Debian9-GCC-loongson3a-Debug",
   "Build-Debian9-GCC-loongson3a-Release",
@@ -108,6 +110,7 @@
   "Build-Win-MSVC-x86_64-Release",
   "Build-Win-MSVC-x86_64-Release-Vulkan",
   "BuildStats-Debian9-EMCC-asmjs-Release-PathKit",
+  "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit",
   "BuildStats-Debian9-EMCC-wasm-Release-PathKit",
   "Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All",
   "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
diff --git a/infra/bots/recipe_modules/build/api.py b/infra/bots/recipe_modules/build/api.py
index 1cfc79e..5da7606 100644
--- a/infra/bots/recipe_modules/build/api.py
+++ b/infra/bots/recipe_modules/build/api.py
@@ -9,6 +9,7 @@
 from recipe_engine import recipe_api
 
 from . import android
+from . import canvaskit
 from . import chromebook
 from . import chromecast
 from . import default
@@ -37,8 +38,12 @@
       self.compile_fn = flutter.compile_fn
       self.copy_fn = flutter.copy_extra_build_products
     elif 'EMCC' in b:
-      self.compile_fn = pathkit.compile_fn
-      self.copy_fn = pathkit.copy_extra_build_products
+      if 'PathKit' in b:
+        self.compile_fn = pathkit.compile_fn
+        self.copy_fn = pathkit.copy_extra_build_products
+      else:
+        self.compile_fn = canvaskit.compile_fn
+        self.copy_fn = canvaskit.copy_extra_build_products
     else:
       self.compile_fn = default.compile_fn
       self.copy_fn = default.copy_extra_build_products
diff --git a/infra/bots/recipe_modules/build/canvaskit.py b/infra/bots/recipe_modules/build/canvaskit.py
new file mode 100644
index 0000000..12c54d8
--- /dev/null
+++ b/infra/bots/recipe_modules/build/canvaskit.py
@@ -0,0 +1,86 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.6_jre'
+INNER_BUILD_SCRIPT = '/SRC/skia/infra/canvaskit/build_canvaskit.sh'
+
+BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM = [
+  'canvaskit.*'
+]
+
+
+def compile_fn(api, checkout_root, _ignore):
+  out_dir = api.vars.cache_dir.join('docker', 'canvaskit')
+  configuration = api.vars.builder_cfg.get('configuration', '')
+
+  # We want to make sure the directories exist and were created by chrome-bot,
+  # because if that isn't the case, docker will make them and they will be
+  # owned by root, which causes mysterious failures. To mitigate this risk
+  # further, we don't use the same out_dir as everyone else (thus the _ignore)
+  # param. Instead, we use a "wasm" subdirectory in the "docker" named_cache.
+  api.file.ensure_directory('mkdirs out_dir', out_dir, mode=0777)
+
+  # This uses the emscriptem sdk docker image and says "run the
+  # build_pathkit.sh helper script in there". Additionally, it binds two
+  # folders: the Skia checkout to /SRC and the output directory to /OUT
+  # The called helper script will make the compile happen and put the
+  # output in the right spot.  The neat thing is that since the Skia checkout
+  # (and, by extension, the build script) is not a part of the image, but
+  # bound in at runtime, we don't have to re-build the image, except when the
+  # toolchain changes.
+  # Of note, the wasm build doesn't re-use any intermediate steps from the
+  # previous builds, so it's essentially a build from scratch every time.
+  cmd = ['docker', 'run', '--rm', '-v', '%s:/SRC' % checkout_root,
+         '-v', '%s:/OUT' % out_dir,
+         DOCKER_IMAGE, INNER_BUILD_SCRIPT]
+  if configuration == 'Debug':
+    cmd.append('debug') # It defaults to Release
+  api.run(
+    api.step,
+    'Build CanvasKit with Docker',
+    cmd=cmd)
+
+
+def copy_extra_build_products(api, _ignore, dst):
+  out_dir = api.vars.cache_dir.join('docker', 'canvaskit')
+  # We don't use the normal copy_build_products because it uses
+  # shutil.move, which attempts to delete the previous file, which
+  # doesn't work because the docker created outputs are read-only and
+  # owned by root (aka only docker images). It's likely safe to change
+  # the shutil.move in the original script to a non-deleting thing
+  # (like copy or copyfile), but there's some subtle behavior differences
+  # especially with directories, that kjlubick felt it best not to risk it.
+  api.python.inline(
+      name='copy wasm output',
+      program='''import errno
+import glob
+import os
+import shutil
+import sys
+
+src = sys.argv[1]
+dst = sys.argv[2]
+build_products_whitelist = %s
+
+try:
+  os.makedirs(dst)
+except OSError as e:
+  if e.errno != errno.EEXIST:
+    raise
+
+for pattern in build_products_whitelist:
+  path = os.path.join(src, pattern)
+  for f in glob.glob(path):
+    dst_path = os.path.join(dst, os.path.relpath(f, src))
+    if not os.path.isdir(os.path.dirname(dst_path)):
+      os.makedirs(os.path.dirname(dst_path))
+    print 'Copying build product %%s to %%s' %% (f, dst_path)
+    # Because Docker usually has some strange permissions (like root
+    # ownership), we'd rather not keep those around. copyfile doesn't
+    # keep the metadata around, so that helps us.
+    shutil.copyfile(f, dst_path)
+''' % str(BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM),
+      args=[out_dir, dst],
+      infra_step=True)
+
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
index cf0a645..6bae6c2 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Debug-PathKit.json
@@ -9,7 +9,7 @@
       "ensure-directory",
       "--mode",
       "0777",
-      "[START_DIR]/cache/docker/wasm"
+      "[START_DIR]/cache/docker/pathkit"
     ],
     "infra_step": true,
     "name": "mkdirs out_dir"
@@ -22,9 +22,9 @@
       "-v",
       "[START_DIR]/cache/work:/SRC",
       "-v",
-      "[START_DIR]/cache/docker/wasm:/OUT",
+      "[START_DIR]/cache/docker/pathkit:/OUT",
       "gcr.io/skia-public/emsdk-release:1.38.6_jre",
-      "/SRC/skia/infra/pathkit/docker/build_pathkit.sh",
+      "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "debug",
       "asm.js"
     ],
@@ -77,7 +77,7 @@
       "python",
       "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['pathkit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
-      "[START_DIR]/cache/docker/wasm",
+      "[START_DIR]/cache/docker/pathkit",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
index 3b50425..9c9845f 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-asmjs-Release-PathKit.json
@@ -9,7 +9,7 @@
       "ensure-directory",
       "--mode",
       "0777",
-      "[START_DIR]/cache/docker/wasm"
+      "[START_DIR]/cache/docker/pathkit"
     ],
     "infra_step": true,
     "name": "mkdirs out_dir"
@@ -22,9 +22,9 @@
       "-v",
       "[START_DIR]/cache/work:/SRC",
       "-v",
-      "[START_DIR]/cache/docker/wasm:/OUT",
+      "[START_DIR]/cache/docker/pathkit:/OUT",
       "gcr.io/skia-public/emsdk-release:1.38.6_jre",
-      "/SRC/skia/infra/pathkit/docker/build_pathkit.sh",
+      "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "asm.js"
     ],
     "env": {
@@ -76,7 +76,7 @@
       "python",
       "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['pathkit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
-      "[START_DIR]/cache/docker/wasm",
+      "[START_DIR]/cache/docker/pathkit",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json
new file mode 100644
index 0000000..1c923d3
--- /dev/null
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-CanvasKit.json
@@ -0,0 +1,120 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cache/docker/canvaskit"
+    ],
+    "infra_step": true,
+    "name": "mkdirs out_dir"
+  },
+  {
+    "cmd": [
+      "docker",
+      "run",
+      "--rm",
+      "-v",
+      "[START_DIR]/cache/work:/SRC",
+      "-v",
+      "[START_DIR]/cache/docker/canvaskit:/OUT",
+      "gcr.io/skia-public/emsdk-release:1.38.6_jre",
+      "/SRC/skia/infra/canvaskit/build_canvaskit.sh",
+      "debug"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "Build CanvasKit with Docker"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Debug-CanvasKit/Debug",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
+    ],
+    "infra_step": true,
+    "name": "copy build products",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.move(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['canvaskit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
+      "[START_DIR]/cache/docker/canvaskit",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
+    ],
+    "infra_step": true,
+    "name": "copy wasm output",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['canvaskit.*']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # Because Docker usually has some strange permissions (like root@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # ownership), we'd rather not keep those around. copyfile doesn't@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # keep the metadata around, so that helps us.@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.copyfile(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
index 5519328..71a407f 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json
@@ -9,7 +9,7 @@
       "ensure-directory",
       "--mode",
       "0777",
-      "[START_DIR]/cache/docker/wasm"
+      "[START_DIR]/cache/docker/pathkit"
     ],
     "infra_step": true,
     "name": "mkdirs out_dir"
@@ -22,9 +22,9 @@
       "-v",
       "[START_DIR]/cache/work:/SRC",
       "-v",
-      "[START_DIR]/cache/docker/wasm:/OUT",
+      "[START_DIR]/cache/docker/pathkit:/OUT",
       "gcr.io/skia-public/emsdk-release:1.38.6_jre",
-      "/SRC/skia/infra/pathkit/docker/build_pathkit.sh",
+      "/SRC/skia/infra/pathkit/build_pathkit.sh",
       "debug"
     ],
     "env": {
@@ -76,7 +76,7 @@
       "python",
       "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['pathkit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
-      "[START_DIR]/cache/docker/wasm",
+      "[START_DIR]/cache/docker/pathkit",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
     ],
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json
new file mode 100644
index 0000000..16da363
--- /dev/null
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json
@@ -0,0 +1,119 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cache/docker/canvaskit"
+    ],
+    "infra_step": true,
+    "name": "mkdirs out_dir"
+  },
+  {
+    "cmd": [
+      "docker",
+      "run",
+      "--rm",
+      "-v",
+      "[START_DIR]/cache/work:/SRC",
+      "-v",
+      "[START_DIR]/cache/docker/canvaskit:/OUT",
+      "gcr.io/skia-public/emsdk-release:1.38.6_jre",
+      "/SRC/skia/infra/canvaskit/build_canvaskit.sh"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "Build CanvasKit with Docker"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Release-CanvasKit/Release",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
+    ],
+    "infra_step": true,
+    "name": "copy build products",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.move(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['canvaskit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
+      "[START_DIR]/cache/docker/canvaskit",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
+    ],
+    "infra_step": true,
+    "name": "copy wasm output",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['canvaskit.*']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # Because Docker usually has some strange permissions (like root@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # ownership), we'd rather not keep those around. copyfile doesn't@@@",
+      "@@@STEP_LOG_LINE@python.inline@    # keep the metadata around, so that helps us.@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.copyfile(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
index 88bd5bc..64c64c6 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json
@@ -9,7 +9,7 @@
       "ensure-directory",
       "--mode",
       "0777",
-      "[START_DIR]/cache/docker/wasm"
+      "[START_DIR]/cache/docker/pathkit"
     ],
     "infra_step": true,
     "name": "mkdirs out_dir"
@@ -22,9 +22,9 @@
       "-v",
       "[START_DIR]/cache/work:/SRC",
       "-v",
-      "[START_DIR]/cache/docker/wasm:/OUT",
+      "[START_DIR]/cache/docker/pathkit:/OUT",
       "gcr.io/skia-public/emsdk-release:1.38.6_jre",
-      "/SRC/skia/infra/pathkit/docker/build_pathkit.sh"
+      "/SRC/skia/infra/pathkit/build_pathkit.sh"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
@@ -75,7 +75,7 @@
       "python",
       "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['pathkit.*']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    # Because Docker usually has some strange permissions (like root\n    # ownership), we'd rather not keep those around. copyfile doesn't\n    # keep the metadata around, so that helps us.\n    shutil.copyfile(f, dst_path)\n",
-      "[START_DIR]/cache/docker/wasm",
+      "[START_DIR]/cache/docker/pathkit",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/build/examples/full.py b/infra/bots/recipe_modules/build/examples/full.py
index be60b6b..fd3b7d2 100644
--- a/infra/bots/recipe_modules/build/examples/full.py
+++ b/infra/bots/recipe_modules/build/examples/full.py
@@ -44,7 +44,9 @@
   'Build-Debian9-Clang-x86_64-Release-Vulkan',
   'Build-Debian9-EMCC-asmjs-Debug-PathKit',
   'Build-Debian9-EMCC-asmjs-Release-PathKit',
+  'Build-Debian9-EMCC-wasm-Debug-CanvasKit',
   'Build-Debian9-EMCC-wasm-Debug-PathKit',
+  'Build-Debian9-EMCC-wasm-Release-CanvasKit',
   'Build-Debian9-EMCC-wasm-Release-PathKit',
   'Build-Debian9-GCC-arm-Release-Chromecast',
   'Build-Debian9-GCC-loongson3a-Release',
diff --git a/infra/bots/recipe_modules/build/pathkit.py b/infra/bots/recipe_modules/build/pathkit.py
index d14c3fe..ab89e36 100644
--- a/infra/bots/recipe_modules/build/pathkit.py
+++ b/infra/bots/recipe_modules/build/pathkit.py
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.6_jre'
-INNER_BUILD_SCRIPT = '/SRC/skia/infra/pathkit/docker/build_pathkit.sh'
+INNER_BUILD_SCRIPT = '/SRC/skia/infra/pathkit/build_pathkit.sh'
 
 BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM = [
   'pathkit.*'
@@ -11,7 +11,7 @@
 
 
 def compile_fn(api, checkout_root, _ignore):
-  out_dir = api.vars.cache_dir.join('docker', 'wasm')
+  out_dir = api.vars.cache_dir.join('docker', 'pathkit')
   configuration = api.vars.builder_cfg.get('configuration', '')
   target_arch   = api.vars.builder_cfg.get('target_arch',   '')
 
@@ -19,12 +19,12 @@
   # because if that isn't the case, docker will make them and they will be
   # owned by root, which causes mysterious failures. To mitigate this risk
   # further, we don't use the same out_dir as everyone else (thus the _ignore)
-  # param. Instead, we use a "wasm" subdirectory in the "docker" named_cache.
+  # param. Instead, we use a "pathkit" subdirectory in the "docker" named_cache.
   api.file.ensure_directory('mkdirs out_dir', out_dir, mode=0777)
 
   # This uses the emscriptem sdk docker image and says "run the
   # build_pathkit.sh helper script in there". Additionally, it binds two
-  # folders: the skia checkout to /SRC and the output directory to /OUT
+  # folders: the Skia checkout to /SRC and the output directory to /OUT
   # The called helper script will make the compile happen and put the
   # output in the right spot.  The neat thing is that since the Skia checkout
   # (and, by extension, the build script) is not a part of the image, but
@@ -36,7 +36,7 @@
          '-v', '%s:/OUT' % out_dir,
          DOCKER_IMAGE, INNER_BUILD_SCRIPT]
   if configuration == 'Debug':
-    cmd.append('debug') # It defaults to Relesae
+    cmd.append('debug') # It defaults to Release
   if target_arch == 'asmjs':
     cmd.append('asm.js') # It defaults to WASM
   api.run(
@@ -46,7 +46,7 @@
 
 
 def copy_extra_build_products(api, _ignore, dst):
-  out_dir = api.vars.cache_dir.join('docker', 'wasm')
+  out_dir = api.vars.cache_dir.join('docker', 'pathkit')
   # We don't use the normal copy_build_products because it uses
   # shutil.move, which attempts to delete the previous file, which
   # doesn't work because the docker created outputs are read-only and
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
index 97cdbf2..9d9510c 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
@@ -203,7 +203,7 @@
       "-e",
       "ASM_JS=1",
       "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v4",
-      "/SRC/skia/infra/pathkit/docker/test_pathkit.sh",
+      "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit",
       "--git_hash",
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
index 54887f1..b3416b4 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
@@ -203,7 +203,7 @@
       "-e",
       "ASM_JS=1",
       "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v4",
-      "/SRC/skia/infra/pathkit/docker/test_pathkit.sh",
+      "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
       "--git_hash",
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
index 2fbd39f..de0719f 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
@@ -201,7 +201,7 @@
       "-v",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v4",
-      "/SRC/skia/infra/pathkit/docker/test_pathkit.sh",
+      "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
       "--git_hash",
diff --git a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
index 9020dcd..ce2c60a 100644
--- a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
@@ -203,7 +203,7 @@
       "-v",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v4",
-      "/SRC/skia/infra/pathkit/docker/test_pathkit.sh",
+      "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
       "--git_hash",
diff --git a/infra/bots/recipes/test_pathkit.py b/infra/bots/recipes/test_pathkit.py
index dc240a1..3ed789f 100644
--- a/infra/bots/recipes/test_pathkit.py
+++ b/infra/bots/recipes/test_pathkit.py
@@ -18,7 +18,7 @@
 
 
 DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v4'
-INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/docker/test_pathkit.sh'
+INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/test_pathkit.sh'
 
 
 def RunSteps(api):
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 76f0db1..e800391 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -286,11 +286,21 @@
         "Build-Debian9-EMCC-asmjs-Release-PathKit"
       ]
     },
+    "Build-Debian9-EMCC-wasm-Debug-CanvasKit": {
+      "tasks": [
+        "Build-Debian9-EMCC-wasm-Debug-CanvasKit"
+      ]
+    },
     "Build-Debian9-EMCC-wasm-Debug-PathKit": {
       "tasks": [
         "Build-Debian9-EMCC-wasm-Debug-PathKit"
       ]
     },
+    "Build-Debian9-EMCC-wasm-Release-CanvasKit": {
+      "tasks": [
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit"
+      ]
+    },
     "Build-Debian9-EMCC-wasm-Release-PathKit": {
       "tasks": [
         "Build-Debian9-EMCC-wasm-Release-PathKit"
@@ -549,6 +559,11 @@
         "Upload-BuildStats-Debian9-EMCC-asmjs-Release-PathKit"
       ]
     },
+    "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit": {
+      "tasks": [
+        "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit"
+      ]
+    },
     "BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
       "tasks": [
         "Upload-BuildStats-Debian9-EMCC-wasm-Release-PathKit"
@@ -10232,6 +10247,134 @@
       ],
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Build-Debian9-EMCC-wasm-Debug-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        },
+        {
+          "name": "docker",
+          "path": "cache/docker"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Build-Debian9-EMCC-wasm-Debug-PathKit": {
       "caches": [
         {
@@ -10360,6 +10503,134 @@
       ],
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Build-Debian9-EMCC-wasm-Release-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        },
+        {
+          "name": "docker",
+          "path": "cache/docker"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Build-Debian9-EMCC-wasm-Release-PathKit": {
       "caches": [
         {
@@ -17029,6 +17300,102 @@
         "perf"
       ]
     },
+    "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compute_buildstats",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "outputs": [
+        "perf"
+      ]
+    },
     "BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
       "caches": [
         {
@@ -68797,6 +69164,105 @@
       "isolate": "swarm_recipe.isolate",
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_buildstats_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Upload-BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
       "caches": [
         {
diff --git a/infra/canvaskit/build_canvaskit.sh b/infra/canvaskit/build_canvaskit.sh
new file mode 100755
index 0000000..71e5d06
--- /dev/null
+++ b/infra/canvaskit/build_canvaskit.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Copyright 2018 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This assumes it is being run inside a docker container of emsdk-base
+# and a Skia checkout has been mounted at /SRC and the output directory
+# is mounted at /OUT
+
+# For example:
+# docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/canvaskit:/OUT gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/canvaskit/build_canvaskit.sh
+
+set +e
+set -x
+# Clean out previous builds (ignoring any errors for things like folders)
+# (e.g. we don't want to delete /OUT/depot_tools/)
+rm -f /OUT/*
+set -e
+
+#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/canvaskit)
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+CANVASKIT_DIR=$BASE_DIR/../../experimental/canvaskit
+
+BUILD_DIR=/OUT $CANVASKIT_DIR/compile.sh $@
diff --git a/infra/pathkit/docker/build_pathkit.sh b/infra/pathkit/build_pathkit.sh
similarity index 72%
rename from infra/pathkit/docker/build_pathkit.sh
rename to infra/pathkit/build_pathkit.sh
index cae48c6..63a7b57 100755
--- a/infra/pathkit/docker/build_pathkit.sh
+++ b/infra/pathkit/build_pathkit.sh
@@ -9,13 +9,17 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/docker/build_pathkit.sh
+# docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/build_pathkit.sh
 
-#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/pathkit/docker)
+set +e
+set -x
+# Clean out previous builds (ignoring any errors for things like folders)
+# (e.g. we don't want to delete /OUT/depot_tools/)
+rm -f /OUT/*
+set -e
+
+#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/pathkit)
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-PATHKIT_DIR=$BASE_DIR/../../../modules/pathkit
-
-# Clean out previous builds
-rm /OUT/*
+PATHKIT_DIR=$BASE_DIR/../../modules/pathkit
 
 BUILD_DIR=/OUT $PATHKIT_DIR/compile.sh $@
diff --git a/infra/pathkit/docker/README.md b/infra/pathkit/docker/README.md
index 10cd775..afcb93f 100644
--- a/infra/pathkit/docker/README.md
+++ b/infra/pathkit/docker/README.md
@@ -26,7 +26,7 @@
     # Run bash in it to poke around and make sure things are properly installed
     docker run -it emsdk-release /bin/bash
     # Compile PathKit with the local image
-    docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT emsdk-base /SRC/infra/pathkit/docker/build_pathkit.sh
+    docker run -v $SKIA_ROOT:/SRC -v $SKIA_ROOT/out/dockerpathkit:/OUT emsdk-base /SRC/infra/pathkit/build_pathkit.sh
 
 karma-chrome-tests
 ------------------
@@ -91,4 +91,4 @@
     docker run -it --shm-size=2gb gold-karma-chrome-tests /bin/bash
     # Run the tests and collect Gold output with the local source repo
     mkdir -p -m 0777 /tmp/dockergold
-    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockergold:/OUT gold-karma-chrome-tests /SRC/infra/pathkit/docker/test_pathkit.sh
+    docker run --shm-size=2gb -v $SKIA_ROOT:/SRC -v /tmp/dockergold:/OUT gold-karma-chrome-tests /SRC/infra/pathkit/test_pathkit.sh
diff --git a/infra/pathkit/docker/test_pathkit.sh b/infra/pathkit/test_pathkit.sh
similarity index 92%
rename from infra/pathkit/docker/test_pathkit.sh
rename to infra/pathkit/test_pathkit.sh
index 2f87f6a..bfd0405 100755
--- a/infra/pathkit/docker/test_pathkit.sh
+++ b/infra/pathkit/test_pathkit.sh
@@ -9,13 +9,13 @@
 # is mounted at /OUT
 
 # For example:
-# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v1 /SRC/infra/pathkit/docker/test_pathkit.sh
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v1 /SRC/infra/pathkit/test_pathkit.sh
 
 set -ex
 
-#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/pathkit/docker)
+#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/pathkit)
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
-PATHKIT_DIR=$BASE_DIR/../../../modules/pathkit
+PATHKIT_DIR=$BASE_DIR/../../modules/pathkit
 
 # Start the aggregator in the background
 /opt/gold-aggregator $@ &
diff --git a/modules/pathkit/Makefile b/modules/pathkit/Makefile
index adce659..701fec8 100644
--- a/modules/pathkit/Makefile
+++ b/modules/pathkit/Makefile
@@ -57,12 +57,12 @@
 	mkdir -p $$SKIA_ROOT/out/dockerbuild
 
 	docker run --rm -v $$SKIA_ROOT:/SRC -v $$SKIA_ROOT/out/dockerbuild:/OUT \
-gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/docker/build_pathkit.sh
+gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/build_pathkit.sh
 	cp ../../out/dockerbuild/pathkit.js   ./npm-wasm/bin/test/pathkit.js
 	cp ../../out/dockerbuild/pathkit.wasm ./npm-wasm/bin/test/pathkit.wasm
 
 	docker run --rm -v $$SKIA_ROOT:/SRC -v $$SKIA_ROOT/out/dockerbuild:/OUT \
-gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/docker/build_pathkit.sh asm.js
+gcr.io/skia-public/emsdk-release:1.38.6_jre /SRC/infra/pathkit/build_pathkit.sh asm.js
 	cp ../../out/dockerbuild/pathkit.js     ./npm-asmjs/bin/test/pathkit.js
 	cp ../../out/dockerbuild/pathkit.js.mem ./npm-asmjs/bin/test/pathkit.js.mem
 
diff --git a/modules/pathkit/compile.sh b/modules/pathkit/compile.sh
index 8956b7b..26e6438 100755
--- a/modules/pathkit/compile.sh
+++ b/modules/pathkit/compile.sh
@@ -4,10 +4,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+set -ex
 
 BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
 HTML_SHELL=$BASE_DIR/shell.html
 BUILD_DIR=${BUILD_DIR:="out/pathkit"}
+mkdir -p $BUILD_DIR
 
 # This expects the environment variable EMSDK to be set
 if [[ ! -d $EMSDK ]]; then
@@ -67,20 +69,21 @@
 fi
 
 OUTPUT="-o $BUILD_DIR/pathkit.js"
+
 source $EMSDK/emsdk_env.sh
-NINJA=`which ninja`
 EMCC=`which emcc`
 EMCXX=`which em++`
 
 
-mkdir -p $BUILD_DIR
-
+# Turn off exiting while we check for ninja (which may not be on PATH)
+set +e
+NINJA=`which ninja`
 if [[ -z $NINJA ]]; then
   git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
   NINJA=$BUILD_DIR/depot_tools/ninja
 fi
-
-set -ex
+# Re-enable error checking
+set -e
 
 echo "Compiling bitcode"
 
@@ -119,7 +122,7 @@
 
 echo "Generating WASM"
 
-em++ $RELEASE_CONF -std=c++14 \
+${EMCXX} $RELEASE_CONF -std=c++14 \
 -Iinclude/config \
 -Iinclude/core \
 -Iinclude/effects \
diff --git a/site/user/modules/canvaskit.md b/site/user/modules/canvaskit.md
index 32bf626..be30cc2 100644
--- a/site/user/modules/canvaskit.md
+++ b/site/user/modules/canvaskit.md
@@ -96,13 +96,13 @@
   var locate_file = '';
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported!');
-    locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.0.3/bin/';
+    locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.1.0/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser.');
     document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
     return;
   }
-  s.src = locate_file + 'skia.js';
+  s.src = locate_file + 'canvaskit.js';
   s.onload = () => {
   var CanvasKit = null;
   var legoJSON = null;