-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d172867
Showing
7 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
BasedOnStyle: "Google" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
CompileFlags: | ||
Add: [-std=c++20, -Wextra] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.* | ||
!.clang-format | ||
!.gitignore | ||
!.clangd | ||
build | ||
tests | ||
compile_commands.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "cxx-module-generator", | ||
"dev": { | ||
"packages": [ | ||
"makeDotCpp" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |