From 297a4baefdd1c73f9df477c6f626984bee8e5dbb Mon Sep 17 00:00:00 2001 From: Andrzej Warzynski Date: Sun, 29 Mar 2020 20:09:35 +0100 Subject: [PATCH] Add the hello-world plugin Adds the first plugin, the first test, LIT config scripts and updates README. --- CMakeLists.txt | 85 +++++++++++++++++++++++++++++++++++++++++ README.md | 72 ++++++++++++++++++++++++++++++++++ lib/CMakeLists.txt | 14 +++++++ lib/HelloWorld.cpp | 77 +++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 10 +++++ test/hello-test.cpp | 26 +++++++++++++ test/lit.cfg.py | 45 ++++++++++++++++++++++ test/lit.site.cfg.py.in | 15 ++++++++ 8 files changed, 344 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 lib/CMakeLists.txt create mode 100644 lib/HelloWorld.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/hello-test.cpp create mode 100644 test/lit.cfg.py create mode 100644 test/lit.site.cfg.py.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..40750c9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,85 @@ +cmake_minimum_required(VERSION 3.4.3) +project(clang-tutor) + +#=============================================================================== +# 1. VERIFY CLANG INSTALLATION DIR +#=============================================================================== +# Set this to a valid LLVM installation dir +set(CT_LLVM_INSTALL_DIR "" CACHE PATH "LLVM installation directory") + +# A bit of a sanity checking +set(CT_LLVM_INCLUDE_DIR "${CT_LLVM_INSTALL_DIR}/include/llvm") +if(NOT EXISTS "${CT_LLVM_INCLUDE_DIR}") +message(FATAL_ERROR + " CT_LLVM_INSTALL_DIR (${CT_LLVM_INCLUDE_DIR}) is invalid.") +endif() + +set(CT_LLVM_CMAKE_FILE "${CT_LLVM_INSTALL_DIR}/lib/cmake/clang/ClangConfig.cmake") +if(NOT EXISTS "${CT_LLVM_CMAKE_FILE}") +message(FATAL_ERROR + " CT_LLVM_CMAKE_FILE (${CT_LLVM_CMAKE_FILE}) is invalid.") +endif() + +#=============================================================================== +# 2. LOAD CLANG CONFIGURATION +# For more: http://llvm.org/docs/CMake.html#embedding-llvm-in-your-project +#=============================================================================== +list(APPEND CMAKE_PREFIX_PATH "${CT_LLVM_INSTALL_DIR}/lib/cmake/llvm/") +list(APPEND CMAKE_PREFIX_PATH "${CT_LLVM_INSTALL_DIR}/lib/cmake/clang/") + +find_package(Clang REQUIRED CONFIG) +message(STATUS "Found Clang ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using ClangConfig.cmake in: ${CT_LLVM_INSTALL_DIR}") + +message("CLANG STATUS: + Includes (clang) ${CLANG_INCLUDE_DIRS} + Includes (llvm) ${LLVM_INCLUDE_DIRS}" +) + +# Compiler flags +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall\ + -fdiagnostics-color=always") + +include_directories("${LLVM_INCLUDE_DIRS};${CLANG_INCLUDE_DIRS}") + +#=============================================================================== +# 3. CLANG-TUTOR BUILD CONFIGURATION +#=============================================================================== +# Use the same C++ standard as LLVM does +set(CMAKE_CXX_STANDARD 14 CACHE STRING "") + +# Build type +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE + STRING "Build type (default Debug):" FORCE) +endif() + +# Compiler flags +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall\ + -fdiagnostics-color=always") + +# LLVM/Clang is normally built without RTTI. Be consistent with that. +if(NOT LLVM_ENABLE_RTTI) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") +endif() + +# -fvisibility-inlines-hidden is set when building LLVM and on Darwin warnings +# are triggered if llvm-tutor is built without this flag (though otherwise it +# builds fine). For consistency, add it here too. +include(CheckCXXCompilerFlag) +check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG) +if (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG} EQUAL "1") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") +endif() + +# Set the build directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") + +#=============================================================================== +# 4. ADD SUB-TARGETS +# Doing this at the end so that all definitions and link/include paths are +# available for the sub-projects. +#=============================================================================== +add_subdirectory(test) +add_subdirectory(lib) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a57266d --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +clang-tutor +========= +[![Build Status](https://github.com/banach-space/clang-tutor/workflows/x86-Ubuntu/badge.svg?branch=master)](https://github.com/banach-space/clang-tutor/actions?query=workflow%3Ax86-Ubuntu+branch%3Amaster) +[![Build Status](https://github.com/banach-space/clang-tutor/workflows/x86-Darwin/badge.svg?branch=master)](https://github.com/banach-space/clang-tutor/actions?query=workflow%3Ax86-Darwin+branch%3Amaster) + + +Example Clang plugins - based on **Clang 10** + +**clang-tutor** is a collection of self-contained reference Clang plugins. It's a +tutorial that targets novice and aspiring Clang developers. Key features: + * **Complete** - includes `CMake` build scripts, LIT tests and CI set-up + * **Out of source** - builds against a binary Clang/LLVM installation (no + need to build Clang from sources) + * **Modern** - based on the latest version of Clang/LLVM (and updated with + every release) + +Status +====== +**Work in progress** + +Everything builds fine and all tests pass. This project is under active +development. More content to be added soon. + +Building & Testing +=================== +You can build **clang-tutor** (and all the provided plugins) as follows: +```bash +cd +cmake -DCT_LLVM_INSTALL_DIR= +make +``` + +The `CT_LLVM_INSTALL_DIR` variable should be set to the root of either the +installation or build directory of LLVM 10. It is used to locate the +corresponding `LLVMConfig.cmake` script that is used to set the include and +library paths. + +In order to run the tests, you need to install **llvm-lit** (aka **lit**). It's +not bundled with LLVM 10 packages, but you can install it with **pip**: +```bash +# Install lit - note that this installs lit globally +pip install lit +``` +Running the tests is as simple as: +```bash +$ lit /test +``` +VoilĂ ! You should see all tests passing. + +License +======== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to http://unlicense.org/ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..448e4ca --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,14 @@ +# THE LIST OF PLUGINS AND THE CORRESPONDING SOURCE FILES +# ====================================================== +add_library( + HelloWorld + SHARED + HelloWorld.cpp + ) + +# CONFIGURE THE PLUGIN LIBRARIES +# ============================== +target_link_libraries( + HelloWorld + "$<$:-undefined dynamic_lookup>" + ) diff --git a/lib/HelloWorld.cpp b/lib/HelloWorld.cpp new file mode 100644 index 0000000..92be036 --- /dev/null +++ b/lib/HelloWorld.cpp @@ -0,0 +1,77 @@ +//============================================================================== +// FILE: +// HelloWorld.cpp +// +// DESCRIPTION: +// +// USAGE: +// +// License: The Unlicense +//============================================================================== +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; + +//----------------------------------------------------------------------------- +// HelloWorld implementation +//----------------------------------------------------------------------------- +class HelloWorld : public RecursiveASTVisitor { +public: + explicit HelloWorld(ASTContext *Context) : Context(Context) {} + bool VisitCXXRecordDecl(CXXRecordDecl *Decl); + +private: + ASTContext *Context; +}; + +bool HelloWorld::VisitCXXRecordDecl(CXXRecordDecl *Declaration) { + FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc()); + if (FullLocation.isValid()) + llvm::outs() << "(clang-tutor) Hello from: " << Declaration->getName() + << "\n" + << "(clang-tutor) location: " + << FullLocation.getSpellingLineNumber() << ":" + << " " << FullLocation.getSpellingColumnNumber() << "\n" + << "(clang-tutor) number of virtual bases classes " + << Declaration->getNumBases() << "\n"; + return true; +} + +class HelloWorldASTConsumer : public clang::ASTConsumer { +public: + explicit HelloWorldASTConsumer(ASTContext *Ctx) : Visitor(Ctx) {} + + void HandleTranslationUnit(clang::ASTContext &Ctx) override { + Visitor.TraverseDecl(Ctx.getTranslationUnitDecl()); + } + +private: + HelloWorld Visitor; +}; + +//----------------------------------------------------------------------------- +// FrotendAction +//----------------------------------------------------------------------------- +class FindNamedClassAction : public clang::PluginASTAction { +public: + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override { + return std::unique_ptr( + std::make_unique(&Compiler.getASTContext())); + } + bool ParseArgs(const CompilerInstance &CI, + const std::vector &args) override { + return true; + } +}; + +//----------------------------------------------------------------------------- +// Registration +//----------------------------------------------------------------------------- +static FrontendPluginRegistry::Add + X(/*Name=*/"hello-world", /*Description:=*/"The HelloWorld plugin"); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..529118c --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,10 @@ +set(CT_TEST_SHLIBEXT "${CMAKE_SHARED_LIBRARY_SUFFIX}") + +set(CT_TEST_SITE_CFG_INPUT "${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in") +set(CT_TEST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +set(LIT_SITE_CFG_IN_HEADER "## Autogenerated from ${CT_TEST_SITE_CFG_INPUT}\n## Do not edit!") + +configure_file("${CT_TEST_SITE_CFG_INPUT}" + "${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py" @ONLY +) diff --git a/test/hello-test.cpp b/test/hello-test.cpp new file mode 100644 index 0000000..f1782d8 --- /dev/null +++ b/test/hello-test.cpp @@ -0,0 +1,26 @@ +// RUN: clang -cc1 -load %shlibdir/libHelloWorld%shlibext -plugin hello-world %s 2>&1 | FileCheck %s + +class Base1 { +}; + +class Base2 { +}; + +class Derived1 : public Base1 { +}; + +class Derived2 : public Base1, public Base2 { +}; + +// CHECK: (clang-tutor) Hello from: Base1 +// CHECK: (clang-tutor) location: 3: 1 +// CHECK: (clang-tutor) number of virtual bases classes 0 +// CHECK: (clang-tutor) Hello from: Base2 +// CHECK: (clang-tutor) location: 6: 1 +// CHECK: (clang-tutor) number of virtual bases classes 0 +// CHECK: (clang-tutor) Hello from: Derived1 +// CHECK: (clang-tutor) location: 9: 1 +// CHECK: (clang-tutor) number of virtual bases classes 1 +// CHECK: (clang-tutor) Hello from: Derived2 +// CHECK: (clang-tutor) location: 12: 1 +// CHECK: (clang-tutor) number of virtual bases classes 2 diff --git a/test/lit.cfg.py b/test/lit.cfg.py new file mode 100644 index 0000000..77df350 --- /dev/null +++ b/test/lit.cfg.py @@ -0,0 +1,45 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import platform + +import lit.formats +# Global instance of LLVMConfig provided by lit +from lit.llvm import llvm_config +from lit.llvm.subst import ToolSubst + +# name: The name of this test suite. +# (config is an instance of TestingConfig created when discovering tests) +config.name = 'CLANG-TUTOR' + +# testFormat: The test format to use to interpret tests. +# As per shtest.py (my formatting): +# ShTest is a format with one file per test. This is the primary format for +# regression tests (...) +# I couldn't find any more documentation on this, but it seems to be exactly +# what we want here. +config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell) + +# suffixes: A list of file extensions to treat as test files. This is overriden +# by individual lit.local.cfg files in the test subdirectories. +config.suffixes = ['.cpp'] + +# test_source_root: The root path where tests are located. +config.test_source_root = os.path.dirname(__file__) + +# excludes: A list of directories to exclude from the testsuite. The 'Inputs' +# subdirectories contain auxiliary inputs for various tests in their parent +# directories. +config.excludes = ['Inputs'] + +# The list of tools required for testing - prepend them with the path specified +# during configuration (i.e. LT_LLVM_TOOLS_DIR/bin) +tools = ["FileCheck", "clang"] +llvm_config.add_tool_substitutions(tools, config.llvm_tools_dir) + +# The LIT variable to hold the file extension for shared libraries (this is +# platform dependent) +config.substitutions.append(('%shlibext', config.llvm_shlib_ext)) +# The LIT variable to hold the location of plugins/libraries +config.substitutions.append(('%shlibdir', config.llvm_shlib_dir)) diff --git a/test/lit.site.cfg.py.in b/test/lit.site.cfg.py.in new file mode 100644 index 0000000..3678fc0 --- /dev/null +++ b/test/lit.site.cfg.py.in @@ -0,0 +1,15 @@ +import sys + +config.llvm_tools_dir = "@CT_LLVM_INSTALL_DIR@/bin" +config.llvm_shlib_ext = "@CT_TEST_SHLIBEXT@" +config.llvm_shlib_dir = "@CMAKE_LIBRARY_OUTPUT_DIRECTORY@" + +import lit.llvm +# lit_config is a global instance of LitConfig +lit.llvm.initialize(lit_config, config) + +# test_exec_root: The root path where tests should be run. +config.test_exec_root = os.path.join("@CMAKE_CURRENT_BINARY_DIR@") + +# Let the main config do the real work. +lit_config.load_config(config, "@CT_TEST_SRC_DIR@/lit.cfg.py")