Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
RichardLuo0 committed Jul 8, 2024
0 parents commit d172867
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 0 deletions.
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BasedOnStyle: "Google"
2 changes: 2 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CompileFlags:
Add: [-std=c++20, -Wextra]
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.*
!.clang-format
!.gitignore
!.clangd
build
tests
compile_commands.json
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# cxx-module-generator
A tool to easily generate module library from any traditional C++ header library.

It converts a header file like
```cpp
// lib.hpp
namespace A {
namespace B {
class C {
int a, b;
};
} // namespace B
} // namespace A
```
into
```cpp
// lib.cppm
module;
#include "lib.hpp"
export module test;
namespace A {
namespace B {
export using A::B::C;
}
}
```
Then you can use `clang++ -march=native -std=c++20 --precompile -c lib.cppm -o lib.pcm` to convert the cppm into pcm.\
At last, you can use the pcm as a C++20 module:
```cpp
import test;

int main() {
A::B::C c;
return 0;
}
```
By using modules, the build time decreases dramatically.

## Usage
For example, to convert boost/json.hpp, the module name is boost.json, use `cmg boost/json.hpp --name=boost.json --namespace=boost::json --`. Do not forget the `--` at the end of command. It means ignore any `compile_commands.json` in current directory (which most probably you do not have one).

Most options are the same as any other clang tools.
Use `cmg --help` for more help.

Use `find path/in/subtree -name '*.hpp' | xargs cmg --namespace=a::b --` if you need to run on multiple files.

## Install
**Clang installation is mandatory**
* Download the file from release section
* Put it next to clang executable (for msys2, it should be `msys2/clang64/bin`)

## Tested library
* boost.json
49 changes: 49 additions & 0 deletions build.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifdef _WIN32
#define POSTFIX ".exe"
#else
#define POSTFIX ""
#endif

import std;
import makeDotCpp;
import makeDotCpp.project;
import makeDotCpp.compiler.Clang;
import makeDotCpp.fileProvider.Glob;
import makeDotCpp.builder;

#include "project.json.hpp"

using namespace makeDotCpp;

int main(int argc, const char **argv) {
std::deque<std::shared_ptr<Export>> packages;
populatePackages(packages);

Project::OptionParser op;
op.parse(argc, argv);

auto compiler = std::make_shared<Clang>();
compiler->addOption("-march=native -std=c++20 -O3 -Wall")
.addLinkOption("-lclang-cpp")
.addLinkOption("-lLLVM-18")
.addLinkOption("-Wl,--stack=4194304");

ExeBuilder builder("cmg");
builder.setCompiler(compiler)
.addSrc(Glob("src/**/*.cppm"))
.addSrc("src/main.cpp");

for (auto &package : packages) {
builder.dependOn(package);
}

Project()
.setName("cxx-module-generator")
.setBuild([&](const Context &ctx) {
auto future = builder.build(ctx);
future.get();
std::cout << "\033[0;32mDone\033[0m" << std::endl;
})
.setInstall([](const Context &) {})
.run(op);
}
8 changes: 8 additions & 0 deletions project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "cxx-module-generator",
"dev": {
"packages": [
"makeDotCpp"
]
}
}
148 changes: 148 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include <clang/AST/Decl.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/AST/Type.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <llvm/Support/raw_ostream.h>

#include <filesystem>
#include <fstream>
#include <iostream>

using namespace clang;
using namespace tooling;
namespace fs = std::filesystem;

static llvm::cl::OptionCategory toolCategory("cxx-module-generator");
static llvm::cl::opt<std::string> moduleName("name",
llvm::cl::desc("Module name"),
llvm::cl::cat(toolCategory));
static llvm::cl::opt<std::string> output("o",
llvm::cl::desc("Specify output path"),
llvm::cl::cat(toolCategory));
static llvm::cl::opt<std::string> nsFilter(
"namespace", llvm::cl::desc("Filter symbol by namespace"),
llvm::cl::cat(toolCategory));

class ModuleWrapper {
private:
std::string content;

public:
ModuleWrapper(const fs::path &originalFile) {
content += "module;\n#include \"" + originalFile.generic_string() + "\"\n";
content += "export module " +
(!moduleName.empty() ? moduleName.getValue()
: originalFile.stem().string()) +
";\n";
}

void openNamespace(NamespaceDecl *decl) {
content += "namespace " + decl->getNameAsString() + " {\n";
}

void closeNamespace(NamespaceDecl *) { content += "}\n"; }

void addSymbol(NamedDecl *decl) {
if (decl != nullptr && decl->isFirstDecl())
content += "export using " + decl->getQualifiedNameAsString() + ";\n";
}

const std::string &getContent() const { return content; }
};

class FindAllSymbols : public RecursiveASTVisitor<FindAllSymbols> {
private:
using Base = RecursiveASTVisitor<FindAllSymbols>;

ModuleWrapper &wrapper;

public:
FindAllSymbols(ModuleWrapper &wrapper) : wrapper(wrapper) {}

bool TraverseNamespaceDecl(NamespaceDecl *decl) {
if (nsFilter.find(decl->getName()) != std::string::npos) {
wrapper.openNamespace(decl);
Base::TraverseNamespaceDecl(decl);
wrapper.closeNamespace(decl);
}
return true;
}

bool TraverseDecl(Decl *decl) {
switch (decl->getKind()) {
case Decl::TranslationUnit:
return TraverseTranslationUnitDecl(
static_cast<TranslationUnitDecl *>(decl));
case Decl::Namespace:
return TraverseNamespaceDecl(static_cast<NamespaceDecl *>(decl));
default:
break;
}
switch (decl->getKind()) {
#define ABSTRACT_DECL(DECL)
#define DECL(CLASS, BASE) \
case Decl::CLASS: \
if (!WalkUpFrom##CLASS##Decl(static_cast<CLASS##Decl *>(decl))) \
return false; \
break;
#include "clang/AST/DeclNodes.inc"
}
return true;
}

#define VISIT_DECL(TYPE) \
bool Visit##TYPE##Decl(TYPE##Decl *decl) { \
if (!decl->isImplicit() && \
decl->getQualifiedNameAsString().find(nsFilter) != std::string::npos) \
wrapper.addSymbol(decl); \
return true; \
}

VISIT_DECL(Tag);
VISIT_DECL(TypedefName);
VISIT_DECL(Function);
#undef VISIT_DECL
};

class CreateModule : public ASTConsumer {
private:
fs::path path;

public:
CreateModule(const fs::path &path) : path(fs::canonical(path)) {}

void HandleTranslationUnit(ASTContext &ctx) override {
ModuleWrapper wrapper(path);
FindAllSymbols visitor(wrapper);
visitor.TraverseDecl(ctx.getTranslationUnitDecl());
auto modulePath = output.getValue() / path.stem() += ".cppm";
fs::create_directories(modulePath.parent_path());
std::ofstream os{modulePath, std::ios::binary};
os.exceptions(std::ios::failbit);
os << wrapper.getContent();
}
};

template <class Consumer>
class SuppressIncludeNotFound : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(
CompilerInstance &, llvm::StringRef path) override {
return std::make_unique<Consumer>(path.str());
}
};

int main(int argc, const char **argv) {
output.setInitialValue(fs::current_path().generic_string());
auto expectedParser = CommonOptionsParser::create(argc, argv, toolCategory);
if (!expectedParser) {
llvm::errs() << expectedParser.takeError();
return 1;
}
CommonOptionsParser &op = expectedParser.get();
ClangTool tool(op.getCompilations(), op.getSourcePathList());
return tool.run(
newFrontendActionFactory<SuppressIncludeNotFound<CreateModule>>().get());
}

0 comments on commit d172867

Please sign in to comment.