|  | # Copyright 2023 The Dawn & Tint Authors | 
|  | # | 
|  | # Redistribution and use in source and binary forms, with or without | 
|  | # modification, are permitted provided that the following conditions are met: | 
|  | # | 
|  | # 1. Redistributions of source code must retain the above copyright notice, this | 
|  | #    list of conditions and the following disclaimer. | 
|  | # | 
|  | # 2. Redistributions in binary form must reproduce the above copyright notice, | 
|  | #    this list of conditions and the following disclaimer in the documentation | 
|  | #    and/or other materials provided with the distribution. | 
|  | # | 
|  | # 3. Neither the name of the copyright holder nor the names of its | 
|  | #    contributors may be used to endorse or promote products derived from | 
|  | #    this software without specific prior written permission. | 
|  | # | 
|  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
|  | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | """ | 
|  | Helper script to download Dawn's source dependencies without the need to | 
|  | install depot_tools by manually. This script implements a subset of | 
|  | `gclient sync`. | 
|  |  | 
|  | This helps embedders, for example through CMake, get all the sources with | 
|  | a single add_subdirectory call (or FetchContent) instead of more complex setups | 
|  |  | 
|  | Note that this script executes blindly the content of DEPS file, run it only on | 
|  | a project that you trust not to contain malicious DEPS files. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import sys | 
|  | import subprocess | 
|  | import argparse | 
|  | from pathlib import Path | 
|  |  | 
|  | parser = argparse.ArgumentParser( | 
|  | prog='fetch_dawn_dependencies', | 
|  | description=__doc__, | 
|  | ) | 
|  |  | 
|  | parser.add_argument('-d', | 
|  | '--directory', | 
|  | type=str, | 
|  | default="", | 
|  | help=""" | 
|  | Working directory, in which we read and apply DEPS files recusively. If not | 
|  | specified, the current working directory is used. | 
|  | """) | 
|  |  | 
|  | parser.add_argument('-g', | 
|  | '--git', | 
|  | type=str, | 
|  | default="git", | 
|  | help=""" | 
|  | Path to the git command used to. By default, git is retrieved from the PATH. | 
|  | You may also use this option to specify extra argument for all calls to git. | 
|  | """) | 
|  |  | 
|  | parser.add_argument('-s', | 
|  | '--shallow', | 
|  | action='store_true', | 
|  | default=True, | 
|  | help=""" | 
|  | Clone repositories without commit history (only getting data for the | 
|  | requested commit). | 
|  | NB: The git server hosting the dependencies must have turned on the | 
|  | `uploadpack.allowReachableSHA1InWant` option. | 
|  | NB2: git submodules may not work as expected (but they are not used by Dawn | 
|  | dependencies). | 
|  | """) | 
|  | parser.add_argument('-ns', | 
|  | '--no-shallow', | 
|  | action='store_false', | 
|  | dest='shallow', | 
|  | help="Deactivate shallow cloning.") | 
|  |  | 
|  | parser.add_argument('-t', | 
|  | '--use-test-deps', | 
|  | action='store_true', | 
|  | default=False, | 
|  | help=""" | 
|  | Deprecated: Test dependencies are now always included. | 
|  | """) | 
|  |  | 
|  |  | 
|  | def main(args): | 
|  | # The dependencies that we need to pull from the DEPS files. | 
|  | # Dependencies of dependencies are prefixed by their ancestors. | 
|  | required_submodules = [ | 
|  | 'third_party/abseil-cpp', | 
|  | 'third_party/dxc', | 
|  | 'third_party/dxheaders', | 
|  | 'third_party/glfw', | 
|  | 'third_party/jinja2', | 
|  | 'third_party/khronos/EGL-Registry', | 
|  | 'third_party/khronos/OpenGL-Registry', | 
|  | 'third_party/libprotobuf-mutator/src', | 
|  | 'third_party/protobuf', | 
|  | 'third_party/markupsafe', | 
|  | 'third_party/glslang/src', | 
|  | 'third_party/google_benchmark/src', | 
|  | 'third_party/googletest', | 
|  | 'third_party/spirv-headers/src', | 
|  | 'third_party/spirv-tools/src', | 
|  | 'third_party/vulkan-headers/src', | 
|  | 'third_party/vulkan-loader/src', | 
|  | 'third_party/vulkan-utility-libraries/src', | 
|  | ] | 
|  |  | 
|  | root_dir = Path(args.directory).resolve() | 
|  |  | 
|  | process_dir(args, root_dir, required_submodules) | 
|  |  | 
|  |  | 
|  | def process_dir(args, dir_path, required_submodules): | 
|  | """ | 
|  | Install dependencies for the provided directory by processing the DEPS file | 
|  | that it contains (if it exists). | 
|  | Recursively install dependencies in sub-directories that are created by | 
|  | cloning dependencies. | 
|  | """ | 
|  | deps_path = dir_path / 'DEPS' | 
|  | if not deps_path.is_file(): | 
|  | return | 
|  |  | 
|  | log(f"Listing dependencies from {dir_path}") | 
|  | DEPS = open(deps_path).read() | 
|  |  | 
|  | ldict = {} | 
|  | exec(DEPS, globals(), ldict) | 
|  | deps = ldict.get('deps') | 
|  | variables = ldict.get('vars', {}) | 
|  |  | 
|  | if deps is None: | 
|  | log(f"WARNING: DEPS file '{deps_path}' does not define a 'deps' variable" | 
|  | ) | 
|  | return | 
|  |  | 
|  | for submodule in required_submodules: | 
|  | if submodule not in deps: | 
|  | continue | 
|  | submodule_path = dir_path / Path(submodule) | 
|  |  | 
|  | raw_url = deps[submodule]['url'] | 
|  | git_url, git_tag = raw_url.format(**variables).rsplit('@', 1) | 
|  |  | 
|  | # Run git from within the submodule's path (don't use for clone) | 
|  | git = lambda *x: subprocess.run([args.git, '-C', submodule_path, *x], | 
|  | capture_output=True) | 
|  |  | 
|  | log(f"Fetching dependency '{submodule}'") | 
|  | if (submodule_path / ".git").is_dir(): | 
|  | # The module was already cloned, but we may need to update it | 
|  | proc = git('rev-parse', 'HEAD') | 
|  | need_update = proc.stdout.decode().strip() != git_tag | 
|  |  | 
|  | if need_update: | 
|  | # The module was already cloned, but we may need to update it | 
|  | proc = git('cat-file', '-t', git_tag) | 
|  | git_tag_exists = proc.returncode == 0 | 
|  |  | 
|  | if not git_tag_exists: | 
|  | log(f"Updating '{submodule_path}' from '{git_url}'") | 
|  | if args.shallow: | 
|  | git('fetch', 'origin', git_tag, '--depth', '1') | 
|  | else: | 
|  | git('fetch', 'origin') | 
|  |  | 
|  | log(f"Checking out tag '{git_tag}'") | 
|  | git('checkout', git_tag) | 
|  |  | 
|  | else: | 
|  | if args.shallow: | 
|  | log(f"Shallow cloning '{git_url}' at '{git_tag}' into '{submodule_path}'" | 
|  | ) | 
|  | shallow_clone(git, git_url, git_tag) | 
|  | else: | 
|  | log(f"Cloning '{git_url}' into '{submodule_path}'") | 
|  | subprocess.run([ | 
|  | args.git, | 
|  | 'clone', | 
|  | '--recurse-submodules', | 
|  | git_url, | 
|  | submodule_path, | 
|  | ], | 
|  | capture_output=True) | 
|  |  | 
|  | log(f"Checking out tag '{git_tag}'") | 
|  | git('checkout', git_tag) | 
|  |  | 
|  | # Recursive call | 
|  | required_subsubmodules = [ | 
|  | m[len(submodule) + 1:] for m in required_submodules | 
|  | if m.startswith(submodule + "/") | 
|  | ] | 
|  | process_dir(args, submodule_path, required_subsubmodules) | 
|  |  | 
|  |  | 
|  | def shallow_clone(git, git_url, git_tag): | 
|  | """ | 
|  | Fetching only 1 commit is not exposed in the git clone API, so we decompose | 
|  | it manually in git init, git fetch, git reset. | 
|  | """ | 
|  | git('init') | 
|  | git('fetch', git_url, git_tag, '--depth', '1') | 
|  |  | 
|  |  | 
|  | def log(msg): | 
|  | """Just makes it look good in the CMake log flow.""" | 
|  | print(f"-- -- {msg}") | 
|  |  | 
|  |  | 
|  | class Var: | 
|  | """ | 
|  | Mock Var class, that the content of DEPS files assume to exist when they | 
|  | are exec-ed. | 
|  | """ | 
|  | def __init__(self, name): | 
|  | self.name = name | 
|  |  | 
|  | def __add__(self, text): | 
|  | return self.name + text | 
|  |  | 
|  | def __radd__(self, text): | 
|  | return text + self.name | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main(parser.parse_args()) |