blob: dd9dd1e65ba2c996c9cf8d865c33775eb1660e44 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2025 Google LLC
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import difflib
import os
import shutil
import subprocess
import sys
import tempfile
# Third party dependencies necessary for Dawn to generate its version of the header.
THIRD_PARTY_DIR = os.path.join('third_party')
JINJA2_DIR = os.path.join('third_party', 'jinja2')
MARKUPSAFE_DIR = os.path.join('third_party', 'markupsafe')
# Dawn generator constants.
DAWN_GENERATOR = os.path.join('generator', 'dawn_json_generator.py')
DAWN_GENERATOR_TEMPLATE_DIR = os.path.join('generator', 'templates')
DAWN_JSON = os.path.join('src', 'dawn', 'dawn.json')
DAWN_WEBGPU_HEADER_TARGET = 'webgpu_headers'
# Upstream webgpu-header constants.
WEBGPU_HEADER = os.path.join('third_party', 'webgpu-headers', 'src',
'webgpu.h')
# Default output file locations.
DAWN_GENERATED_HEADER = os.path.join('webgpu-headers', 'webgpu.h')
WEBGPU_GENERATED_HEADER = os.path.join('webgpu-headers', 'webgpu_upstream.h')
GENERATED_DIFF = os.path.join('webgpu-headers', 'webgpu.h.diff')
# Helper that removes all comments from headers so that they can be diff-ed easier.
def _remove_comments(input_path, output_path):
lines = []
with open(input_path, 'r') as input:
last_line_was_comment = False
in_comment_block = False
for line in input:
stripped = line.strip()
# If we are in a comment block, skip until we find the end.
if in_comment_block:
if stripped.endswith("*/"):
in_comment_block = False
last_line_was_comment = True
continue
# Drop empty lines directly following comments.
if not stripped and last_line_was_comment:
continue
if stripped.startswith('//'):
# Simple single line comment case, just skip the line.
last_line_was_comment = True
continue
if stripped.startswith("/*") and stripped.endswith("*/"):
# Simple single line comment with multi-line syntax, just skip the line.
last_line_was_comment = True
continue
if stripped.startswith("/*") and "*/" not in stripped:
# Otherwise, if we are a multi-line comment that isn't inlined.
in_comment_block = True
continue
last_line_was_comment = False
lines.append(line)
with open(output_path, 'w') as output:
for line in lines:
output.write(line)
# Helper that just calls into Dawn's generators and generates webgpu.h without
# comments.
def _gen_dawn_header(dir):
subprocess.check_call([
sys.executable, DAWN_GENERATOR, '--template-dir',
DAWN_GENERATOR_TEMPLATE_DIR, '--output-dir', dir, '--dawn-json',
DAWN_JSON, '--targets', DAWN_WEBGPU_HEADER_TARGET, '--jinja2-path',
JINJA2_DIR, '--markupsafe-path', MARKUPSAFE_DIR
])
_remove_comments(os.path.join(dir, DAWN_GENERATED_HEADER),
os.path.join(dir, DAWN_GENERATED_HEADER))
# Helper that removes comments from the upstream webgpu.h file.
def _gen_webgpu_header(dir):
_remove_comments(WEBGPU_HEADER, os.path.join(dir, WEBGPU_GENERATED_HEADER))
# Helper that generates the diff between the Dawn and upstream headers.
def _gen_header_diff(temp_dir, output_dir):
_gen_dawn_header(temp_dir)
_gen_webgpu_header(temp_dir)
with open(os.path.join(temp_dir, DAWN_GENERATED_HEADER), 'r') as dawn_header, \
open(os.path.join(temp_dir, WEBGPU_GENERATED_HEADER), 'r') as webgpu_header, \
open(os.path.join(output_dir, GENERATED_DIFF), 'w') as diff_file:
diff = difflib.unified_diff(webgpu_header.readlines(),
dawn_header.readlines(),
fromfile='webgpu_header',
tofile='dawn_header')
# Skip writing all the line numbers as these all change every time
# anything in the file changes.
for line in diff:
if line.startswith('@@'):
diff_file.writelines('@@\n')
else:
diff_file.writelines(line)
# Helper that verifies that all the necessary dependencies to run checks are
# available. If they are not, we just silently pass for now.
def _check_deps():
if not os.path.isdir(JINJA2_DIR):
print(
f'Third party dependency jinja2 is not available at {JINJA2_DIR}')
return False
if not os.path.isdir(MARKUPSAFE_DIR):
print(
f'Third party dependency markupsafe is not available at {MARKUPSAFE_DIR}'
)
return False
if not os.path.exists(WEBGPU_HEADER):
print(
f'Third party upstream webgpu.h is not available at {WEBGPU_HEADER}'
)
return False
return True
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=('gen', 'check'))
options = parser.parse_args()
if options.action == 'gen':
with tempfile.TemporaryDirectory() as temp_dir:
_gen_header_diff(temp_dir, THIRD_PARTY_DIR)
return 0
if options.action == 'check':
if not _check_deps():
return 0
with tempfile.TemporaryDirectory() as temp_dir:
_gen_header_diff(temp_dir, temp_dir)
with open(os.path.join(temp_dir, GENERATED_DIFF), 'r') as temp_file, \
open(os.path.join(THIRD_PARTY_DIR, GENERATED_DIFF), 'r') as curr_file:
diff = ''.join(
difflib.unified_diff(temp_file.readlines(),
curr_file.readlines()))
if diff:
print(
"Changes resulted in new diffs between Dawn's and the "
"upstream webgpu.h. Please re-run "
"'third_party/webgpu-headers/cli gen' manually and "
"verify/commit the new diffs in "
"'third_party/webgpu-headers/webgpu.h.diff' if the "
"changes are expected.")
return 1
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))