Skip to content

Commit d172867

Browse files
committed
init
0 parents  commit d172867

File tree

7 files changed

+268
-0
lines changed

7 files changed

+268
-0
lines changed

.clang-format

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BasedOnStyle: "Google"

.clangd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CompileFlags:
2+
Add: [-std=c++20, -Wextra]

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.*
2+
!.clang-format
3+
!.gitignore
4+
!.clangd
5+
build
6+
tests
7+
compile_commands.json

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# cxx-module-generator
2+
A tool to easily generate module library from any traditional C++ header library.
3+
4+
It converts a header file like
5+
```cpp
6+
// lib.hpp
7+
namespace A {
8+
namespace B {
9+
class C {
10+
int a, b;
11+
};
12+
} // namespace B
13+
} // namespace A
14+
```
15+
into
16+
```cpp
17+
// lib.cppm
18+
module;
19+
#include "lib.hpp"
20+
export module test;
21+
namespace A {
22+
namespace B {
23+
export using A::B::C;
24+
}
25+
}
26+
```
27+
Then you can use `clang++ -march=native -std=c++20 --precompile -c lib.cppm -o lib.pcm` to convert the cppm into pcm.\
28+
At last, you can use the pcm as a C++20 module:
29+
```cpp
30+
import test;
31+
32+
int main() {
33+
A::B::C c;
34+
return 0;
35+
}
36+
```
37+
By using modules, the build time decreases dramatically.
38+
39+
## Usage
40+
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).
41+
42+
Most options are the same as any other clang tools.
43+
Use `cmg --help` for more help.
44+
45+
Use `find path/in/subtree -name '*.hpp' | xargs cmg --namespace=a::b --` if you need to run on multiple files.
46+
47+
## Install
48+
**Clang installation is mandatory**
49+
* Download the file from release section
50+
* Put it next to clang executable (for msys2, it should be `msys2/clang64/bin`)
51+
52+
## Tested library
53+
* boost.json

build.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#ifdef _WIN32
2+
#define POSTFIX ".exe"
3+
#else
4+
#define POSTFIX ""
5+
#endif
6+
7+
import std;
8+
import makeDotCpp;
9+
import makeDotCpp.project;
10+
import makeDotCpp.compiler.Clang;
11+
import makeDotCpp.fileProvider.Glob;
12+
import makeDotCpp.builder;
13+
14+
#include "project.json.hpp"
15+
16+
using namespace makeDotCpp;
17+
18+
int main(int argc, const char **argv) {
19+
std::deque<std::shared_ptr<Export>> packages;
20+
populatePackages(packages);
21+
22+
Project::OptionParser op;
23+
op.parse(argc, argv);
24+
25+
auto compiler = std::make_shared<Clang>();
26+
compiler->addOption("-march=native -std=c++20 -O3 -Wall")
27+
.addLinkOption("-lclang-cpp")
28+
.addLinkOption("-lLLVM-18")
29+
.addLinkOption("-Wl,--stack=4194304");
30+
31+
ExeBuilder builder("cmg");
32+
builder.setCompiler(compiler)
33+
.addSrc(Glob("src/**/*.cppm"))
34+
.addSrc("src/main.cpp");
35+
36+
for (auto &package : packages) {
37+
builder.dependOn(package);
38+
}
39+
40+
Project()
41+
.setName("cxx-module-generator")
42+
.setBuild([&](const Context &ctx) {
43+
auto future = builder.build(ctx);
44+
future.get();
45+
std::cout << "\033[0;32mDone\033[0m" << std::endl;
46+
})
47+
.setInstall([](const Context &) {})
48+
.run(op);
49+
}

project.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "cxx-module-generator",
3+
"dev": {
4+
"packages": [
5+
"makeDotCpp"
6+
]
7+
}
8+
}

src/main.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <clang/AST/Decl.h>
2+
#include <clang/AST/RecursiveASTVisitor.h>
3+
#include <clang/AST/Type.h>
4+
#include <clang/Frontend/CompilerInstance.h>
5+
#include <clang/Tooling/CommonOptionsParser.h>
6+
#include <clang/Tooling/Tooling.h>
7+
#include <llvm/Support/raw_ostream.h>
8+
9+
#include <filesystem>
10+
#include <fstream>
11+
#include <iostream>
12+
13+
using namespace clang;
14+
using namespace tooling;
15+
namespace fs = std::filesystem;
16+
17+
static llvm::cl::OptionCategory toolCategory("cxx-module-generator");
18+
static llvm::cl::opt<std::string> moduleName("name",
19+
llvm::cl::desc("Module name"),
20+
llvm::cl::cat(toolCategory));
21+
static llvm::cl::opt<std::string> output("o",
22+
llvm::cl::desc("Specify output path"),
23+
llvm::cl::cat(toolCategory));
24+
static llvm::cl::opt<std::string> nsFilter(
25+
"namespace", llvm::cl::desc("Filter symbol by namespace"),
26+
llvm::cl::cat(toolCategory));
27+
28+
class ModuleWrapper {
29+
private:
30+
std::string content;
31+
32+
public:
33+
ModuleWrapper(const fs::path &originalFile) {
34+
content += "module;\n#include \"" + originalFile.generic_string() + "\"\n";
35+
content += "export module " +
36+
(!moduleName.empty() ? moduleName.getValue()
37+
: originalFile.stem().string()) +
38+
";\n";
39+
}
40+
41+
void openNamespace(NamespaceDecl *decl) {
42+
content += "namespace " + decl->getNameAsString() + " {\n";
43+
}
44+
45+
void closeNamespace(NamespaceDecl *) { content += "}\n"; }
46+
47+
void addSymbol(NamedDecl *decl) {
48+
if (decl != nullptr && decl->isFirstDecl())
49+
content += "export using " + decl->getQualifiedNameAsString() + ";\n";
50+
}
51+
52+
const std::string &getContent() const { return content; }
53+
};
54+
55+
class FindAllSymbols : public RecursiveASTVisitor<FindAllSymbols> {
56+
private:
57+
using Base = RecursiveASTVisitor<FindAllSymbols>;
58+
59+
ModuleWrapper &wrapper;
60+
61+
public:
62+
FindAllSymbols(ModuleWrapper &wrapper) : wrapper(wrapper) {}
63+
64+
bool TraverseNamespaceDecl(NamespaceDecl *decl) {
65+
if (nsFilter.find(decl->getName()) != std::string::npos) {
66+
wrapper.openNamespace(decl);
67+
Base::TraverseNamespaceDecl(decl);
68+
wrapper.closeNamespace(decl);
69+
}
70+
return true;
71+
}
72+
73+
bool TraverseDecl(Decl *decl) {
74+
switch (decl->getKind()) {
75+
case Decl::TranslationUnit:
76+
return TraverseTranslationUnitDecl(
77+
static_cast<TranslationUnitDecl *>(decl));
78+
case Decl::Namespace:
79+
return TraverseNamespaceDecl(static_cast<NamespaceDecl *>(decl));
80+
default:
81+
break;
82+
}
83+
switch (decl->getKind()) {
84+
#define ABSTRACT_DECL(DECL)
85+
#define DECL(CLASS, BASE) \
86+
case Decl::CLASS: \
87+
if (!WalkUpFrom##CLASS##Decl(static_cast<CLASS##Decl *>(decl))) \
88+
return false; \
89+
break;
90+
#include "clang/AST/DeclNodes.inc"
91+
}
92+
return true;
93+
}
94+
95+
#define VISIT_DECL(TYPE) \
96+
bool Visit##TYPE##Decl(TYPE##Decl *decl) { \
97+
if (!decl->isImplicit() && \
98+
decl->getQualifiedNameAsString().find(nsFilter) != std::string::npos) \
99+
wrapper.addSymbol(decl); \
100+
return true; \
101+
}
102+
103+
VISIT_DECL(Tag);
104+
VISIT_DECL(TypedefName);
105+
VISIT_DECL(Function);
106+
#undef VISIT_DECL
107+
};
108+
109+
class CreateModule : public ASTConsumer {
110+
private:
111+
fs::path path;
112+
113+
public:
114+
CreateModule(const fs::path &path) : path(fs::canonical(path)) {}
115+
116+
void HandleTranslationUnit(ASTContext &ctx) override {
117+
ModuleWrapper wrapper(path);
118+
FindAllSymbols visitor(wrapper);
119+
visitor.TraverseDecl(ctx.getTranslationUnitDecl());
120+
auto modulePath = output.getValue() / path.stem() += ".cppm";
121+
fs::create_directories(modulePath.parent_path());
122+
std::ofstream os{modulePath, std::ios::binary};
123+
os.exceptions(std::ios::failbit);
124+
os << wrapper.getContent();
125+
}
126+
};
127+
128+
template <class Consumer>
129+
class SuppressIncludeNotFound : public ASTFrontendAction {
130+
public:
131+
std::unique_ptr<ASTConsumer> CreateASTConsumer(
132+
CompilerInstance &, llvm::StringRef path) override {
133+
return std::make_unique<Consumer>(path.str());
134+
}
135+
};
136+
137+
int main(int argc, const char **argv) {
138+
output.setInitialValue(fs::current_path().generic_string());
139+
auto expectedParser = CommonOptionsParser::create(argc, argv, toolCategory);
140+
if (!expectedParser) {
141+
llvm::errs() << expectedParser.takeError();
142+
return 1;
143+
}
144+
CommonOptionsParser &op = expectedParser.get();
145+
ClangTool tool(op.getCompilations(), op.getSourcePathList());
146+
return tool.run(
147+
newFrontendActionFactory<SuppressIncludeNotFound<CreateModule>>().get());
148+
}

0 commit comments

Comments
 (0)