diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c007faf..e144784e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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++ \ @@ -34,7 +34,7 @@ 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 @@ -42,20 +42,19 @@ jobs: 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 - diff --git a/documentation/config.rst b/documentation/config.rst index 7d2caa90..9298effd 100644 --- a/documentation/config.rst +++ b/documentation/config.rst @@ -44,6 +44,7 @@ files. Typically the following files will be generated: ``.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. @@ -253,4 +254,3 @@ Config file directives: .. code-block:: bash +pybind11_include_file pybind11/smart_holder.h - diff --git a/source/class.cpp b/source/class.cpp index d29935e1..2963d6d7 100644 --- a/source/class.cpp +++ b/source/class.cpp @@ -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())); @@ -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; @@ -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(); @@ -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. diff --git a/source/function.cpp b/source/function.cpp index cf2f54e1..62b46c5d 100644 --- a/source/function.cpp +++ b/source/function.cpp @@ -11,6 +11,7 @@ /// @author Sergey Lyskov #include +#include #include #include @@ -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; diff --git a/source/options.cpp b/source/options.cpp index 147a62d9..703782f1 100644 --- a/source/options.cpp +++ b/source/options.cpp @@ -20,6 +20,8 @@ llvm::cl::OptionCategory BinderToolCategory("Binder options"); cl::opt 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 O_annotate_functions("annotate-functions", cl::desc("Annotate each function bindings with full function signature"), cl::init(false), cl::cat(BinderToolCategory)); + cl::opt 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)); diff --git a/source/options.hpp b/source/options.hpp index 85be3c23..78df6f7e 100644 --- a/source/options.hpp +++ b/source/options.hpp @@ -20,6 +20,7 @@ extern llvm::cl::OptionCategory BinderToolCategory; extern llvm::cl::opt O_annotate_includes; +extern llvm::cl::opt O_annotate_functions; extern llvm::cl::opt O_single_file; extern llvm::cl::opt O_trace; extern llvm::cl::opt O_verbose; diff --git a/test/T07.class.hpp b/test/T07.class.hpp index 38bc53d3..0a814796 100644 --- a/test/T07.class.hpp +++ b/test/T07.class.hpp @@ -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: // diff --git a/test/self-test.py b/test/self-test.py index cfa9c09d..e6dc7c63 100755 --- a/test/self-test.py +++ b/test/self-test.py @@ -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); @@ -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')