dawn-cmake/tools/fetch_dawn_dependencies.py

230 lines
7.4 KiB
Python

# Copyright 2023 The Dawn & Tint Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
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="""
Fetch dependencies needed for testing
""")
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/vulkan-deps',
'third_party/vulkan-deps/spirv-headers/src',
'third_party/vulkan-deps/spirv-tools/src',
'third_party/vulkan-deps/vulkan-headers/src',
'third_party/vulkan-deps/vulkan-loader/src',
'third_party/vulkan-deps/vulkan-tools/src',
'third_party/glfw',
'third_party/abseil-cpp',
'third_party/jinja2',
'third_party/markupsafe',
]
if args.use_test_deps:
required_submodules += [
'third_party/googletest',
]
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"ERROR: DEPS file '{deps_path}' does not define a 'deps' variable"
)
exit(1)
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 not submodule_path.is_dir():
if args.shallow:
log(f"Shallow cloning '{git_url}' at '{git_tag}' into '{submodule_path}'"
)
shallow_clone(git, git_url, git_tag, submodule_path)
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)
elif (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:
# The caller may have "flattened" the source tree to get rid of
# some heavy submodules.
log(f"(Overridden by a local copy of the submodule)")
# 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, submodule_path):
"""
Fetching only 1 commit is not exposed in the git clone API, so we decompose
it manually in git init, git fetch, git reset.
"""
submodule_path.mkdir()
git('init')
git('remote', 'add', 'origin', git_url)
git('fetch', 'origin', 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())