Skip to content

Commit

Permalink
Add the hello-world plugin
Browse files Browse the repository at this point in the history
Adds the first plugin, the first test, LIT config scripts and updates
README.
  • Loading branch information
banach-space committed Apr 4, 2020
1 parent 9ef3f97 commit 297a4ba
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 0 deletions.
85 changes: 85 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <build/dir>
cmake -DCT_LLVM_INSTALL_DIR=<installation/dir/of/llvm/10> <source/dir/clang-tutor>
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 <build_dir>/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/
14 changes: 14 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
"$<$<PLATFORM_ID:Darwin>:-undefined dynamic_lookup>"
)
77 changes: 77 additions & 0 deletions lib/HelloWorld.cpp
Original file line number Diff line number Diff line change
@@ -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<HelloWorld> {
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<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &Compiler,
llvm::StringRef InFile) override {
return std::unique_ptr<clang::ASTConsumer>(
std::make_unique<HelloWorldASTConsumer>(&Compiler.getASTContext()));
}
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &args) override {
return true;
}
};

//-----------------------------------------------------------------------------
// Registration
//-----------------------------------------------------------------------------
static FrontendPluginRegistry::Add<FindNamedClassAction>
X(/*Name=*/"hello-world", /*Description:=*/"The HelloWorld plugin");
10 changes: 10 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
26 changes: 26 additions & 0 deletions test/hello-test.cpp
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions test/lit.cfg.py
Original file line number Diff line number Diff line change
@@ -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))
15 changes: 15 additions & 0 deletions test/lit.site.cfg.py.in
Original file line number Diff line number Diff line change
@@ -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")

0 comments on commit 297a4ba

Please sign in to comment.