Skip to content

Commit 41167f8

Browse files
committed
Better logging, improved auto_veto, and mapped_types
- Add experimental mapped_types option; - Add logging message providing information on why STL was applied on a type; - Make autoveto take into consideration the vetoed argument and return types; - Add list of autovetoed function to the report
1 parent feaf6f9 commit 41167f8

File tree

6 files changed

+179
-36
lines changed

6 files changed

+179
-36
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Automatic generation of C++ -- Julia bindings
22

3-
![Linux](https://github.com/grasph/wrapit/actions/workflows/test-linux.yml/badge.svg) ![macOS](https://github.com/grasph/wrapit/actions/workflows/test-macos.yml/badge.svg)
3+
[![Linux](https://github.com/grasph/wrapit/actions/workflows/test-linux.yml/badge.svg" alt="Linux")](https://github.com/grasph/wrapit/actions?query=workflow%3ALinux+branch%3Amain) [![macOS](https://github.com/grasph/wrapit/actions/workflows/test-macos.yml/badge.svg)](https://github.com/grasph/wrapit/actions?query=workflow%3AmacOS+branch%3Amain)
44

55
The goal of this project is the automatization of the generation of [Julia](https://julialang.org) bindings for C++ libraries.
66

77
The WrapIt! tool complements the [CxxWrap.jl](https://github.com/JuliaInterop/CxxWrap.jl) package. It generates automatically the c++ wrapper code needed by CxxWrap.jl from the c++ header files of the library to wrap.
88

99
Support of generation of code in multiple files (now the default) to reduce time and memory needs for compilation in case of large project. See `n_classes_per_file` in [config.md](../doc/config.md).
1010

11-
🆕 Installation using the Julia package manager and execution of the tool from the Julia REPL: [WrapIt.jl](https://github.com/grasph/WrapIt.jl/).
11+
💡Installation using the Julia package manager and execution of the tool from the Julia REPL: [WrapIt.jl](https://github.com/grasph/WrapIt.jl/).
1212

1313
## Usage
1414

@@ -40,11 +40,14 @@ An alternative to the installation of the pre-built executable is to build it fr
4040
#### Dependencies:
4141

4242
* Software to be present on the system before running `cmake`:
43-
* libclang: packages `clang-13` and `libclang-13-dev` on Debian
44-
* libclang: packages `llvm`, `llvm-devel`, `clang`, `clang-devel` on Alma/Rocky/RHEL
45-
* On MacOS, you can install `llvm/clang` with brew running the command `brew install llvm` from a terminal
43+
* libclang release 13¹: packages `clang-13` and `libclang-13-dev` on Debian.
44+
* libclang relase 13¹: packages `llvm13`, `llvm13-devel`, `clang13`, `clang13-devel` on Alma/Rocky/RHEL
45+
* _If release 13 is not available for the linux distribution version of yoursystem, you can use the option `-DLLVM_BUILD` to build LLVM and Clang from the source code, that will be automatically downloaded from the LLVM project github repository._
4646
* Software will be downloaded by the `cmake` command:
4747
* [tomlplusplus](https://github.com/marzer/tomlplusplus.git)
48+
* [llvm/clang](https://github.com/llvm/llvm-project/releases/) if the option `-DBUILD_LLVM=ON` is used.
49+
50+
¹ WrapIt is not guaranteed to generate correct code with higher versions of clang. Version 16 is known to give issues. The symptom is function wrappers declared with some argument or the return type as `int` while it if of another type.
4851

4952
#### Build
5053

@@ -56,9 +59,9 @@ cmake -S .. -B . -DCMAKE_INSTALL_PREFIX=/opt
5659
cmake --build .
5760
```
5861

59-
On OS X it is necessary to add the prefix of the clang/llvm installation, `-DCMAKE_PREFIX_PATH=/opt/homebrew/Cellar/llvm/LLVM_VERSION`
62+
On OS X it is necessary to add the prefix of the clang/llvm installation, `-DCMAKE_PREFIX_PATH=<path>` or use the `-DBUILD_LLVM=ON` to download and build LLVM and clang. Dep
6063

61-
The build process will produce the `wrapit` executable. Install it with `cmake --build . --target install`.
64+
The build process will produce the `wrapit` executable. Install it with `cmake --install .`.
6265

6366
## A simple example
6467

doc/config.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,24 @@ inheritances = [ "" ]
125125
# example, allows the implementation in Julia of a wrapper with the original
126126
# function name, that can make the required customization.
127127
julia_names = [ ]
128+
129+
# Bit-to-bit map of C++ types to julia types (experimental)
130+
#
131+
# This control the generation of map_type() code that maps memory of
132+
# a C++ struct to a julia struct.
133+
#
134+
# Use extra_headers to add the header file that defines the C++ struct.
135+
# Currently, the struct needed to be vetoed if it is defined in a header of the
136+
# input list, to prevent doubling mapping.
137+
#
138+
# Note: it does not generate the julia code to define the Julia version of the type.
139+
#
140+
# Format: [ "CxxType -> JuliaType", ...]
141+
#
142+
# This feature is experimental and may change in a future release.
143+
#
144+
mapped_types = []
145+
128146
```
129147

130148
### Extra options for the julia module code generation

src/CodeTree.cpp

Lines changed: 131 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ CodeTree::generate_cxx(){
666666
o << "\n\nJLCXX_MODULE define_julia_module(jlcxx::Module& jlModule){\n";
667667

668668

669-
indent(o, 1) << "\nthrow_if_version_incompatibility();\n\n";
669+
indent(o << "\n", 1) << "throw_if_version_incompatibility();\n\n";
670670

671671
indent(o, 1) << "std::vector<std::shared_ptr<Wrapper>> wrappers = {\n";
672672
std::string sep;
@@ -677,6 +677,17 @@ CodeTree::generate_cxx(){
677677
o << "\n";
678678
indent(o, 1) << "};\n";
679679

680+
if(type_straight_mapping_.size() > 0){
681+
indent(o, 1) << "// mapping of types that are bit-to-bit matched\n";
682+
indent(o, 1) << "// WrapIt note: map is generated from the mapped_types list provided in\n";
683+
indent(o, 1) << "// the configuration file without further check..\n";
684+
}
685+
686+
for(const auto& type_pair: type_straight_mapping_){
687+
indent(o, 1) << "jlModule.map_type<" << type_pair.first
688+
<< ">(\"" << type_pair.second << "\");\n";
689+
}
690+
680691
for(const auto& e: enums_){
681692
generate_enum_cxx(o, e.cursor);
682693
}
@@ -1291,7 +1302,19 @@ CodeTree::visit_global_function(CXCursor cursor){
12911302
int min_args;
12921303
std::tie(missing_types, min_args) = visit_function_arg_and_return_types(cursor);
12931304

1294-
if(!auto_veto_ || !inform_missing_types(missing_types, MethodRcd(cursor))){
1305+
auto missing_some_type = inform_missing_types(missing_types, MethodRcd(cursor));
1306+
1307+
bool dowrap;
1308+
if(auto_veto_){
1309+
//wrap funcion only if no type is missing
1310+
dowrap = !missing_some_type;
1311+
if(!dowrap) auto_vetoed_methods_.insert(cursor);
1312+
} else{
1313+
//always wrap the function
1314+
dowrap = true;
1315+
}
1316+
1317+
if(dowrap){
12951318
functions_.emplace_back(cursor, min_args);
12961319
}
12971320

@@ -1467,9 +1490,24 @@ void CodeTree::check_for_stl(const CXType& type_){
14671490
}
14681491

14691492
if(itype>=0){
1493+
14701494
auto& beltype_rcd = types_.at(itype);
14711495
beltype_rcd.to_wrap = true;
1472-
1496+
1497+
if(verbose > 0){
1498+
auto [_, def ] = find_base_type_definition_(btype);
1499+
std::cerr << "INFO: STL activated for type "
1500+
<< beltype_rcd.type_name
1501+
<< " to support " << fully_qualified_name(type_)
1502+
<< ". ";
1503+
if(!clang_Cursor_isNull(visited_cursor_)){
1504+
std::cerr << "Hint: the latest is likely needed at "
1505+
<< clang_getCursorLocation(visited_cursor_)
1506+
<< ". ";
1507+
}
1508+
std::cerr << "\n";
1509+
}
1510+
14731511
bool isconst = clang_isConstQualifiedType(eltype);
14741512
bool isptr = (eltype.kind == CXType_Pointer);
14751513
if(isptr){
@@ -1513,6 +1551,10 @@ CodeTree::register_type(const CXType& type, int* pItype, int* pIenum){
15131551

15141552
const auto& type0 = clang_getCursorType(c);
15151553

1554+
1555+
if(maintype && is_natively_supported(type0)) return true;
1556+
1557+
15161558
//by retrieving the type from the definition cursor, we discard
15171559
//the qualifiers.
15181560
std::string type0_name = fully_qualified_name(type0);
@@ -1628,6 +1670,11 @@ CodeTree::visit_function_arg_and_return_types(CXCursor cursor){
16281670

16291671
std::vector<CXType> missing_types;
16301672

1673+
//FIXME: if the registration of the function failed, then we can end
1674+
//up with types which were registered for that function but are finally
1675+
//not needed. We should avoid these uncessary registrations that add unecessary
1676+
//wraps.
1677+
16311678
if(return_type.kind != CXType_Void){
16321679
if(verbose > 3) std::cerr << cursor << ", return type: " << return_type << "\n";
16331680
if(is_class_param(return_type)){
@@ -1636,8 +1683,9 @@ CodeTree::visit_function_arg_and_return_types(CXCursor cursor){
16361683
} else if(type_map_.is_mapped(return_type, /*as_return=*/ true)){
16371684
if(verbose > 3) std::cerr << return_type << " is mapped to an alternative type.\n";
16381685
} else{
1639-
bool rc = register_type(return_type);
1640-
if(!rc) missing_types.push_back(return_type);
1686+
//we call in_veto_list instead of is_type_vetoed to not exclude std::vector
1687+
bool rc = !in_veto_list(fully_qualified_name(return_type)) && register_type(return_type);
1688+
if(!rc) missing_types.push_back(base_type(return_type));
16411689
}
16421690
}
16431691

@@ -1654,7 +1702,8 @@ CodeTree::visit_function_arg_and_return_types(CXCursor cursor){
16541702
} else if(type_map_.is_mapped(argtype)){
16551703
if(verbose > 3) std::cerr << argtype << " is mapped to an alternative type.\n";
16561704
} else{
1657-
bool rc = register_type(argtype);
1705+
//we call in_veto_list instead of is_type_vetoed to not exclude std::vector
1706+
bool rc = !in_veto_list(fully_qualified_name(base_type(argtype))) && register_type(argtype);
16581707
if(!rc) missing_types.push_back(argtype);
16591708
}
16601709
}
@@ -1752,17 +1801,30 @@ CodeTree::visit_member_function(CXCursor cursor){
17521801
<< clang_getCursorLocation(cursor)
17531802
<< " skipped because the definition of its class "
17541803
"was not found.\n";
1755-
} else if(!auto_veto_ || !inform_missing_types(missing_types, MethodRcd(cursor), p)){
1756-
auto it = std::find_if(p->methods.begin(), p->methods.end(),
1757-
[cursor](const MethodRcd& m){
1804+
} else{
1805+
bool missing_some_type = inform_missing_types(missing_types, MethodRcd(cursor), p);
1806+
bool dowrap;
1807+
if(auto_veto_){
1808+
//method wrapped only if all argument and return types can be wrapped
1809+
dowrap = !missing_some_type;
1810+
if(!dowrap) auto_vetoed_methods_.insert(cursor);
1811+
} else{
1812+
//always wrap the function
1813+
dowrap = true;
1814+
}
1815+
1816+
if(dowrap){
1817+
auto it = std::find_if(p->methods.begin(), p->methods.end(),
1818+
[cursor](const MethodRcd& m){
17581819
return clang_equalCursors(clang_getCanonicalCursor(m.cursor),
17591820
clang_getCanonicalCursor(cursor));
17601821
});
1761-
if(it == p->methods.end()){
1762-
p->methods.emplace_back(cursor, min_args);
1763-
} else if(it->min_args > min_args){
1764-
it->min_args = min_args;
1765-
it->cursor = cursor;
1822+
if(it == p->methods.end()){
1823+
p->methods.emplace_back(cursor, min_args);
1824+
} else if(it->min_args > min_args){
1825+
it->min_args = min_args;
1826+
it->cursor = cursor;
1827+
}
17661828
}
17671829
}
17681830
}
@@ -1956,19 +2018,29 @@ CodeTree::visit_class_constructor(CXCursor cursor){
19562018
bool is_def_ctor = (0 == clang_Cursor_getNumArguments(cursor));
19572019

19582020
if(p && !is_def_ctor){
1959-
if(access == CX_CXXPublic
1960-
&& (!auto_veto_ || !inform_missing_types(missing_types, MethodRcd(cursor), p))){
2021+
if(access == CX_CXXPublic){
2022+
auto missing_some_type = inform_missing_types(missing_types, MethodRcd(cursor), p);
2023+
bool dowrap;
19612024

1962-
auto it = std::find_if(p->methods.begin(), p->methods.end(),
1963-
[cursor](const MethodRcd& m){
1964-
return clang_equalCursors(clang_getCanonicalCursor(m.cursor),
1965-
clang_getCanonicalCursor(cursor));
1966-
});
1967-
if(it == p->methods.end()){
1968-
p->methods.emplace_back(cursor, min_args);
1969-
} else if(it->min_args > min_args){
1970-
it->min_args = min_args;
1971-
it->cursor = cursor;
2025+
if(auto_veto_){
2026+
dowrap = !missing_some_type;
2027+
if(!dowrap) auto_vetoed_methods_.insert(cursor);
2028+
} else{
2029+
dowrap = true;
2030+
}
2031+
2032+
if(dowrap){
2033+
auto it = std::find_if(p->methods.begin(), p->methods.end(),
2034+
[cursor](const MethodRcd& m){
2035+
return clang_equalCursors(clang_getCanonicalCursor(m.cursor),
2036+
clang_getCanonicalCursor(cursor));
2037+
});
2038+
if(it == p->methods.end()){
2039+
p->methods.emplace_back(cursor, min_args);
2040+
} else if(it->min_args > min_args){
2041+
it->min_args = min_args;
2042+
it->cursor = cursor;
2043+
}
19722044
}
19732045
}
19742046
}
@@ -2190,6 +2262,8 @@ CXChildVisitResult CodeTree::visit(CXCursor cursor, CXCursor parent,
21902262

21912263
if(!pp) pp = clang_getCursorPrintingPolicy(cursor);
21922264

2265+
tree.visited_cursor_ = cursor;
2266+
21932267
if(verbose > 5) std::cerr << "visiting " << clang_getCursorLocation(cursor)
21942268
<< "\n";
21952269

@@ -2415,6 +2489,21 @@ std::ostream& CodeTree::report(std::ostream& o){
24152489
if(types_missing_def_.size() == 0){
24162490
o << "No missing definitions\n";
24172491
}
2492+
2493+
2494+
s = "Auto-vetoed functions";
2495+
o << "\n" << s << "\n";
2496+
for(unsigned i = 0; i < s.size(); ++i) o << "-";
2497+
o << "\n";
2498+
2499+
for(const auto& cursor: auto_vetoed_methods_){
2500+
auto sig = FunctionWrapper(cxx_to_julia_, MethodRcd(cursor),
2501+
find_class_of_method(cursor),
2502+
type_map_,
2503+
cxxwrap_version_).signature();
2504+
o << sig << "\n";
2505+
}
2506+
24182507
//
24192508
// std::set<int> used_headers;
24202509
// for(const auto& t: types_missing_def_){
@@ -2968,6 +3057,8 @@ bool CodeTree::is_natively_supported(const std::string& type_fqn,
29683057
};
29693058

29703059
static std::vector<rcd> natively_supported = {
3060+
{"_jl_value_t", 0},
3061+
{"jl_value_t", 0},
29713062
{"std::string", 0},
29723063
{"std::wstring", 0},
29733064
{"std::vector", 1},
@@ -3031,6 +3122,8 @@ void CodeTree::generate_project_file(std::ostream& o,
30313122
<< "\"\n";
30323123
}
30333124

3125+
//FIXME: factorize codes of set_julia_names and set_mapped_types
3126+
30343127
void CodeTree::set_julia_names(const std::vector<std::string>& name_map){
30353128
cxx_to_julia_.clear();
30363129
std::regex re("\\s*->\\s");
@@ -3042,3 +3135,15 @@ void CodeTree::set_julia_names(const std::vector<std::string>& name_map){
30423135
}
30433136
}
30443137
}
3138+
3139+
void CodeTree::set_mapped_types(const std::vector<std::string>& name_map){
3140+
type_straight_mapping_.clear();
3141+
std::regex re("\\s*->\\s");
3142+
for(const std::string& m: name_map){
3143+
std::sregex_token_iterator it{m.begin(), m.end(), re, -1};
3144+
std::vector<std::string> tokens{it, {}};
3145+
if(tokens.size() > 1){
3146+
type_straight_mapping_[tokens[0]] = tokens[1];
3147+
}
3148+
}
3149+
}

src/CodeTree.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
#include "TypeMapper.h"
3333
#include "Graph.h"
3434

35+
//to be used by set<CXCursor>
36+
static bool operator<(const CXCursor& c1, const CXCursor& c2){
37+
return c1.data[0] < c2.data[0]
38+
|| (c1.data[0] == c2.data[0] && c1.data[1] < c2.data[1])
39+
|| (c1.data[0] == c2.data[0] && c1.data[1] == c2.data[1] && c1.data[2] < c2.data[2]);
40+
}
41+
3542
namespace fs = std::filesystem;
3643

3744

@@ -277,6 +284,8 @@ namespace codetree{
277284
void set_force_mode(bool forced){ out_open_mode_ = forced ? std::ios_base::out : std::ios_base::app; }
278285

279286
void set_julia_names(const std::vector<std::string>& name_map);
287+
288+
void set_mapped_types(const std::vector<std::string>& name_map);
280289

281290
protected:
282291

@@ -393,6 +402,7 @@ namespace codetree{
393402

394403
std::set<std::string> types_missing_def_;
395404
std::set<std::string> builtin_types_;
405+
std::set<CXCursor> auto_vetoed_methods_;
396406

397407
std::vector<std::string> forced_headers_;
398408

@@ -482,6 +492,8 @@ namespace codetree{
482492

483493
std::map<std::string, std::string> cxx_to_julia_;
484494

495+
std::map<std::string, std::string> type_straight_mapping_;
496+
485497
std::string module_name_;
486498

487499
std::ios_base::openmode out_open_mode_;
@@ -505,6 +517,9 @@ namespace codetree{
505517
CXTranslationUnit unit_;
506518
CXIndex index_;
507519

520+
//Current top-level visited cursor
521+
CXCursor visited_cursor_;
522+
508523
//Map of child->mother class inheritance preference
509524
std::map<std::string, std::string> inheritance_;
510525

@@ -547,7 +562,7 @@ namespace codetree{
547562
unsigned global_var_setters = 0;
548563
unsigned global_funcs = 0;
549564
} nwraps_;
550-
565+
551566
};
552567
}
553568
#endif //CODETREE_H not defined

0 commit comments

Comments
 (0)