Add and use FindLibClang module for c-picker setup

c-picker uses libclang to parse the code. If compatible libclang is not
available in the system's default library directory c-picker needs to
have CLANG_LIBRARY_PATH environment variable pointing to the suitable
directory. The new module tries to find this directory and the
environment variable is now set during the c-picker call.

Change-Id: I09327b2d09620884ee72ff831ead546a0d10b900
Signed-off-by: Imre Kis <imre.kis@arm.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86029e0..bcfb234 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,12 +23,40 @@
 set(CPICKER_CACHE_PATH ${CMAKE_CURRENT_BINARY_DIR}/cpicker_cache CACHE PATH "Directory of c-picker generated file")
 set(UNIT_TEST_COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/common/main.cpp)
 set(CMAKE_CXX_STANDARD 11)
+set(CLANG_LIBRARY_PATH_HELP "libclang directory for c-picker")
 
 # Checking TF-A
 if (NOT TF_A_PATH)
 	message(FATAL_ERROR "TF_A_PATH is not set")
 endif()
 
+# Trying to set CLANG_LIBRARY_PATH using the following methods
+# 1. Using cache or command line definition
+#    Show warning if environment variable is also set but has different value
+# 2. Copying the value of CLANG_LIBRARY_PATH environment variable if set
+# 3. find_package (llvm-config, common paths or Windows registry)
+# If none of the above steps succeeded CMake emits a fatal error and stops
+if (DEFINED CLANG_LIBRARY_PATH)
+	message(STATUS "Using CLANG_LIBRARY_PATH from CMake variable (command line or cache)")
+
+	if (DEFINED ENV{CLANG_LIBRARY_PATH})
+		if (NOT (${CLANG_LIBRARY_PATH} STREQUAL $ENV{CLANG_LIBRARY_PATH}))
+			message(WARNING "Both CLANG_LIBRARY_PATH CMake and environment variables are set but have different values")
+		endif()
+	endif()
+else()
+	if (DEFINED ENV{CLANG_LIBRARY_PATH})
+		message(STATUS "Setting CLANG_LIBRARY_PATH based on environment variable")
+		set(CLANG_LIBRARY_PATH $ENV{CLANG_LIBRARY_PATH} CACHE PATH ${CLANG_LIBRARY_PATH_HELP})
+	else()
+		message(STATUS "Setting CLANG_LIBRARY_PATH based on find_package")
+		find_package(LibClang REQUIRED)
+		set(CLANG_LIBRARY_PATH ${LibClang_LIBRARY_DIRS} CACHE PATH ${CLANG_LIBRARY_PATH_HELP})
+	endif()
+endif()
+
+message(STATUS "CLANG_LIBRARY_PATH has been set to ${CLANG_LIBRARY_PATH}")
+
 # Checking c-picker
 find_program(CPICKER_COMMAND "c-picker")
 if (NOT CPICKER_COMMAND)
diff --git a/cmake/FindLibClang.cmake b/cmake/FindLibClang.cmake
new file mode 100644
index 0000000..c0eefc5
--- /dev/null
+++ b/cmake/FindLibClang.cmake
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2019, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+#[=======================================================================[.rst:
+FindLibClang
+-------
+
+Finds the LibClang library.
+
+Imported Targets
+^^^^^^^^^^^^^^^^
+
+This module provides the following imported targets, if found:
+
+``LibClang``
+  The Clang library
+
+Result Variables
+^^^^^^^^^^^^^^^^
+
+This will define the following variables:
+
+``LibClang_FOUND``
+  True if the system has the Clang library.
+``LibClang_LIBRARY_DIRS``
+  Libraries needed to link to Clang.
+
+#]=======================================================================]
+
+
+# 1. Use llvm-config
+find_program(_LLVM_CONFIG_COMMAND "llvm-config")
+
+if (_LLVM_CONFIG_COMMAND)
+	message(STATUS "Setting LibClang_LIBRARY_DIRS using ${_LLVM_CONFIG_COMMAND}")
+
+	execute_process(
+		COMMAND ${_LLVM_CONFIG_COMMAND} --libdir
+		OUTPUT_VARIABLE _LLVM_CONFIG_OUTPUT
+	)
+
+	# Stripping newline
+	string(STRIP ${_LLVM_CONFIG_OUTPUT} LibClang_LIBRARY_DIRS)
+endif()
+
+# 2. Try to find as library
+if (NOT LibClang_LIBRARY_DIRS)
+	message(STATUS "Setting LibClang_LIBRARY_DIRS based on common directories list")
+
+	set(LIBCLANG_COMMON_PATHS
+		/usr/lib/llvm-9/lib
+		/usr/lib/llvm-8/lib
+		/usr/lib/llvm-7/lib
+		/usr/lib/llvm-6.0/lib)
+
+	if (WIN32)
+		set(CMAKE_FIND_LIBRARY_PREFIXES "lib")
+		set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
+
+		get_filename_component(LLVM_PATH_FROM_REGISTRY [HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\LLVM\\LLVM] ABSOLUTE)
+		list(APPEND LIBCLANG_COMMON_PATHS "${LLVM_PATH_FROM_REGISTRY}/bin")
+	endif()
+
+	find_library(_LIBCLANG_PATH
+		NAMES clang
+		HINTS ${LIBCLANG_COMMON_PATHS}
+	)
+
+	if (_LIBCLANG_PATH)
+		get_filename_component(LibClang_LIBRARY_DIRS ${_LIBCLANG_PATH} DIRECTORY)
+	endif()
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(LibClang
+	"Please install llvm-config or set LibClang path manually"
+	LibClang_LIBRARY_DIRS
+)
diff --git a/cmake/UnitTest.cmake b/cmake/UnitTest.cmake
index d2255df..207b25c 100644
--- a/cmake/UnitTest.cmake
+++ b/cmake/UnitTest.cmake
@@ -11,6 +11,7 @@
 #     CPICKER_COMMAND: command of the c-picker
 #     CPICKER_CACHE_PATH: root directory of the c-picker generate files
 #     UNIT_TEST_COMMON_SOURCES: common source files for every test build
+#     CLANG_LIBRARY_PATH: libclang directory for c-picker
 #   Modules
 #     CTest module should be included in the root CMakeLists.txt before calling this function
 
@@ -43,14 +44,18 @@
 
 			# Fetching referenced source files as the dependencies of the generated file
 			execute_process(
-				COMMAND ${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${TF_A_PATH} --print-dependencies
+				COMMAND
+					${CMAKE_COMMAND} -E env CLANG_LIBRARY_PATH=${CLANG_LIBRARY_PATH}
+					${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${TF_A_PATH} --print-dependencies
 				OUTPUT_VARIABLE CPICKER_DEPENDENCIES
 			)
 
 			# Adding custom command for invoking c-picker
 			add_custom_command(
 				OUTPUT ${CPICKER_OUTPUT}
-				COMMAND ${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${TF_A_PATH} > ${CPICKER_OUTPUT}
+				COMMAND
+					${CMAKE_COMMAND} -E env CLANG_LIBRARY_PATH=${CLANG_LIBRARY_PATH}
+					${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${TF_A_PATH} > ${CPICKER_OUTPUT}
 				DEPENDS ${TEST_SOURCE} ${CPICKER_DEPENDENCIES}
 				COMMENT "Generating c-picker output ${CPICKER_OUTPUT}"
 			)