skia2/gn/find_headers.py
Matt Turner a8689e3d4d Give a human-intelligible message if gn generates a warning
If gn generates an otherwise harmless warning, find_headers.py will fail
to parse the gn-generated JSON because gn prints both the warning and
the JSON to stdout. Though find_headers.py prints the output of gn if
JSON parsing fails, a human investigating this failure is likely to
assume that the warning they see is from the build system, not from
find_headers.py, and is not related to the failure.

For example, this warning about an argument which has no effect breaks
the build:

    WARNING at build arg file (use "gn args <out_dir>" to edit):36:36: Build argument has no effect.
    skia_skqp_global_error_tolerance = 8
                                       ^
    [...]

Change-Id: Iafa7252ba161e4def1438f5d9480b64fdaa887d2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/510536
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2022-02-23 18:40:03 +00:00

108 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright 2016 Google Inc.
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import collections
import json
import os
import subprocess
import sys
# Finds all public sources in include directories then write them to skia.h.
# Also write skia.h.deps, which Ninja uses to track dependencies. It's the
# very same mechanism Ninja uses to know which .h files affect which .cpp files.
gn = sys.argv[1]
absolute_source = sys.argv[2]
skia_h = sys.argv[3]
include_dirs = sys.argv[4:]
absolute_source = os.path.normpath(absolute_source)
include_dirs = [os.path.join(os.path.normpath(include_dir), '')
for include_dir in include_dirs]
include_dirs.sort(key=len, reverse=True)
gn_desc_cmd = [gn, 'desc', '.', '--root=%s' % absolute_source, '--format=json',
'*']
desc_json_txt = ''
try:
desc_json_txt = subprocess.check_output(gn_desc_cmd).decode('utf-8')
except subprocess.CalledProcessError as e:
print(e.output.decode('utf-8'))
raise
if desc_json_txt.startswith('WARNING'):
print('\ngn generated a warning when we asked for JSON output.',
'To see the warning, run this command from the out_dir:',
'(you may need to quote the * argument)\n',
' '.join(gn_desc_cmd),
'\n', sep='\n')
sys.exit(-1)
desc_json = {}
try:
desc_json = json.loads(desc_json_txt)
except ValueError:
print(desc_json_txt)
raise
sources = set()
for target in desc_json.values():
# We'll use `public` headers if they're listed, or pull them from `sources`
# if not. GN sneaks in a default "public": "*" into the JSON if you don't
# set one explicitly.
search_list = target.get('public')
if search_list == '*':
search_list = target.get('sources', [])
for name in search_list:
sources.add(os.path.join(absolute_source, os.path.normpath(name[2:])))
Header = collections.namedtuple('Header', ['absolute', 'include'])
headers = {}
for source in sources:
source_as_include = [os.path.relpath(source, absolute_source)
for include_dir in include_dirs
if source.startswith(include_dir)]
if not source_as_include:
continue
statinfo = os.stat(source)
key = str(statinfo.st_ino) + ':' + str(statinfo.st_dev)
# On Windows os.stat st_ino is 0 until 3.3.4 and st_dev is 0 until 3.4.0.
if key == '0:0':
key = source
include_path = source_as_include[0]
if key not in headers or len(include_path) < len(headers[key].include):
headers[key] = Header(source, include_path)
headers = sorted(headers.values(), key=lambda x: x.include)
with open(skia_h, 'w') as f:
f.write('// skia.h generated by GN.\n')
f.write('#ifndef skia_h_DEFINED\n')
f.write('#define skia_h_DEFINED\n')
for header in headers:
f.write('#include "' + header.include + '"\n')
f.write('#endif//skia_h_DEFINED\n')
with open(skia_h + '.deps', 'w') as f:
f.write(skia_h + ':')
for header in headers:
f.write(' ' + header.absolute)
f.write(' build.ninja.d')
f.write('\n')
# Temporary: during development this file wrote skia.h.d, not skia.h.deps,
# and I think we have some bad versions of those files laying around.
if os.path.exists(skia_h + '.d'):
os.remove(skia_h + '.d')