Reland "Add deps to enable performing the cmake build of dawn_node on CQ"

This is a reland of ecbdd8fbe7fe7178a019e484da628dd72b7c8bbb
It prefixes the DEPS variables with "dawn_". Because they are globals,
they may collide with other variables in other projects.

Original change's description:
> Add deps to enable performing the cmake build of dawn_node on CQ
>
> (and the cmake build of Dawn in general)
>
> Bug: dawn:688
> Change-Id: If7c037a03d237372739aed1f5dc78bffb7975a24
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65603
> Reviewed-by: Ben Clayton <bclayton@google.com>
> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
> Commit-Queue: Austin Eng <enga@chromium.org>

Bug: dawn:688
Change-Id: I81ec3d5298efea54b1417ff58569cf1c615ea372
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/66400
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff76121..8767f8e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -137,6 +137,7 @@
 set_if_not_defined(NODE_ADDON_API_DIR "${DAWN_THIRD_PARTY_DIR}/node-addon-api" "Directory in which to find node-addon-api")
 set_if_not_defined(NODE_API_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/node-api-headers" "Directory in which to find node-api-headers")
 set_if_not_defined(WEBGPU_IDL_PATH "${DAWN_THIRD_PARTY_DIR}/gpuweb/webgpu.idl" "Path to the webgpu.idl definition file")
+set_if_not_defined(GO_EXECUTABLE "go" "Golang executable for running the IDL generator")
 
 # Much of the backend code is shared among desktop OpenGL and OpenGL ES
 if (${DAWN_ENABLE_DESKTOP_GL} OR ${DAWN_ENABLE_OPENGLES})
diff --git a/DEPS b/DEPS
index 0ef703c..5cb32df 100644
--- a/DEPS
+++ b/DEPS
@@ -10,6 +10,9 @@
 
   'dawn_standalone': True,
   'dawn_node': False, # Also fetches dependencies required for building NodeJS bindings.
+  'dawn_cmake_version': 'version:3.13.5',
+  'dawn_cmake_win32_sha1': 'b106d66bcdc8a71ea2cdf5446091327bfdb1bcd7',
+  'dawn_go_version': 'version:1.16',
 }
 
 deps = {
@@ -155,6 +158,24 @@
     'url': '{github_git}/gpuweb/gpuweb.git@67edc187f5305a72456663c34d51153601b79f3b',
     'condition': 'dawn_node',
   },
+
+  'tools/golang': {
+    'condition': 'dawn_node',
+    'packages': [{
+      'package': 'infra/3pp/tools/go/${{platform}}',
+      'version': Var('dawn_go_version'),
+    }],
+    'dep_type': 'cipd',
+  },
+
+  'tools/cmake': {
+    'condition': 'dawn_node and (host_os == "mac" or host_os == "linux")',
+    'packages': [{
+      'package': 'infra/3pp/tools/cmake/${{platform}}',
+      'version': Var('dawn_cmake_version'),
+    }],
+    'dep_type': 'cipd',
+  },
 }
 
 hooks = [
@@ -249,6 +270,30 @@
     'action': ['python', 'build/util/lastchange.py',
                '-o', 'build/util/LASTCHANGE'],
   },
+  # TODO(https://crbug.com/1180257): Use CIPD for CMake on Windows.
+  {
+    'name': 'cmake_win32',
+    'pattern': '.',
+    'condition': 'dawn_node and host_os == "win"',
+    'action': [ 'download_from_google_storage',
+                '--no_resume',
+                '--platform=win32',
+                '--no_auth',
+                '--bucket', 'chromium-tools',
+                Var('dawn_cmake_win32_sha1'),
+                '-o', 'tools/cmake-win32.zip'
+    ],
+  },
+  {
+    'name': 'cmake_win32_extract',
+    'pattern': '.',
+    'condition': 'dawn_node and host_os == "win"',
+    'action': [ 'python',
+                'scripts/extract.py',
+                'tools/cmake-win32.zip',
+                'tools/cmake-win32/',
+    ],
+  },
 ]
 
 recursedeps = [
diff --git a/scripts/extract.py b/scripts/extract.py
new file mode 100644
index 0000000..2999d98
--- /dev/null
+++ b/scripts/extract.py
@@ -0,0 +1,182 @@
+# Copyright (c) 2015, Google Inc.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+"""Extracts archives."""
+
+import hashlib
+import optparse
+import os
+import os.path
+import tarfile
+import shutil
+import sys
+import zipfile
+
+
+def CheckedJoin(output, path):
+    """
+  CheckedJoin returns os.path.join(output, path). It does sanity checks to
+  ensure the resulting path is under output, but shouldn't be used on untrusted
+  input.
+  """
+    path = os.path.normpath(path)
+    if os.path.isabs(path) or path.startswith('.'):
+        raise ValueError(path)
+    return os.path.join(output, path)
+
+
+class FileEntry(object):
+    def __init__(self, path, mode, fileobj):
+        self.path = path
+        self.mode = mode
+        self.fileobj = fileobj
+
+
+class SymlinkEntry(object):
+    def __init__(self, path, mode, target):
+        self.path = path
+        self.mode = mode
+        self.target = target
+
+
+def IterateZip(path):
+    """
+  IterateZip opens the zip file at path and returns a generator of entry objects
+  for each file in it.
+  """
+    with zipfile.ZipFile(path, 'r') as zip_file:
+        for info in zip_file.infolist():
+            if info.filename.endswith('/'):
+                continue
+            yield FileEntry(info.filename, None, zip_file.open(info))
+
+
+def IterateTar(path, compression):
+    """
+  IterateTar opens the tar.gz or tar.bz2 file at path and returns a generator of
+  entry objects for each file in it.
+  """
+    with tarfile.open(path, 'r:' + compression) as tar_file:
+        for info in tar_file:
+            if info.isdir():
+                pass
+            elif info.issym():
+                yield SymlinkEntry(info.name, None, info.linkname)
+            elif info.isfile():
+                yield FileEntry(info.name, info.mode,
+                                tar_file.extractfile(info))
+            else:
+                raise ValueError('Unknown entry type "%s"' % (info.name, ))
+
+
+def main(args):
+    parser = optparse.OptionParser(usage='Usage: %prog ARCHIVE OUTPUT')
+    parser.add_option('--no-prefix',
+                      dest='no_prefix',
+                      action='store_true',
+                      help='Do not remove a prefix from paths in the archive.')
+    options, args = parser.parse_args(args)
+
+    if len(args) != 2:
+        parser.print_help()
+        return 1
+
+    archive, output = args
+
+    if not os.path.exists(archive):
+        # Skip archives that weren't downloaded.
+        return 0
+
+    with open(archive) as f:
+        sha256 = hashlib.sha256()
+        while True:
+            chunk = f.read(1024 * 1024)
+            if not chunk:
+                break
+            sha256.update(chunk)
+        digest = sha256.hexdigest()
+
+    stamp_path = os.path.join(output, ".dawn_archive_digest")
+    if os.path.exists(stamp_path):
+        with open(stamp_path) as f:
+            if f.read().strip() == digest:
+                print "Already up-to-date."
+                return 0
+
+    if archive.endswith('.zip'):
+        entries = IterateZip(archive)
+    elif archive.endswith('.tar.gz'):
+        entries = IterateTar(archive, 'gz')
+    elif archive.endswith('.tar.bz2'):
+        entries = IterateTar(archive, 'bz2')
+    else:
+        raise ValueError(archive)
+
+    try:
+        if os.path.exists(output):
+            print "Removing %s" % (output, )
+            shutil.rmtree(output)
+
+        print "Extracting %s to %s" % (archive, output)
+        prefix = None
+        num_extracted = 0
+        for entry in entries:
+            # Even on Windows, zip files must always use forward slashes.
+            if '\\' in entry.path or entry.path.startswith('/'):
+                raise ValueError(entry.path)
+
+            if not options.no_prefix:
+                new_prefix, rest = entry.path.split('/', 1)
+
+                # Ensure the archive is consistent.
+                if prefix is None:
+                    prefix = new_prefix
+                if prefix != new_prefix:
+                    raise ValueError((prefix, new_prefix))
+            else:
+                rest = entry.path
+
+            # Extract the file into the output directory.
+            fixed_path = CheckedJoin(output, rest)
+            if not os.path.isdir(os.path.dirname(fixed_path)):
+                os.makedirs(os.path.dirname(fixed_path))
+            if isinstance(entry, FileEntry):
+                with open(fixed_path, 'wb') as out:
+                    shutil.copyfileobj(entry.fileobj, out)
+            elif isinstance(entry, SymlinkEntry):
+                os.symlink(entry.target, fixed_path)
+            else:
+                raise TypeError('unknown entry type')
+
+            # Fix up permissions if needbe.
+            # TODO(davidben): To be extra tidy, this should only track the execute bit
+            # as in git.
+            if entry.mode is not None:
+                os.chmod(fixed_path, entry.mode)
+
+            # Print every 100 files, so bots do not time out on large archives.
+            num_extracted += 1
+            if num_extracted % 100 == 0:
+                print "Extracted %d files..." % (num_extracted, )
+    finally:
+        entries.close()
+
+    with open(stamp_path, 'w') as f:
+        f.write(digest)
+
+    print "Done. Extracted %d files." % (num_extracted, )
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/src/dawn_node/CMakeLists.txt b/src/dawn_node/CMakeLists.txt
index c8ac7af..0cdeaa2 100644
--- a/src/dawn_node/CMakeLists.txt
+++ b/src/dawn_node/CMakeLists.txt
@@ -41,7 +41,7 @@
         message(FATAL_ERROR "idlgen() missing IDLS argument(s)")
     endif()
     add_custom_command(
-        COMMAND "go" "run" "main.go"
+        COMMAND ${GO_EXECUTABLE} "run" "main.go"
                 "--template" "${IDLGEN_TEMPLATE}"
                 "--output"   "${IDLGEN_OUTPUT}"
                 ${IDLGEN_IDLS}