Skip to content

Commit

Permalink
add --annotate-functions option (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
lyskov authored Jan 10, 2025
1 parent b280629 commit 99aa4b7
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 29 deletions.
23 changes: 11 additions & 12 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ on:
push:
pull_request:
schedule:
#Every 5 days at midnight
#Every 5 days at midnight
- cron: "0 0 1/5 * *"

jobs:
compilejobFedora:
strategy:
fail-fast: false
fail-fast: false
matrix:
version: [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
version: [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
runs-on: ubuntu-latest
name: Binder_on_Fedora${{ matrix.version }}
container:
image: fedora:${{ matrix.version }}
steps:
- name: Install
- name: Install
run: |
set -x
uname -a
uname -a
cat /etc/issue
yum -y install git zlib zlib-devel ncurses-devel clang clang-devel clang-libs llvm llvm-devel llvm-static \
libcxx-devel cmake make gcc gcc-c++ \
Expand All @@ -34,28 +34,27 @@ jobs:
make install
ldd source/binder
ldd -u -r source/binder || echo "OK"
ctest . --output-on-failure
ctest . --output-on-failure
compilejobOSX:
runs-on: macos-latest
name: Binder_on_OSX
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install
- name: Install
run: |
set -x
brew install wget coreutils xz pybind11 git
wget --no-verbose https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-x86_64-apple-darwin.tar.xz
wget --no-verbose https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.5/clang+llvm-14.0.5-x86_64-apple-darwin.tar.xz
ls
tar -xJf clang+llvm-11.0.0-x86_64-apple-darwin.tar.xz
export PATH=$PATH:clang+llvm-11.0.0-x86_64-apple-darwin/bin
tar -xJf clang+llvm-14.0.5-x86_64-apple-darwin.tar.xz
export PATH=$PATH:clang+llvm-14.0.5-x86_64-apple-darwin/bin
- name: Compile
run: |
export PATH=$PATH:clang+llvm-11.0.0-x86_64-apple-darwin/bin
export PATH=$PATH:clang+llvm-14.0.5-x86_64-apple-darwin/bin
cmake CMakeLists.txt
make
make install
otool -L source/binder
ctest . --output-on-failure
2 changes: 1 addition & 1 deletion documentation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ files. Typically the following files will be generated: ``<root-module>.cpp``, `


``--annotate-includes`` [debug] if specified Binder will comment each include with type name which trigger it inclusion.
``--annotate-functions`` [debug] if specified Binder will generate an extra comment for each function/constructor bound containing its C++ type signature.


``--trace`` [debug] if specified instruct Binder to add extra debug output before binding each type. This might be useful when debugging generated code that produce seg-faults during python import.
Expand Down Expand Up @@ -253,4 +254,3 @@ Config file directives:
.. code-block:: bash
+pybind11_include_file pybind11/smart_holder.h
58 changes: 44 additions & 14 deletions source/class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1032,8 +1032,15 @@ string bind_constructor(ConstructorBindingInfo const &CBI, uint args_to_bind, bo
// string function_qualified_name { F->getQualifiedNameAsString() };

string c;

if( O_annotate_functions ) {
clang::FunctionDecl const *F = CBI.T;
string const include = relevant_include(F);
c += "\t// function-signature: " + function_qualified_name(F, true) + "(" + function_arguments(F) + ") file:" + (include.size() ? include.substr(1, include.size() - 2) : "") + " line:" + line_number(F) + "\n";
}

if( args_to_bind == CBI.T->getNumParams() and not CBI.T->isVariadic() ) {
c = "\tcl.def( pybind11::init<{}>()"_format(function_arguments(CBI.T));
c += "\tcl.def( pybind11::init<{}>()"_format(function_arguments(CBI.T));

for( uint i = 0; i < CBI.T->getNumParams() and i < args_to_bind; ++i ) {
c += ", pybind11::arg(\"{}\")"_format(string(CBI.T->getParamDecl(i)->getName()));
Expand All @@ -1052,14 +1059,14 @@ string bind_constructor(ConstructorBindingInfo const &CBI, uint args_to_bind, bo

for( uint i = 0; i < CBI.T->getNumParams() and i < args_to_bind; ++i ) { args_helper += ", pybind11::arg(\"{}\")"_format(string(CBI.T->getParamDecl(i)->getName())); }

// if( CBI.T->isVariadic() ) c = fmt::format(constructor_lambda_template, params, args.second, constructor_types.first, constructor_types.second);
// else if( constructor_types.first.size() and constructor_types.second.size() ) c = fmt::format(constructor_lambda_template, params, args.second, constructor_types.first,
// constructor_types.second); else if( constructor_types.first.size() ) c = fmt::format(constructor_template, params, args.second, constructor_types.first); else c =
// if( CBI.T->isVariadic() ) c += fmt::format(constructor_lambda_template, params, args.second, constructor_types.first, constructor_types.second);
// else if( constructor_types.first.size() and constructor_types.second.size() ) c += fmt::format(constructor_lambda_template, params, args.second, constructor_types.first,
// constructor_types.second); else if( constructor_types.first.size() ) c += fmt::format(constructor_template, params, args.second, constructor_types.first); else c +=
// fmt::format(constructor_template, params, args.second, constructor_types.second);

if( CBI.C->isAbstract() ) c = fmt::format(constructor_template, params, args.second, CBI.trampoline_qualified_name);
else if( CBI.trampoline ) c = fmt::format(constructor_with_trampoline_template, params, args.second, CBI.class_qualified_name, CBI.trampoline_qualified_name);
else c = fmt::format(constructor_template_with_py_arg, params, args.second, CBI.class_qualified_name, args_helper);
if( CBI.C->isAbstract() ) c += fmt::format(constructor_template, params, args.second, CBI.trampoline_qualified_name);
else if( CBI.trampoline ) c += fmt::format(constructor_with_trampoline_template, params, args.second, CBI.class_qualified_name, CBI.trampoline_qualified_name);
else c += fmt::format(constructor_template_with_py_arg, params, args.second, CBI.class_qualified_name, args_helper);
}

return c;
Expand All @@ -1069,20 +1076,40 @@ string bind_constructor(ConstructorBindingInfo const &CBI, uint args_to_bind, bo
/// Generate code for binding default constructor
string bind_default_constructor(ConstructorBindingInfo const &CBI) // CXXRecordDecl const *, string const & binding_qualified_name)
{
string code;
if( O_annotate_functions ) {
clang::FunctionDecl const *F = CBI.T;
if(F) {
string const include = relevant_include(F);
code += "\t// function-signature: " + function_qualified_name(F, true) + "(" + function_arguments(F) + ") file:" + (include.size() ? include.substr(1, include.size() - 2) : "") + " line:" + line_number(F) + "\n";
}
else {
code += "\t// function-signature: " + CBI.class_qualified_name + "()\n";
}
}

// version before error: chosen constructor is explicit in copy-initialization
// return "\tcl.def(pybind11::init<>());__\n";

// return "\tcl.def( pybind11::init( [](){{ return new {0}(); }} ) );\n"_format(binding_qualified_name);

if( CBI.C->isAbstract() ) return "\tcl.def( pybind11::init( [](){{ return new {0}(); }} ) );\n"_format(CBI.trampoline_qualified_name);
else if( CBI.trampoline ) return "\tcl.def( pybind11::init( [](){{ return new {0}(); }}, [](){{ return new {1}(); }} ) );\n"_format(CBI.class_qualified_name, CBI.trampoline_qualified_name);
else return "\tcl.def( pybind11::init( [](){{ return new {0}(); }} ) );\n"_format(CBI.class_qualified_name);
if( CBI.C->isAbstract() ) code += "\tcl.def( pybind11::init( [](){{ return new {0}(); }} ) );\n"_format(CBI.trampoline_qualified_name);
else if( CBI.trampoline ) code += "\tcl.def( pybind11::init( [](){{ return new {0}(); }}, [](){{ return new {1}(); }} ) );\n"_format(CBI.class_qualified_name, CBI.trampoline_qualified_name);
else code += "\tcl.def( pybind11::init( [](){{ return new {0}(); }} ) );\n"_format(CBI.class_qualified_name);

return code;
}

/// Generate copy constructor in most cases this will be just: "\tcl.def(pybind11::init<{} const &>());\n"_format(binding_qualified_name);
/// but for POD structs with zero data mambers this will be a lambda function. This is done as a workaround for Pybind11 2,2+ bug
string bind_copy_constructor(ConstructorBindingInfo const &CBI) // CXXConstructorDecl const *T, string const & binding_qualified_name)
{
string code;
if( O_annotate_functions ) {
clang::FunctionDecl const *F = CBI.T;
string const include = relevant_include(F);
code += "\t// function-signature: " + function_qualified_name(F, true) + "(" + function_arguments(F) + ") file:" + (include.size() ? include.substr(1, include.size() - 2) : "") + " line:" + line_number(F) + "\n";
}
// CXXRecordDecl const *C = T->getParent();

// C->dump();
Expand Down Expand Up @@ -1111,15 +1138,18 @@ string bind_copy_constructor(ConstructorBindingInfo const &CBI) // CXXConstructo
}

if( CBI.trampoline ) {
if( CBI.C->isAbstract() ) return "\tcl.def(pybind11::init<{}{} &>());\n"_format(CBI.trampoline_qualified_name, const_bit);
if( CBI.C->isAbstract() ) code += "\tcl.def(pybind11::init<{}{} &>());\n"_format(CBI.trampoline_qualified_name, const_bit);
else {
// not yet supported by Pybind11? return "\tcl.def( pybind11::init( []({0} const &o){{ return new {0}(o); }}, []({1} const &o){{ return new {1}(o); }} )
// );\n"_format(CBI.class_qualified_name, CBI.binding_qualified_name);
return "\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.trampoline_qualified_name, const_bit) +
(CBI.T->getAccess() == AS_public ? "\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.class_qualified_name, const_bit) : "");
code += \
"\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.trampoline_qualified_name, const_bit) +
(CBI.T->getAccess() == AS_public ? "\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.class_qualified_name, const_bit) : "");
}
}
else return "\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.class_qualified_name, const_bit);
else code += "\tcl.def( pybind11::init( []({0}{1} &o){{ return new {0}(o); }} ) );\n"_format(CBI.class_qualified_name, const_bit);

return code;
}

// Generate binding for given constructor. If constructor have default arguments generate set of bindings by creating separate bindings for each argument with default.
Expand Down
6 changes: 6 additions & 0 deletions source/function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
/// @author Sergey Lyskov

#include <function.hpp>
#include <options.hpp>

#include <class.hpp>
#include <fmt/format.h>
Expand Down Expand Up @@ -451,6 +452,11 @@ string bind_function(string const &module, FunctionDecl const *F, Context &conte
{
string code;

if( O_annotate_functions ) {
string const include = relevant_include(F);
code += "\t// function-signature: " + function_qualified_name(F) + "(" + function_arguments(F) + ") file:" + (include.size() ? include.substr(1, include.size() - 2) : "") + " line:" + line_number(F) + "\n";
}

int num_params = F->getNumParams();

int args_to_bind = 0;
Expand Down
2 changes: 2 additions & 0 deletions source/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ llvm::cl::OptionCategory BinderToolCategory("Binder options");

cl::opt<bool> O_annotate_includes("annotate-includes", cl::desc("Annotate each includes in generated code with type name that trigger it inclusion"), cl::init(false), cl::cat(BinderToolCategory));

cl::opt<bool> O_annotate_functions("annotate-functions", cl::desc("Annotate each function bindings with full function signature"), cl::init(false), cl::cat(BinderToolCategory));

cl::opt<bool> O_single_file("single-file", cl::desc("Concatenate all binder output into single file with name: root-module-name + '.cpp'. Use this for a small projects and for testing."),
cl::init(false), cl::cat(BinderToolCategory));

Expand Down
1 change: 1 addition & 0 deletions source/options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
extern llvm::cl::OptionCategory BinderToolCategory;

extern llvm::cl::opt<bool> O_annotate_includes;
extern llvm::cl::opt<bool> O_annotate_functions;
extern llvm::cl::opt<bool> O_single_file;
extern llvm::cl::opt<bool> O_trace;
extern llvm::cl::opt<bool> O_verbose;
Expand Down
1 change: 1 addition & 0 deletions test/T07.class.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

// -*- mode:c++;tab-width:2;indent-tabs-mode:t;show-trailing-whitespace:t;rm-trailing-spaces:t -*-
// vi: set ts=2 noet:
//
Expand Down
7 changes: 5 additions & 2 deletions test/self-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ def run_test(test_path, build_dir, pyenv):
python = pyenv.python
python_includes = '-I'+pyenv.python_include_dir #'-I/usr/include/python2.7'

command_line = '{binder} --bind "" --skip-line-number --root-module {root_module} --prefix {build_dir} --single-file --annotate-includes {config}{cli} {source} -- -x c++ -std=c++11 -I {source_dir} -I {source_dir}/.. -isystem {pybind11} {python_includes}' \
extras = '--annotate-functions' if Options.annotate else ''

command_line = '{binder} --bind "" --skip-line-number --root-module {root_module} --prefix {build_dir} --single-file --annotate-includes {extras} {config}{cli} {source} -- -x c++ -std=c++11 -I {source_dir} -I {source_dir}/.. -isystem {pybind11} {python_includes}' \
.format(binder=Options.binder, root_module=root_module, build_dir=build_dir, source_dir=source_dir, cli=cli, source=source_include,
config='--config {}'.format(config) if config else '', pybind11=Options.pybind11, python_includes=python_includes)
config='--config {}'.format(config) if config else '', pybind11=Options.pybind11, python_includes=python_includes, extras=extras)

execute('{} Running test...'.format(test), command_line);

Expand Down Expand Up @@ -150,6 +152,7 @@ def main():
parser.add_argument('--pybind11', default='', help='Path to pybind11 source tree')

parser.add_argument("--accept", action="store_true", help="Run tests and accept new tests results as reference")
parser.add_argument("--annotate", action="store_true", help="Run Binder with extra annotation options")

parser.add_argument('args', nargs=argparse.REMAINDER, help='Optional: list of tests to run')

Expand Down

0 comments on commit 99aa4b7

Please sign in to comment.