mirror of
https://github.com/google/pebble.git
synced 2025-03-15 00:31:21 +00:00
286 lines
9.9 KiB
Python
286 lines
9.9 KiB
Python
# Copyright 2024 Google LLC
|
|
#
|
|
# 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.
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
dump_tree = False
|
|
|
|
|
|
def add_clang_compat_module_to_sys_path_if_needed():
|
|
try:
|
|
import clang.cindex
|
|
except:
|
|
sys.path.append(os.path.join(os.path.dirname(__file__),
|
|
'clang_compat'))
|
|
logging.info("Importing clang python compatibility module")
|
|
add_clang_compat_module_to_sys_path_if_needed()
|
|
import clang.cindex
|
|
|
|
|
|
def get_homebrew_llvm_lib_path():
|
|
try:
|
|
o = subprocess.check_output(['brew', 'ls', 'llvm'])
|
|
except subprocess.CalledProcessError:
|
|
# No brew llvm installed
|
|
return None
|
|
|
|
# Brittleness alert! Grepping output of `brew info llvm` for llvm bin path:
|
|
m = re.search('.*/llvm-config', o)
|
|
if m:
|
|
llvm_config_path = m.group(0)
|
|
|
|
o = subprocess.check_output([llvm_config_path, '--libdir'])
|
|
llvm_lib_path = o.strip()
|
|
|
|
# Make sure --enable-clang and --enable-python options were used:
|
|
if os.path.exists(os.path.join(llvm_lib_path, 'libclang.dylib')) and \
|
|
glob.glob(os.path.join(llvm_lib_path,
|
|
'python*', 'site-packages', 'clang')):
|
|
return llvm_lib_path
|
|
else:
|
|
logging.info("Found llvm from homebrew, but not installed with"
|
|
" --with-clang --with-python")
|
|
|
|
|
|
def load_library():
|
|
try:
|
|
libclang_lib = clang.cindex.conf.lib
|
|
except clang.cindex.LibclangError:
|
|
pass
|
|
except:
|
|
raise
|
|
else:
|
|
return
|
|
|
|
if sys.platform == 'darwin':
|
|
libclang_path = get_homebrew_llvm_lib_path()
|
|
if not libclang_path:
|
|
# Try using Xcode's libclang:
|
|
logging.info("llvm from homebrew not found,"
|
|
" trying Xcode's instead")
|
|
xcode_path = subprocess.check_output(['xcode-select',
|
|
'--print-path']).strip()
|
|
libclang_path = \
|
|
os.path.join(xcode_path,
|
|
'Toolchains/XcodeDefault.xctoolchain/usr/lib')
|
|
clang.cindex.conf.set_library_path(libclang_path)
|
|
elif sys.platform == 'linux2':
|
|
libclang_path = subprocess.check_output(['llvm-config',
|
|
'--libdir']).strip()
|
|
clang.cindex.conf.set_library_path(libclang_path)
|
|
|
|
libclang_lib = clang.cindex.conf.lib
|
|
|
|
|
|
def do_libclang_setup():
|
|
load_library()
|
|
|
|
functions = (
|
|
("clang_Cursor_getCommentRange",
|
|
[clang.cindex.Cursor],
|
|
clang.cindex.SourceRange),
|
|
)
|
|
|
|
for f in functions:
|
|
clang.cindex.register_function(clang.cindex.conf.lib, f, False)
|
|
|
|
def is_node_kind_a_type_decl(kind):
|
|
return kind == clang.cindex.CursorKind.STRUCT_DECL or \
|
|
kind == clang.cindex.CursorKind.ENUM_DECL or \
|
|
kind == clang.cindex.CursorKind.TYPEDEF_DECL
|
|
|
|
def get_node_spelling(node):
|
|
return clang.cindex.conf.lib.clang_getCursorSpelling(node)
|
|
|
|
def get_comment_range(node):
|
|
source_range = clang.cindex.conf.lib.clang_Cursor_getCommentRange(node)
|
|
if source_range.start.file is None:
|
|
return None
|
|
|
|
return source_range
|
|
|
|
def get_comment_range_for_decl(node):
|
|
source_range = get_comment_range(node)
|
|
if source_range is None:
|
|
if node.kind == clang.cindex.CursorKind.TYPEDEF_DECL:
|
|
for child in node.get_children():
|
|
if is_node_kind_a_type_decl(child.kind) and len(get_node_spelling(child)) == 0:
|
|
source_range = get_comment_range(child)
|
|
|
|
return source_range
|
|
|
|
def get_comment_string_for_decl(node):
|
|
comment_range = get_comment_range_for_decl(node)
|
|
comment_string = get_string_from_file(comment_range)
|
|
if comment_string is None:
|
|
return None
|
|
|
|
if '@addtogroup' in comment_string:
|
|
# This is actually a block comment, not a comment specifically for this type. Ignore it.
|
|
return None
|
|
|
|
return comment_string
|
|
|
|
def get_string_from_file(source_range):
|
|
if source_range is None:
|
|
return None
|
|
|
|
source_range_file = source_range.start.file
|
|
if source_range_file is None:
|
|
return None
|
|
|
|
with open(source_range_file.name) as f:
|
|
f.seek(source_range.start.offset)
|
|
return f.read(source_range.end.offset - source_range.start.offset)
|
|
|
|
def dump_node(node, indent_level=0):
|
|
spelling = node.spelling
|
|
if node.kind == clang.cindex.CursorKind.MACRO_DEFINITION:
|
|
spelling = get_node_spelling(node)
|
|
|
|
print "%*s%s> %s" % (indent_level * 2, "", node.kind, spelling)
|
|
print "%*sRange: %s" % (4 + (indent_level * 2), "", str(node.extent))
|
|
print "%*sComment: %s" % (4 + (indent_level * 2), "", str(get_comment_range_for_decl(node)))
|
|
|
|
def return_true(node):
|
|
return True
|
|
|
|
def for_each_node(node, func, level=0, filter_func=return_true):
|
|
if not filter_func(node):
|
|
return
|
|
|
|
if dump_tree:
|
|
# Skip over nodes that are added by clang internals
|
|
if node.location.file is not None:
|
|
dump_node(node, level)
|
|
|
|
func(node)
|
|
|
|
for child in node.get_children():
|
|
for_each_node(child, func, level + 1, filter_func)
|
|
|
|
|
|
def extract_declarations(tu, filenames, func):
|
|
matching_basenames = {os.path.basename(f) for f in filenames}
|
|
def filename_filter_func(node):
|
|
node_file = node.location.file
|
|
if node_file is None:
|
|
return True
|
|
|
|
node_filename = node_file.name
|
|
if node_filename is None:
|
|
return True
|
|
|
|
base_name = os.path.basename(node_filename)
|
|
return base_name in matching_basenames
|
|
|
|
for_each_node(tu.cursor, func, filter_func=filename_filter_func)
|
|
|
|
|
|
def parse_file(filename, filenames, func, internal_sdk_build=False, compiler_flags=None):
|
|
src_dir = os.path.join(os.path.dirname(__file__), "../src")
|
|
|
|
args = [ "-I%s/core" % src_dir,
|
|
"-I%s/include" % src_dir,
|
|
"-I%s/fw" % src_dir,
|
|
"-I%s/fw/applib/vendor/uPNG" % src_dir,
|
|
"-I%s/fw/applib/vendor/tinflate" % src_dir,
|
|
"-I%s/fw/vendor/jerryscript/jerry-core" % src_dir,
|
|
"-I%s/libbtutil/include" % src_dir,
|
|
"-I%s/libos/include" % src_dir,
|
|
"-I%s/libutil/includes" % src_dir,
|
|
"-I%s/libc/include" % src_dir,
|
|
"-I%s/../build/src/fw" % src_dir,
|
|
"-I%s/include" % src_dir,
|
|
"-DSDK",
|
|
"-fno-builtin-itoa"]
|
|
|
|
# Add header search paths, recursing subdirs:
|
|
for inc_sub_dir in ['fw/util']:
|
|
args += [inc_sub_dir]
|
|
args += ["-I%s" % d for d in glob.glob(os.path.join(src_dir, "%s/*/" % inc_sub_dir))]
|
|
|
|
if internal_sdk_build:
|
|
args.append("-DINTERNAL_SDK_BUILD")
|
|
else:
|
|
args.append("-DPUBLIC_SDK")
|
|
|
|
args.extend(compiler_flags)
|
|
|
|
# Check Clang for unsigned types being undefined
|
|
# https://sourceware.org/ml/newlib/2014/msg00082.html
|
|
# this workaround should be removed when fixed in newlib
|
|
cmd = ['clang'] + ['-dM', '-E', '-']
|
|
try:
|
|
out = subprocess.check_output(cmd, stdin=open('/dev/null')).strip()
|
|
if not isinstance(out, str):
|
|
out = out.decode(sys.stdout.encoding or 'iso8859-1')
|
|
except Exception as err:
|
|
print('Could not run clang type checking %r' % err)
|
|
raise
|
|
|
|
if '__UINT8_TYPE__' not in out:
|
|
args.insert(0, r"-D__UINT8_TYPE__=unsigned __INT8_TYPE__")
|
|
args.insert(0, r"-D__UINT16_TYPE__=unsigned __INT16_TYPE__")
|
|
args.insert(0, r"-D__UINT32_TYPE__=unsigned __INT32_TYPE__")
|
|
args.insert(0, r"-D__UINT64_TYPE__=unsigned __INT64_TYPE__")
|
|
args.insert(0, r"-D__UINTPTR_TYPE__=unsigned __INTPTR_TYPE__")
|
|
|
|
# Tools pull in time.h from arm toolchain instead of using our core/utils/time/time.h
|
|
# with modified definition of struct tm, so disable accidental include of wrong time.h
|
|
args.insert(0, r"-D_TIME_H_")
|
|
|
|
# Try and find our arm toolchain and use the headers from that.
|
|
gcc_path = subprocess.check_output(['which', 'arm-none-eabi-gcc']).strip()
|
|
include_path = os.path.join(os.path.dirname(gcc_path), '../arm-none-eabi/include')
|
|
args.append("-I%s" % include_path)
|
|
|
|
# Find the arm-none-eabi-gcc libgcc path including stdbool.h
|
|
cmd = ['arm-none-eabi-gcc'] + ['-E', '-v', '-xc', '-']
|
|
try:
|
|
out = subprocess.check_output(cmd, stdin=open('/dev/null'), stderr=subprocess.STDOUT).strip().splitlines()
|
|
if '#include <...> search starts here:' in out:
|
|
libgcc_include_path = out[out.index('#include <...> search starts here:') + 1].strip()
|
|
args.append("-I%s" % libgcc_include_path)
|
|
except Exception as err:
|
|
print('Could not run arm-none-eabi-gcc path detection %r' % err)
|
|
|
|
if not os.path.isfile(filename):
|
|
raise Exception("Invalid filename: " + filename)
|
|
|
|
index = clang.cindex.Index.create()
|
|
tu = index.parse(filename, args=args, options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD)
|
|
|
|
extract_declarations(tu, filenames, func)
|
|
|
|
for d in tu.diagnostics:
|
|
if d.severity >= clang.cindex.Diagnostic.Error \
|
|
and d.spelling != "conflicting types for 'itoa'":
|
|
if d.severity == clang.cindex.Diagnostic.Error:
|
|
error_str = "Error: %s" % d.__repr__()
|
|
elif d.severity == clang.cindex.Diagnostic.Fatal:
|
|
error_str = "Fatal: %s" % d.__repr__()
|
|
|
|
class ParsingException(Exception):
|
|
pass
|
|
|
|
raise ParsingException(error_str)
|
|
|
|
do_libclang_setup()
|
|
|