Add CMake macro and actually test the generated code

This commit is contained in:
Martchus 2017-10-24 01:00:40 +02:00
parent 14428c8890
commit 8679263f09
11 changed files with 196 additions and 41 deletions

View File

@ -27,8 +27,10 @@ else()
message(FATAL_ERROR "Specified directory for c++utilities sources \"${BUNDLED_CPP_UTILITIES_PATH}\" does not exist.")
endif()
# add helper library for using RapidJSON
# add header-only library containing JSONSerializable and helper for using RapidJSON
add_subdirectory(lib)
# allow inclusion of CMake modules from that lib in other parts of the project
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/cmake/modules" "${CMAKE_MODULE_PATH}")
# add code generator
add_subdirectory(moc)

View File

@ -16,6 +16,9 @@ set(TEST_SRC_FILES
tests/cppunit.cpp
tests/jsonreflector.cpp
)
set(CMAKE_MODULE_FILES
cmake/modules/ReflectionGenerator.cmake
)
set(DOC_FILES
README.md
)

View File

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if(DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
return()
endif()
set(REFLECTION_GENERATOR_MODULE_LOADED YES)
# find code generator
set(REFLECTION_GENERATOR_EXECUTABLE reflective_rapidjson_moc)
if(CMAKE_CROSSCOMPILING OR NOT TARGET "${REFLECTION_GENERATOR_EXECUTABLE}")
# find "reflective_rapidjson_moc" from path
find_program(REFLECTION_GENERATOR_EXECUTABLE "${REFLECTION_GENERATOR_EXECUTABLE}")
endif()
if(NOT REFLECTION_GENERATOR_EXECUTABLE)
message(FATAL_ERROR "Unable to find executable of generator for reflection code.")
endif()
# define helper functions
include(CMakeParseArguments)
function(add_reflection_generator_invocation)
# parse arguments
set(OPTIONAL_ARGS)
set(ONE_VALUE_ARGS OUTPUT_FILE OUTPUT_NAME)
set(MULTI_VALUE_ARGS INPUT_FILES GENERATORS OUTPUT_LISTS)
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
# determine file name or file path if none specified
if(OUTPUT_FILE AND OUTPUT_NAME)
message(FATAL_ERROR "Specify either OUTPUT_NAME or OUTPUT_FILE but not both.")
endif()
if(NOT ARGS_OUTPUT_FILE)
if(NOT ARGS_OUTPUT_NAME)
set(ARGS_OUTPUT_NAME "reflection.h")
endif()
set(ARGS_OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUTPUT_NAME}")
endif()
add_custom_command(
OUTPUT "${ARGS_OUTPUT_FILE}"
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}"
-o "${ARGS_OUTPUT_FILE}"
-i ${ARGS_INPUT_FILES}
-g ${ARGS_GENERATORS}
DEPENDS "${ARGS_INPUT_FILES}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating reflection code"
)
# append the output file to lists specified via OUTPUT_LISTS
if(ARGS_OUTPUT_LISTS)
foreach(OUTPUT_LIST ${ARGS_OUTPUT_LISTS})
set("${OUTPUT_LIST}" "${${OUTPUT_LIST}};${ARGS_OUTPUT_FILE}" PARENT_SCOPE)
endforeach()
endif()
endfunction()

View File

@ -39,6 +39,14 @@ list(APPEND PRIVATE_LIBRARIES clangTooling)
# also add reflective_rapidjson
list(APPEND PRIVATE_LIBRARIES reflective_rapidjson)
# trigger code generator for tests
include(ReflectionGenerator)
add_reflection_generator_invocation(
INPUT_FILES tests/overall.cpp
GENERATORS json
OUTPUT_LISTS TEST_SRC_FILES
)
# include modules to apply configuration
include(BasicConfig)
include(WindowsResources)
@ -47,4 +55,3 @@ include(TestTarget)
include(ShellCompletion)
include(Doxygen)
include(ConfigHeader)

View File

@ -1,5 +1,6 @@
#include "./frontendaction.h"
#include "./consumer.h"
#include "./generator.h"
#include <c++utilities/application/global.h>
@ -17,11 +18,13 @@ FrontendAction::CreateASTConsumer(clang::CompilerInstance &compilerInstance, llv
{
VAR_UNUSED(inputFile)
// propagate compiler instance to factory
m_factory.setCompilerInstance(&compilerInstance);
// configure frontent, preporocessor and language options
clang::FrontendOptions &frontendOpts = compilerInstance.getFrontendOpts();
clang::Preprocessor &pp = compilerInstance.getPreprocessor();
clang::LangOptions &lngOpts = compilerInstance.getLangOpts();
// configure frontent, preporocessor and language options
frontendOpts.SkipFunctionBodies = true;
pp.enableIncrementalProcessing(true);
pp.SetSuppressIncludeNotFoundError(true);

View File

@ -83,24 +83,24 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
return;
}
// put everything into ReflectiveRapidJSON::Reflector
// put everything into namespace ReflectiveRapidJSON::Reflector
os << "namespace ReflectiveRapidJSON {\n"
"namespace Reflector {\n";
"namespace Reflector {\n\n";
// add push and pull functions for each class, for an example of the resulting
// output, see ../lib/tests/jsonserializable.cpp (code under comment "pretend serialization code...")
for (const RelevantClass &relevantClass : m_relevantClasses) {
// print push method
os << "template <> inline void push<::" << relevantClass.qualifiedName << ">(const " << relevantClass.qualifiedName
<< " &reflectable, Value::Object &value, Document::AllocatorType &allocator)\n{\n";
os << "template <> inline void push<::" << relevantClass.qualifiedName << ">(const ::" << relevantClass.qualifiedName
<< " &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)\n{\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
}
os << "}\n";
// print pull method
os << "template <> inline void pull<::" << relevantClass.qualifiedName << ">(" << relevantClass.qualifiedName
<< " &reflectable, const GenericValue<UTF8<char>>::ConstObject &value)\n{\n";
os << "template <> inline void pull<::" << relevantClass.qualifiedName << ">(::" << relevantClass.qualifiedName
<< " &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)\n{\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value);\n";
}
@ -126,10 +126,13 @@ CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
fileManager.Retain();
}
CodeFactory::CodeFactory(const char *applicationPath, const std::vector<const char *> &sourceFiles, std::ostream &os)
CodeFactory::CodeFactory(
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<const char *> &clangOptions, std::ostream &os)
: m_applicationPath(applicationPath)
, m_sourceFiles(sourceFiles)
, m_clangOptions(clangOptions)
, m_os(os)
, m_compilerInstance(nullptr)
{
}
@ -145,8 +148,9 @@ std::vector<string> CodeFactory::makeClangArgs() const
static const initializer_list<const char *> flags
= { m_applicationPath, "-x", "c++", "-fPIE", "-fPIC", "-Wno-microsoft", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" };
vector<string> clangArgs;
clangArgs.reserve(flags.size() + m_sourceFiles.size());
clangArgs.reserve(flags.size() + m_clangOptions.size() + m_sourceFiles.size());
clangArgs.insert(clangArgs.end(), flags.begin(), flags.end());
clangArgs.insert(clangArgs.end(), m_clangOptions.cbegin(), m_clangOptions.cend());
clangArgs.insert(clangArgs.end(), m_sourceFiles.cbegin(), m_sourceFiles.cend());
return clangArgs;
}

View File

@ -10,16 +10,19 @@ namespace clang {
class Decl;
class NamedDecl;
class CXXRecordDecl;
class CompilerInstance;
} // namespace clang
namespace ReflectiveRapidJSON {
class CodeFactory;
/*!
* \brief The CodeGenerator class is the base for generators used by the CodeFactory class.
*/
class CodeGenerator {
public:
CodeGenerator();
CodeGenerator(CodeFactory &factory);
virtual ~CodeGenerator();
virtual void addDeclaration(clang::Decl *decl);
@ -28,20 +31,30 @@ public:
virtual void generate(std::ostream &os) const = 0;
protected:
CodeFactory &factory() const;
static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
private:
CodeFactory &m_factory;
};
inline CodeGenerator::CodeGenerator()
inline CodeGenerator::CodeGenerator(CodeFactory &factory)
: m_factory(factory)
{
}
inline CodeFactory &CodeGenerator::factory() const
{
return m_factory;
}
/*!
* \brief The JSONSerializationCodeGenerator class generates code for JSON (de)serialization
* of objects inheriting from an instantiation of JSONSerializable.
*/
class JSONSerializationCodeGenerator : public CodeGenerator {
public:
JSONSerializationCodeGenerator();
JSONSerializationCodeGenerator(CodeFactory &factory);
void addDeclaration(clang::Decl *decl) override;
void generate(std::ostream &os) const override;
@ -57,7 +70,8 @@ private:
std::vector<RelevantClass> m_relevantClasses;
};
inline JSONSerializationCodeGenerator::JSONSerializationCodeGenerator()
inline JSONSerializationCodeGenerator::JSONSerializationCodeGenerator(CodeFactory &factory)
: CodeGenerator(factory)
{
}
@ -75,15 +89,18 @@ inline JSONSerializationCodeGenerator::RelevantClass::RelevantClass(const std::s
*/
class CodeFactory {
public:
CodeFactory(const char *applicationPath, const std::vector<const char *> &sourceFiles, std::ostream &os);
CodeFactory(
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<const char *> &clangOptions, std::ostream &os);
~CodeFactory();
std::vector<std::unique_ptr<CodeGenerator>> &generators();
const std::vector<std::unique_ptr<CodeGenerator>> &generators() const;
template <typename GeneratorType> void addGenerator();
void addDeclaration(clang::Decl *decl);
bool readAST();
bool generate() const;
clang::CompilerInstance *compilerInstance();
void setCompilerInstance(clang::CompilerInstance *compilerInstance);
private:
struct ToolInvocation;
@ -92,14 +109,16 @@ private:
const char *const m_applicationPath;
const std::vector<const char *> &m_sourceFiles;
const std::vector<const char *> &m_clangOptions;
std::ostream &m_os;
std::vector<std::unique_ptr<CodeGenerator>> m_generators;
std::unique_ptr<ToolInvocation> m_toolInvocation;
clang::CompilerInstance *m_compilerInstance;
};
inline std::vector<std::unique_ptr<CodeGenerator>> &CodeFactory::generators()
template <typename GeneratorType> void CodeFactory::addGenerator()
{
return m_generators;
m_generators.emplace_back(std::make_unique<GeneratorType>(*this));
}
inline const std::vector<std::unique_ptr<CodeGenerator>> &CodeFactory::generators() const
@ -107,6 +126,16 @@ inline const std::vector<std::unique_ptr<CodeGenerator>> &CodeFactory::generator
return m_generators;
}
inline clang::CompilerInstance *CodeFactory::compilerInstance()
{
return m_compilerInstance;
}
inline void CodeFactory::setCompilerInstance(clang::CompilerInstance *compilerInstance)
{
m_compilerInstance = compilerInstance;
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_GENERATOR_H

View File

@ -29,17 +29,16 @@ int main(int argc, char *argv[])
Argument inputFilesArg("input-files", 'i', "specifies the input files");
inputFilesArg.setValueNames({ "path" });
inputFilesArg.setRequiredValueCount(Argument::varValueCount);
Argument outputFileArg("output-file", 'o', "specifies the output file");
outputFileArg.setValueNames({ "path" });
outputFileArg.setRequiredValueCount(1);
outputFileArg.setCombinable(true);
Argument generatorsArgs("generators", 'g', "specifies the generators (by default all generators are enabled)");
generatorsArgs.setValueNames({ "json" });
generatorsArgs.setRequiredValueCount(Argument::varValueCount);
generatorsArgs.setCombinable(true);
ConfigValueArgument outputFileArg("output-file", 'o', "specifies the output file", { "path" });
Argument generatorsArg("generators", 'g', "specifies the generators (by default all generators are enabled)");
generatorsArg.setValueNames({ "json" });
generatorsArg.setPreDefinedCompletionValues({ "json" });
generatorsArg.setRequiredValueCount(Argument::varValueCount);
generatorsArg.setCombinable(true);
ConfigValueArgument clangOptionsArg("clang-opt", 'c', "specifies an argument to be passed to Clang", { "option" });
HelpArgument helpArg(parser);
NoColorArgument noColorArg;
parser.setMainArguments({ &inputFilesArg, &outputFileArg, &noColorArg, &helpArg });
parser.setMainArguments({ &inputFilesArg, &outputFileArg, &generatorsArg, &clangOptionsArg, &noColorArg, &helpArg });
// parse arguments
parser.parseArgsOrExit(argc, argv);
@ -57,21 +56,22 @@ int main(int argc, char *argv[])
ofstream outputFile;
if (outputFileArg.isPresent()) {
outputFile.exceptions(ios_base::badbit | ios_base::failbit);
outputFile.open(outputFileArg.values(0).front(), ios_base::out | ios_base::binary);
outputFile.open(outputFileArg.values(0).front(), ios_base::out | ios_base::trunc | ios_base::binary);
os = &outputFile;
} else {
os = &cout;
}
// configure code generator
CodeFactory factory(parser.executable(), inputFilesArg.values(0), *os);
auto &generators(factory.generators());
vector<const char *> defaultClangOptions;
CodeFactory factory(
parser.executable(), inputFilesArg.values(0), clangOptionsArg.isPresent() ? clangOptionsArg.values(0) : defaultClangOptions, *os);
// add only specified generators if the --generator argument is present
if (generatorsArgs.isPresent()) {
if (generatorsArg.isPresent()) {
// find and construct generators by name
for (vector<const char *> generatorName : generatorsArgs.values(0)) {
for (const char *generatorName : generatorsArg.values(0)) {
if (!strcmp(generatorName, "json")) {
generators.emplace_back(std::make_unique<JSONSerializationCodeGenerator>());
factory.addGenerator<JSONSerializationCodeGenerator>();
} else {
cerr << Phrases::Error << "The specified generator \"" << generatorName << "\" does not exist." << Phrases::EndFlush;
return -5;
@ -79,7 +79,7 @@ int main(int argc, char *argv[])
}
} else {
// add default generators
generators.emplace_back(std::make_unique<JSONSerializationCodeGenerator>());
factory.addGenerator<JSONSerializationCodeGenerator>();
}
// read AST elements from input files

View File

@ -19,11 +19,13 @@ struct Person : public ReflectiveRapidJSON::JSONSerializable<Person>
struct NonReflectableClass
{
int foo;
}
};
struct SomeOtherNonReflectableClass : public NonReflectableClass
{
int bar;
};
}
namespace TestNamespace2 {

View File

@ -1,11 +1,12 @@
namespace ReflectiveRapidJSON {
namespace Reflector {
template <> inline void push<::TestNamespace1::Person>(const TestNamespace1::Person &reflectable, Value::Object &value, Document::AllocatorType &allocator)
template <> inline void push<::TestNamespace1::Person>(const ::TestNamespace1::Person &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
push(reflectable.age, "age", value, allocator);
push(reflectable.alive, "alive", value, allocator);
}
template <> inline void pull<::TestNamespace1::Person>(TestNamespace1::Person &reflectable, const GenericValue<UTF8<char>>::ConstObject &value)
template <> inline void pull<::TestNamespace1::Person>(::TestNamespace1::Person &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)
{
pull(reflectable.age, "age", value);
pull(reflectable.alive, "alive", value);

View File

@ -1,5 +1,7 @@
#include "../generator.h"
#include "../../lib/jsonserializable.h"
#include "resources/config.h"
#include <c++utilities/conversion/stringbuilder.h>
@ -22,6 +24,16 @@ using namespace TestUtilities;
using namespace ConversionUtilities;
using namespace ReflectiveRapidJSON;
/*!
* \brief The TestStruct struct inherits from JSONSerializable and should hence have functional fromJson()
* and toJson() methods. This is asserted in OverallTests::testIncludingGeneratedHeader();
*/
struct TestStruct : public JSONSerializable<TestStruct> {
int someInt = 0;
string someString = "foo";
string yetAnotherString = "bar";
};
/*!
* \brief The OverallTests class tests the overall functionality of the code generator (CLI and generator itself).
*/
@ -29,6 +41,7 @@ class OverallTests : public TestFixture {
CPPUNIT_TEST_SUITE(OverallTests);
CPPUNIT_TEST(testGeneratorItself);
CPPUNIT_TEST(testCLI);
CPPUNIT_TEST(testIncludingGeneratedHeader);
CPPUNIT_TEST_SUITE_END();
public:
@ -37,6 +50,7 @@ public:
void testGeneratorItself();
void testCLI();
void testIncludingGeneratedHeader();
private:
vector<string> m_expectedCode;
@ -81,19 +95,28 @@ void OverallTests::tearDown()
{
}
/*!
* \brief Tests whether the generator works by using it directly.
*/
void OverallTests::testGeneratorItself()
{
const string inputFilePath(testFilePath("some_structs.h"));
const vector<const char *> inputFiles{ inputFilePath.data() };
const vector<const char *> clangOptions{};
stringstream buffer;
CodeFactory factory(TestApplication::appPath(), inputFiles, buffer);
factory.generators().emplace_back(make_unique<JSONSerializationCodeGenerator>());
CodeFactory factory(TestApplication::appPath(), inputFiles, clangOptions, buffer);
factory.addGenerator<JSONSerializationCodeGenerator>();
CPPUNIT_ASSERT(factory.readAST());
CPPUNIT_ASSERT(factory.generate());
assertEqualityLinewise(m_expectedCode, toArrayOfLines(buffer.str()));
}
/*!
* \brief Test the generator via CLI.
* \remarks Only available under UNIX (like) systems so far, because TESTUTILS_ASSERT_EXEC has not been implemented
* for other platforms.
*/
void OverallTests::testCLI()
{
#ifdef PLATFORM_UNIX
@ -105,3 +128,28 @@ void OverallTests::testCLI()
assertEqualityLinewise(m_expectedCode, toArrayOfLines(stdout));
#endif
}
/*!
* \brief Tests whether the generated reflection code actually works.
*/
void OverallTests::testIncludingGeneratedHeader()
{
TestStruct test;
test.someInt = 42;
test.someString = "the answer";
test.yetAnotherString = "but what was the question";
const string expectedJSON("{\"someInt\":42,\"someString\":\"the answer\",\"yetAnotherString\":\"but what was the question\"}");
// test serialization
CPPUNIT_ASSERT_EQUAL(expectedJSON, string(test.toJson().GetString()));
// test deserialization
const TestStruct parsedTest(TestStruct::fromJson(expectedJSON));
CPPUNIT_ASSERT_EQUAL(test.someInt, parsedTest.someInt);
CPPUNIT_ASSERT_EQUAL(test.someString, parsedTest.someString);
CPPUNIT_ASSERT_EQUAL(test.yetAnotherString, parsedTest.yetAnotherString);
}
// include file required for reflection of TestStruct; generation of this header is triggered using
// the CMake function add_reflection_generator_invocation()
#include "reflection.h"