From c3afb63529df60d85ab9c44a930b3982ac00be6e Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Thu, 31 Mar 2022 10:34:31 +0200 Subject: [PATCH] Better detection for libclang and its builtin header path --- radio/util/find_clang.py | 116 ++++++++++++++++++++++++++++++++ radio/util/generate_datacopy.py | 37 +++------- 2 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 radio/util/find_clang.py diff --git a/radio/util/find_clang.py b/radio/util/find_clang.py new file mode 100644 index 000000000..89e2a2e6a --- /dev/null +++ b/radio/util/find_clang.py @@ -0,0 +1,116 @@ +# +# Most of this code is from clang_complete: +# https://github.com/xavierd/clang_complete/blob/master/plugin/libclang.py +# +import os +import sys +from clang.cindex import * + +# Check if libclang is able to find the builtin include files. +# +# libclang sometimes fails to correctly locate its builtin include files. This +# happens especially if libclang is not installed at a standard location. This +# function checks if the builtin includes are available. +def canFindBuiltinHeaders(index, args = []): + flags = 0 + currentFile = ("test.c", '#include "stddef.h"') + try: + tu = index.parse("test.c", args, [currentFile], flags) + except TranslationUnitLoadError as e: + return 0 + + return len(tu.diagnostics) == 0 + +# Derive path to clang builtin headers. +# +# This function tries to derive a path to clang's builtin header files. We are +# just guessing, but the guess is very educated. In fact, we should be right +# for all manual installations (the ones where the builtin header path problem +# is very common) as well as a set of very common distributions. +def getBuiltinHeaderPath(library_path): + if os.path.isfile(library_path): + library_path = os.path.dirname(library_path) + + knownPaths = [ + library_path + "/../lib/clang", # default value + library_path + "/../clang", # gentoo + library_path + "/clang", # opensuse + library_path + "/", # Google + "/usr/lib64/clang", # x86_64 (openSUSE, Fedora) + "/usr/lib/clang" + ] + + for path in knownPaths: + try: + subDirs = [f for f in os.listdir(path) if os.path.isdir(path + "/" + f)] + subDirs = sorted(subDirs) or ['.'] + path = path + "/" + subDirs[-1] + "/include" + print("searching builtins in " + path) + if canFindBuiltinHeaders(index, ["-I" + path]): + return path + except: + pass + + return None + +def findLibClang(): + if sys.platform == "darwin": + knownPaths = [ + "/usr/local/Cellar/llvm/6.0.0/lib", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", + "/Library/Developer/CommandLineTools/usr/lib" + ] + libSuffix = ".dylib" + elif sys.platform.startswith("linux"): + knownPaths = [ + "/usr/lib/llvm-7/lib", + "/usr/lib/llvm-6.0/lib", + "/usr/lib/llvm-3.8/lib", + "/usr/local/lib", + "/usr/lib" + ] + libSuffix = ".so" + elif sys.platform == "win32" or sys.platform == "msys": + knownPaths = os.environ.get("PATH").split(':') + libSuffix = ".dll" + else: + # Unsupported platform + return None + + for path in knownPaths: + # print("trying " + path) + if os.path.exists(path + "/libclang" + libSuffix): + return path + + return None + +def initLibClang(): + global index + + library_path = findLibClang() + if library_path: + print("libclang found: " + library_path) + if os.path.isdir(library_path): + Config.set_library_path(library_path) + else: + Config.set_library_file(library_path) + else: + print("ERROR: libclang not found!") + return False + + Config.set_compatibility_check(False) + + try: + index = Index.create() + except Exception as e: + print("ERROR: could not load libclang from '%s'." % library_path) + return False + + global builtin_hdr_path + builtin_hdr_path = getBuiltinHeaderPath(library_path) + if not builtin_hdr_path: + print("ERROR: builtin header path not found") + return False + + # Everything is OK, libclang can be used + return True diff --git a/radio/util/generate_datacopy.py b/radio/util/generate_datacopy.py index 3b572b234..b7eda5b05 100755 --- a/radio/util/generate_datacopy.py +++ b/radio/util/generate_datacopy.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +import find_clang import sys import clang.cindex import time @@ -9,25 +10,6 @@ import os structs = [] extrastructs = [] - -def find_libclang(): - if sys.platform == "darwin": - for path in ("/usr/local/Cellar/llvm/6.0.0/lib/libclang.dylib", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib", - "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib"): - if os.path.exists(path): - return path - elif sys.platform.startswith("linux"): - for version in ("7", "6.0", "3.8"): - path = "/usr/lib/llvm-%s/lib/libclang.so" % version - if os.path.exists(path): - return path - for path in ("/usr/local/lib/libclang.so", - "/usr/lib/libclang.so"): - if os.path.exists(path): - return path - - def build_struct(cursor, anonymousUnion=False): if not anonymousUnion: structs.append(cursor.spelling) @@ -108,13 +90,16 @@ def print_translation_unit_diags(diags, prefix=''): def main(): - libclang = find_libclang() - if libclang: - # print(libclang, file=sys.stderr) - clang.cindex.Config.set_library_file(libclang) - index = clang.cindex.Index.create() - translation_unit = index.parse(sys.argv[1], ['-x', 'c++', '-std=c++11'] + sys.argv[2:]) + if not find_clang.initLibClang(): + sys.exit(-1) + + index = find_clang.index + args = ['-x', 'c++', '-std=c++11'] + sys.argv[2:] + if find_clang.builtin_hdr_path: + args.append("-I" + find_clang.builtin_hdr_path) + + translation_unit = index.parse(sys.argv[1], args) if translation_unit.diagnostics: print_translation_unit_diags(translation_unit.diagnostics)