From 8c607218821f5a761ec265af29a0c0387b0fae0c Mon Sep 17 00:00:00 2001 From: Philippe Gras Date: Sat, 23 Mar 2024 21:01:50 +0100 Subject: [PATCH] Added support for CxxWrap 0.15.x and several improvements. Code for CxxWrap versions 0.15.x to generate differs from code for vesions 0.14.x, due to a change in the API. Wrapping of STL vectors of object pointers do not work with version 0.15.0. More details in https://github.com/JuliaInterop/CxxWrap.jl/issues/419. The unit tests and the ROOT example that use such vector are conifgure to use 0.14.x version for now. The CxxWrap version the code should be generated can be selected using the new `cxxwrap_version` configuration parameter. Several other changes: o new --get command line option to retrieve values of some parameters; o new --add-cfg command line option to configure parameter normally set in the .wit configuration file; o fixed generation of random uuid; o add check of the verions of the libcxxwrap when compiling and when running the code. o Cleanup Julia package dependencies installation and version handling; Removed project setting from runtests.jl which is not needed anymore, as each test runs within its own temporary Julia project. --- CMakeLists.txt | 1 + README.md | 2 + doc/config.md | 5 + examples/ex001-HelloWorld/CMakeLists.txt | 100 +++++++++++----- examples/ex001-HelloWorld/Makefile | 13 +- examples/ex001-HelloWorld/install.jl | 2 +- examples/ex002-ROOT/Makefile | 37 ++++-- examples/ex002-ROOT/ROOT.wit | 3 + src/CodeTree.cpp | 91 ++++++++++++-- src/CodeTree.h | 31 +++-- src/FunctionWrapper.cpp | 83 +++++++------ src/FunctionWrapper.h | 10 +- src/cxxwrap_version.cpp | 14 +++ src/cxxwrap_version.h | 11 ++ src/main.cpp | 112 ++++++++++++++---- src/utils.cpp | 36 ++++++ src/utils.h | 13 ++ src/uuid_utils.cpp | 7 +- test/Project.toml | 6 - .../runTestAccessAndDelete.jl | 4 +- test/TestAutoAdd/runTestAutoAdd.jl | 4 +- test/TestCtorDefVal/runTestCtorDefVal.jl | 4 +- test/TestEmptyClass/runTestEmptyClass.jl | 4 +- test/TestEnum/runTestEnum.jl | 4 +- test/TestInheritance/runTestInheritance.jl | 4 +- test/TestNamespace/runTestNamespace.jl | 4 +- test/TestNoFinalizer/runTestNoFinalizer.jl | 4 +- test/TestOperators/runTestOperators.jl | 4 +- test/TestOrder/runTestOrder.jl | 4 +- test/TestPointers/runTestPointers.jl | 4 +- test/TestPropagation/Makefile | 10 +- test/TestPropagation/runTestPropagation1.jl | 4 +- test/TestPropagation/runTestPropagation2.jl | 4 +- test/TestPropagation/runTestPropagation3.jl | 4 +- test/TestSizet/runTestSizet.jl | 4 +- test/TestStdString/runTestStdString.jl | 4 +- test/TestStdVector/TestStdVector.wit | 3 + test/TestStdVector/runTestStdVector.jl | 4 +- test/TestStringView/runTestStringView.jl | 4 +- test/TestTemplate1/runTestTemplate1.jl | 4 +- test/TestTemplate2/runTestTemplate2.jl | 4 +- test/TestUsingType/TestUsingType.wit | 4 + test/TestUsingType/runTestUsingType.jl | 4 +- test/TestVarField/runTestVarField.jl | 11 +- test/TestVarField/runTestVarFieldOff.jl | 4 +- test/TestVarField/runTestVarFieldOn.jl | 4 +- test/WrapitTestSetup.cmake | 104 ++++++++++------ test/make.rules | 12 +- test/runtests.jl | 12 +- test/test_examples.jl | 7 +- 50 files changed, 620 insertions(+), 212 deletions(-) create mode 100644 src/cxxwrap_version.cpp create mode 100644 src/cxxwrap_version.h delete mode 100644 test/Project.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index c958309..91b1a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,7 @@ add_executable(wrapit src/TypeRcd.cpp src/TypeMapper.cpp src/utils.cpp + src/cxxwrap_version.cpp src/uuid_utils.cpp src/libclang-ext.cpp src/FunctionWrapper.cpp diff --git a/README.md b/README.md index 7a7c54b..ab5013b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Automatic generation of C++ -- Julia bindings +![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) + The goal of this project is the automatization of the generation of [Julia](https://julialang.org) bindings for C++ libraries. 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. diff --git a/doc/config.md b/doc/config.md index b0e77f5..b6b4588 100644 --- a/doc/config.md +++ b/doc/config.md @@ -22,6 +22,11 @@ include_dirs = [ "." ] # See clang -std= option # Possible values: "c++11", "c++14", "c++17", and "c++20" cxx-std = "c++17" + +# Version of CxxWrap the code must be generated for. +# Pay attention to quote the string. E.g, "0.14" +# If empty, assume latest supported one. +cxxwrap_version = "" ``` ### Extra options to control the generated code organisation diff --git a/examples/ex001-HelloWorld/CMakeLists.txt b/examples/ex001-HelloWorld/CMakeLists.txt index 7603d08..4d5dab2 100644 --- a/examples/ex001-HelloWorld/CMakeLists.txt +++ b/examples/ex001-HelloWorld/CMakeLists.txt @@ -5,33 +5,8 @@ project(ex001-HelloWorld) set(CMAKE_MACOSX_RPATH 1) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}/deps") -# Path for CxxWrap cmake files: - -find_program(PRINT_CXXWRAP_PATH print-cxxwrap-path.jl - PATHS "${CMAKE_CURRENT_LIST_DIR}/../../buildtools") - -if(PRINT_CXXWRAP_PATH STREQUAL PRINT_CXXWRAP_PATH-NOTFOUND) - message(FATAL_ERROR "Failed to find the print-cxxwarp-path.jl build tool.") -endif() - -execute_process( - COMMAND "${PRINT_CXXWRAP_PATH}" - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE CXXWRAP_PREFIX - RESULT_VARIABLE result) - -if(NOT result EQUAL 0) - message(FATAL_ERROR "Execution of ${PRINT_CXXWRAP_PATH} failed") -endif() - -list(APPEND CMAKE_PREFIX_PATH ${CXXWRAP_PREFIX}) - -find_package(JlCxx) -get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION) -get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY) -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${JlCxx_location}") - -message(STATUS "Found JlCxx at ${JlCxx_location}") +# julia is used to retrieve the CxxWrap library paths +find_program(JULIA julia REQUIRED) set(WRAPIT_VERBOSITY 1 CACHE STRING "Define verbosity level of the wrapit command. An integer, 0 for a quiet mode, 1 for a normal mode, higher numbers for a verbosity that increases with the number.") @@ -46,14 +21,81 @@ else() endif() set(WRAPIT_WIT_FILE "${CMAKE_SOURCE_DIR}/Hello.wit") -set(WRAPPER_MODULE "Hello") +message(STATUS "Wrapit configuration file: ${WRAPIT_WIT_FILE}") + +execute_process( + COMMAND ${WRAPIT} --get module_name ${WRAPIT_WIT_FILE} + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE WRAPPER_MODULE + RESULT_VARIABLE result) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to retrieve the Julia module name parameter using the configuration file ${WRAPIT_WIT_FILE}.") +endif() + + set(WRAPPER_LIB jl${WRAPPER_MODULE}) set(WRAPPER_JULIA_PACKAGE_DIR ${WRAPPER_MODULE}) set(WRAPPER_JULIA_PACKAGE_FILE ${WRAPPER_MODULE}.jl) +execute_process( + COMMAND ${JULIA} "--project=${CMAKE_BINARY_DIR}" -e "import TOML; print(get(TOML.parse(open(\"${WRAPIT_WIT_FILE}\")), \"cxxwrap_version\", \"\"));" + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE CXXWRAP_REQUESTED_VERSION + RESULT_VARIABLE result + ) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to parse ${WRAPIT_WIT_FILE}") +endif() + +if("${CXXWRAP_REQUESTED_VERSION}" STREQUAL "") +execute_process( + COMMAND ${JULIA} --project=${CMAKE_BINARY_DIR} -e "import Pkg; Pkg.add(\"CxxWrap\"); import CxxWrap; print(pkgversion(CxxWrap));" + OUTPUT_VARIABLE CXXWRAP_INSTALLED_VERSION + RESULT_VARIABLE result +) + #if no CxxWrap version requirement was specified in the .wit file, + #we align it to the version that was installed + set(WRAPIT_OPT --add-cfg "cxxwrap_version=\"${CXXWRAP_INSTALLED_VERSION}\"") + message(STATUS ${WRAPIT}) +else() +execute_process( + COMMAND ${JULIA} --project=${CMAKE_BINARY_DIR} -e "import Pkg; import CxxWrap; Pkg.add(name=\"CxxWrap\", version=\"${CXXWRAP_REQUESTED_VERSION}\"); Pkg.resolve(); print(pkgversion(CxxWrap));" + OUTPUT_VARIABLE CXXWRAP_INSTALLED_VERSION + RESULT_VARIABLE result) +endif() + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to install CxxWrap") +elseif("${CXXWRAP_REQUESTED_VERSION}" STREQUAL "") + message(STATUS "CxxWrap version requested to be compatible with any version, using v${CXXWRAP_INSTALLED_VERSION}") +else() + message(STATUS "CxxWrap version requested to be compatible with ${CXXWRAP_REQUESTED_VERSION}, using version: ${CXXWRAP_INSTALLED_VERSION}") +endif() + +execute_process( + COMMAND "${JULIA}" --project=${CMAKE_BINARY_DIR} -e "import CxxWrap; print(CxxWrap.prefix_path())" + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE CXXWRAP_PREFIX + RESULT_VARIABLE result) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to retrieve CxxWrap library path") +else() + message(STATUS "CxxWrap library path prefix: ${CXXWRAP_PREFIX}") +endif() + +find_package(JlCxx PATHS ${CXXWRAP_PREFIX}) + +get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION) +get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${JlCxx_location}") + + # Generate the wrapper code. This is done at configure time. execute_process( - COMMAND "${WRAPIT}" -v "${WRAPIT_VERBOSITY}" --force --update --cmake --output-prefix "${CMAKE_BINARY_DIR}" "${WRAPIT_WIT_FILE}" + COMMAND "${WRAPIT}" ${WRAPIT_OPT} -v "${WRAPIT_VERBOSITY}" --force --update --cmake --output-prefix "${CMAKE_BINARY_DIR}" "${WRAPIT_WIT_FILE}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMAND_ECHO STDERR RESULT_VARIABLE result) diff --git a/examples/ex001-HelloWorld/Makefile b/examples/ex001-HelloWorld/Makefile index f9df159..2b35ac6 100644 --- a/examples/ex001-HelloWorld/Makefile +++ b/examples/ex001-HelloWorld/Makefile @@ -9,7 +9,16 @@ CXXFLAGS += $(patsubst -std=gnu%,,$(shell $(JL_SHARE)/julia-config.jl --cflags)) LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags) LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) WRAPIT = $(shell which wrapit) -CXXWRAP_PREFIX=$(shell ../../buildtools/print-cxxwrap-path.jl) +WIT_FILE = Hello.wit +CXXWRAP_VERSION=$(shell julia -e "import TOML; print(get(TOML.parse(open(\"$(WIT_FILE)\")), \"cxxwrap_version\", \"\"));") + +ifeq ($(CXXWRAP_VERSION),) +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(\"CxxWrap\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +CXXWRAP_VERSION:=$(shell julia --project=${BUILD_DIR} -e "import CxxWrap; print(pkgversion(CxxWrap));") +WRAPIT_OPT=--add-cfg cxxwrap_version=\"$(CXXWRAP_VERSION)\" +else +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(name=\"CxxWrap\", version=\"$(CXXWRAP_VERSION)\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +endif CXXWRAP_CPPFLAGS=-I $(CXXWRAP_PREFIX)/include -I . --std=c++17 LDLIBS +=-L $(CXXWRAP_PREFIX)/lib -lcxxwrap_julia -lcxxwrap_julia_stl @@ -47,7 +56,7 @@ clean: -$(RM) -r $(PRODUCTS) $(BUILD_DIR)/libHello/src/jl%.cxx: %.wit - $(WRAPIT) --force --output-prefix $(BUILD_DIR) $< + $(WRAPIT) $(WRAPIT_OPT) --force --output-prefix $(BUILD_DIR) $< $(BUILD_DIR)/%.o: $(BUILD_DIR)/libHello/src/%.cxx -mkdir -p $(BUILD_DIR) diff --git a/examples/ex001-HelloWorld/install.jl b/examples/ex001-HelloWorld/install.jl index 3fd1fae..bcd952f 100755 --- a/examples/ex001-HelloWorld/install.jl +++ b/examples/ex001-HelloWorld/install.jl @@ -8,7 +8,7 @@ build_location = "build" wrapper_module = "Hello" #Generate the wrapper code and compile: -run(`cmake -B $build_location`) +run(`cmake --fresh -B $build_location`) run(`cmake --build $build_location`) #Install the wrapport module: diff --git a/examples/ex002-ROOT/Makefile b/examples/ex002-ROOT/Makefile index 13ee66a..e1aaf71 100644 --- a/examples/ex002-ROOT/Makefile +++ b/examples/ex002-ROOT/Makefile @@ -1,3 +1,4 @@ + BUILD_DIR=build JL_SHARE = $(shell julia -e 'print(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia"))') @@ -7,7 +8,16 @@ CXXFLAGS += $(patsubst -std=gnu%,,$(shell $(JL_SHARE)/julia-config.jl --cflags)) LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags) LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) WRAPIT = $(shell which wrapit) -CXXWRAP_PREFIX=$(shell ../../buildtools/print-cxxwrap-path.jl) +WIT_FILE = ROOT.wit +CXXWRAP_VERSION=$(shell julia -e "import TOML; print(get(TOML.parse(open(\"$(WIT_FILE)\")), \"cxxwrap_version\", \"\"));") + +ifeq ($(CXXWRAP_VERSION),) +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(\"CxxWrap\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +CXXWRAP_VERSION:=$(shell julia --project=${BUILD_DIR} -e "import CxxWrap; print(pkgversion(CxxWrap));") +WRAPIT_OPT=--add-cfg cxxwrap_version=\"$(CXXWRAP_VERSION)\" +else +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(name=\"CxxWrap\", version=\"$(CXXWRAP_VERSION)\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +endif CXXWRAP_CPPFLAGS=-I $(CXXWRAP_PREFIX)/include -I . --std=c++17 LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) @@ -72,24 +82,27 @@ run_demo2: all LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=.:@:@v#.#:@stdlib julia -i demo_TGraph.jl test: all - LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia demo_ROOT.jl + LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) \ +JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) demo_ROOT.jl cmp demo_ROOT.png demo_ROOT-ref.png - LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia demo_TGraph.jl + LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) \ +JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) demo_TGraph.jl cmp demo_TGraph.png demo_TGraph-ref.png - LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/write_tree1.jl \ -&& JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/read_tree1.jl - LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/write_tree2.jl \ -&& JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/read_tree2.jl - LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/write_tree3.jl \ -&& JULIA_LOAD_PATH=:`pwd`/build/ROOT/src julia TTree_examples/read_tree3.jl + LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) \ +JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) TTree_examples/write_tree1.jl \ +&& JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) TTree_examples/read_tree1.jl + LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) \ +JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) TTree_examples/write_tree2.jl \ +&& JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) -e 'import Pkg; Pkg.activate(;temp=true); Pkg.add("UnROOT"); include("TTree_examples/read_tree2.jl")' + LD_LIBRARY_PATH=libROOT:$(shell root-config --libdir) \ +JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) TTree_examples/write_tree3.jl \ +&& JULIA_LOAD_PATH=`pwd`/build/ROOT/src:$(JULIA_LOAD_PATH): julia --project=$(BUILD_DIR) TTree_examples/read_tree3.jl %/libROOT/src/generated_cxx %/ROOT/Project.toml %/ROOT/src/ROOT-export.jl: %/ROOT-generated.wit jlROOT-veto.h $(WRAPIT) TBranchPtr.h Templates.h $(MAKE) $*/ROOT-generated.wit - $(WRAPIT) --force --update --output-prefix $* $< + $(WRAPIT) $(WRAPIT_OPT) --force --update --output-prefix $* $< $(eval GENERATED_CXX:=$(file < $(BUILD_DIR)/libROOT/src/generated_cxx)) $(eval OBJS:=$(addprefix $(BUILD_DIR)/libROOT/build/, $(patsubst %.cxx,%.o, $(GENERATED_CXX)))) -# cp -a TBranchPtr.h $(BUILD_DIR)/libROOT/src - $(BUILD_DIR)/libROOT/build/%.o: $(BUILD_DIR)/libROOT/src/%.cxx $(BUILD_DIR) [ -d $(BUILD_DIR)/libROOT/build ] || mkdir -p $(BUILD_DIR)/libROOT/build diff --git a/examples/ex002-ROOT/ROOT.wit b/examples/ex002-ROOT/ROOT.wit index 6d1f3b4..ed3b8d9 100644 --- a/examples/ex002-ROOT/ROOT.wit +++ b/examples/ex002-ROOT/ROOT.wit @@ -16,6 +16,9 @@ veto_list = "jlROOT-veto.h" fields_and_variables = false +# Currently not working with CxxWrap 0.15.0, use 0.14.x +cxxwrap_version = "0.14" + #auto_veto = false vetoed_copy_ctor_classes = [ "TTreeReader", "RDataFrame" ] diff --git a/src/CodeTree.cpp b/src/CodeTree.cpp index 4e80ec1..117edac 100644 --- a/src/CodeTree.cpp +++ b/src/CodeTree.cpp @@ -21,6 +21,7 @@ #include "libclang-ext.h" #include "FileTimeRestorer.h" #include "utils.h" +#include "cxxwrap_version.h" extern const char* version; @@ -184,6 +185,53 @@ std::string CodeTree::wrapper_classsname(const std::string& classname) const{ return s; } +std::ostream& +CodeTree::generate_version_check_cxx(std::ostream& o) const{ + auto [jllmin, jllmax] = version_libcxxwrap_bounds(cxxwrap_version_); + + indent(o,0) << "//method from libcxxwrap returning its version\n"; + indent(o,0) << "extern \"C\" JLCXX_API const char* cxxwrap_version_string();\n\n"; + indent(o, 0) << "//Check the code is compiled with a compatible version of libcxxwrap:\n"; + indent(o,0) << "static_assert(1000*1000*JLCXX_VERSION_MAJOR " + " + 1000 * JLCXX_VERSION_MINOR + JLCXX_VERSION_PATCH >= " << jllmin << "\n"; + indent(o, 1) << "&& 1000 * 1000 * JLCXX_VERSION_MAJOR " + " + 1000 * JLCXX_VERSION_MINOR + JLCXX_VERSION_PATCH < " << jllmax << ",\n"; + indent(o,1) << "\"The code was generated with WrapIt! for \"\n"; + indent(o,1) << "\"a different CxxWrap version (controlled with the cxxwrap_version parameter).\");\n\n"; + indent(o, 0) << "//Check the version of loaded libcxxwrap library:\n"; + indent(o,0) << "void throw_if_version_incompatibility(){\n"; + indent(o,1) << "std::string version_str = cxxwrap_version_string();\n"; + indent(o,1) << "static std::regex r(\"([[:digit:]]{1,3})\\\\.([[:digit:]]{1,3})\\\\.([[:digit:]]{1,3})\");\n"; + indent(o,1) << "std::smatch matches;\n"; + indent(o,1) << "if(!std::regex_match(version_str, matches, r)){\n"; + indent(o,2) << "std::cerr << \"Warning: Failed to check libcxxwrap version.\";\n"; + indent(o,1) << "} else{"; + indent(o,2) << "long version_int = 1000*1000*strtol(matches[1].str().c_str(), 0, 10)\n"; + indent(o,2) << " + 1000*strtol(matches[2].str().c_str(), 0, 10)\n"; + indent(o,2) << " + strtol(matches[3].str().c_str(), 0, 10);\n"; + indent(o,2) << "if(version_int < " << jllmin << " || version_int >= " << jllmax << "){\n"; + indent(o,3) << "throw std::runtime_error(std::string(\"Found libcxxwrap_jll version \")\n"; + indent(o,3) << " + version_str + \", while module " << module_name_ << " requires a version in \"\n"; + indent(o,3) << "\"[" << version_int_to_string(jllmin) << ", " << version_int_to_string(jllmax) << ").\"\n"; + indent(o,3) << "\" Note: if the module was installed with the package manager, the Project.toml file \"\n"; + indent(o,3) << "\"of the package is probably missing a compat specification that would have prevented \"\n"; + indent(o,3) << "\"the inconsistency.\");\n"; + indent(o,2) << "}\n"; + indent(o,1) << "}\n"; + indent(o,0) << "}\n"; + +// indent(o, 1) << "long libcxxwrap_vers = 1000*100* JLCXX_VERSION_MAJOR + 1000 * JLCXX_VERSION_MINOR + JLCXX_VERSION_PATCH;\n"; +// indent(o, 1) << "if(libcxxwrap_vers < " << jllmin << " || libcxxwrap_vers >= " << jllmax << "){\n"; +// indent(o, 2) << "throw std::runtime_error(\"Found libcxxwrap_jll version \" " +// << " JLCXX_VERSION_STRING \", module " << module_name_ +// << " needs a vesion in [" +// << version_int_to_string(jllmin) << ", " +// << version_int_to_string(jllmax) << ")" +// ". Note: if the module was installed with the package manager, the Project.toml file of the package is probably missing a compat specification that would have prevented the inconsistency.\");\n"; +// indent(o, 1) << "}\n\n"; + return o; +} + std::ostream& CodeTree::generate_template_add_type_cxx(std::ostream& o, const TypeRcd& type_rcd, @@ -259,7 +307,8 @@ CodeTree::generate_template_add_type_cxx(std::ostream& o, #ifdef DEFINE_TEMPLATE_METHODS_IN_CTOR if(type_rcd.default_ctor){ FunctionWrapper::gen_ctor(o, 2, "t", type_rcd.template_parameters.empty(), - type_rcd.finalize, std::string()); + type_rcd.finalize, std::string(), + cxxwrap_version_); } #endif @@ -399,7 +448,8 @@ CodeTree::generate_cxx_for_type(std::ostream& o, //Generate a wrapper for the implicit default ctor if needed if(t.default_ctor){ FunctionWrapper::gen_ctor(o, 2, "t", t.template_parameters.empty(), - t.finalize, std::string()); + t.finalize, std::string(), + cxxwrap_version_); } } @@ -530,8 +580,9 @@ CodeTree::generate_cxx(){ "#include \"jlcxx/functions.hpp\"\n" "#include \"jlcxx/stl.hpp\"\n\n"; - o << "#include \"jl" << module_name_ << ".h\"\n"; - + o << "#include \"jl" << module_name_ << ".h\"\n\n" + "#include \n\n"; + // for(const auto& include: forced_headers_){ // o << "#include \"" << include << "\"\n"; // } @@ -609,8 +660,13 @@ CodeTree::generate_cxx(){ } + generate_version_check_cxx(o); + o << "\n\nJLCXX_MODULE define_julia_module(jlcxx::Module& jlModule){\n"; + + indent(o, 1) << "\nthrow_if_version_incompatibility();\n\n"; + indent(o, 1) << "std::vector> wrappers = {\n"; std::string sep; for(const auto& w: wrappers){ @@ -785,7 +841,8 @@ CodeTree::generate_accessor_cxx(std::ostream& o, const TypeRcd* type_rcd, const CXCursor& cursor, bool getter_only, int nindents){ - FunctionWrapper helper(MethodRcd(cursor), type_rcd, type_map_, "", "", nindents); + FunctionWrapper helper(MethodRcd(cursor), type_rcd, type_map_, + cxxwrap_version_, "", "", nindents); int ngens = 0; helper.gen_accessors(o, getter_only, &ngens); @@ -912,11 +969,12 @@ CodeTree::method_cxx_decl(std::ostream& o, const MethodRcd& method, TypeRcd* pTypeRcd = find_class_of_method(method.cursor); - FunctionWrapper wrapper(method, pTypeRcd, type_map_, varname, classname, nindents, - templated); + FunctionWrapper wrapper(method, pTypeRcd, type_map_, cxxwrap_version_, varname, + classname, nindents, templated); //FIXME: check that code below is needed. Should now be vetoed upstream - if(std::find(veto_list_.begin(), veto_list_.end(), wrapper.signature()) != veto_list_.end()){ + if(std::find(veto_list_.begin(), veto_list_.end(), + wrapper.signature()) != veto_list_.end()){ if(verbose > 0){ std::cerr << "Info: " << "func " << wrapper.signature() << " vetoed\n"; } @@ -1010,7 +1068,7 @@ CodeTree::generate_methods_of_templated_type_cxx(std::ostream& o, // wrapped.constructor<>(); if(t.default_ctor){ FunctionWrapper::gen_ctor(o, 3, "wrapped", /*templated=*/true, - t.finalize, std::string()); + t.finalize, std::string(), cxxwrap_version_); } // wrapped.method("get_first", [](const T& a) -> T1 { return a.get_first(); }); @@ -1216,7 +1274,7 @@ CodeTree::visit_global_function(CXCursor cursor){ } - FunctionWrapper wrapper(MethodRcd(cursor), nullptr, type_map_); + FunctionWrapper wrapper(MethodRcd(cursor), nullptr, type_map_, cxxwrap_version_); if(in_veto_list(wrapper.signature())){ // if(std::find(veto_list_.begin(), veto_list_.end(), wrapper.signature()) != veto_list_.end()){ if(verbose > 0){ @@ -1670,7 +1728,7 @@ CodeTree::visit_member_function(CXCursor cursor){ TypeRcd* pTypeRcd = find_class_of_method(cursor); - FunctionWrapper wrapper(MethodRcd(cursor), pTypeRcd, type_map_); + FunctionWrapper wrapper(MethodRcd(cursor), pTypeRcd, type_map_, cxxwrap_version_); if(in_veto_list(wrapper.signature())){ // if(std::find(veto_list_.begin(), veto_list_.end(), wrapper.signature()) != veto_list_.end()){ @@ -1768,7 +1826,8 @@ CodeTree::inform_missing_types(std::vector missing_types, }; if(verbose > 0){ - std::string funcname = FunctionWrapper(methodRcd, classRcd, type_map_).signature(); + std::string funcname = FunctionWrapper(methodRcd, classRcd, type_map_, + cxxwrap_version_).signature(); std::cerr << "Warning: missing definition of type " << (missing_types.size() > 1 ? "s" : "") << " " @@ -1867,7 +1926,8 @@ CodeTree::visit_class_constructor(CXCursor cursor){ TypeRcd* pTypeRcd = find_class_of_method(cursor); - FunctionWrapper wrapper(MethodRcd(cursor), pTypeRcd, type_map_); + FunctionWrapper wrapper(MethodRcd(cursor), pTypeRcd, type_map_, + cxxwrap_version_); if(in_veto_list(wrapper.signature())){ //if(std::find(veto_list_.begin(), veto_list_.end(), wrapper.signature()) != veto_list_.end()){ @@ -2942,4 +3002,9 @@ void CodeTree::generate_projet_file(std::ostream& o, o << "\n[deps]\n" "CxxWrap = \"1f15a43c-97ca-5a2a-ae31-89f07a497df4\"\n" ""; + + int version_depth = version_major(cxxwrap_version_) == 0 ? 2 : 1; + o << "\n[compat]\n" + "CxxWrap = \"" << version_int_to_string(cxxwrap_version_, version_depth) + << "\"\n"; } diff --git a/src/CodeTree.h b/src/CodeTree.h index 3bfbbdd..9e6d0c0 100644 --- a/src/CodeTree.h +++ b/src/CodeTree.h @@ -71,7 +71,7 @@ namespace codetree{ std::vector include_files; std::vector types_; - std::vector types_sorted_indices_; + std::vector types_sorted_indices_; std::vector functions_; std::vector enums_; std::vector typedefs_; @@ -113,7 +113,7 @@ namespace codetree{ } static std::string resolve_clang_resource_dir_path(std::string relpath); - + bool has_cursor(const std::vector vec, const CXCursor& cursor){ for(const auto& c: vec){ if(clang_equalCursors(c, cursor)) return true; @@ -145,7 +145,7 @@ namespace codetree{ bool isAccessible(CXType type) const; CXType resolve_private_typedef(CXType type) const; - + static std::string libclangdir(); //Set clang resource directory. @@ -182,7 +182,7 @@ namespace codetree{ void generate_projet_file(std::ostream&o, const std::string& uuid, const std::string& version); - + //To be called before the generate_xx functions. //Sorts the types such that a parent type appears in the list @@ -259,6 +259,11 @@ namespace codetree{ void add_export_veto_word(const std::string& s) { export_blacklist_.insert(s); } + //Sets version of CxxWrap the code should be generated for. + //Version is coded as + //10^6 * major_number + 1000 * minor_number + patch_number; + void set_cxxwrap_version(long val){ cxxwrap_version_ = val; } + void set_n_classes_per_file(int n_classes_per_file) { n_classes_per_file_ = n_classes_per_file; } void set_out_cxx_dir(const std::string& val) { out_cxx_dir_ = val; } @@ -268,7 +273,7 @@ namespace codetree{ void set_module_name(const std::string& val){ module_name_ = val;} void set_force_mode(bool forced){ out_open_mode_ = forced ? std::ios_base::out : std::ios_base::app; } - + protected: enum class accessor_mode_t {none, getter, both }; @@ -281,7 +286,7 @@ namespace codetree{ // and if it is the case, marks the element type as requiring // std::vector and std:valarray support. void check_for_stl(const CXType& type); - + bool in_veto_list(const std::string signature) const; //Check is a field or variable is veto status for accessor generation @@ -437,6 +442,8 @@ namespace codetree{ //before calling this function void update_wrapper_filenames(); + std::ostream& generate_version_check_cxx(std::ostream& o) const; + std::ostream& generate_type_wrapper_header(std::ostream& o) const; //Try to open file path for writing. Exit application if @@ -451,10 +458,10 @@ namespace codetree{ bool is_natively_supported(const CXType& type, int* params = nullptr) const; - + bool is_natively_supported(const std::string& type_fqn, int* nparams = nullptr) const; - + private: std::string clang_resource_dir_; @@ -462,8 +469,10 @@ namespace codetree{ std::string cmake_; + long cxxwrap_version_; + bool update_mode_; - + std::string header_file_path_; std::string module_name_; @@ -514,12 +523,12 @@ namespace codetree{ bool import_setindex_; Graph type_dependencies_; - + std::vector towrap_type_filenames_; std::set towrap_type_filenames_set_; TypeMapper type_map_; - + struct { unsigned enums = 0; unsigned types = 0; diff --git a/src/FunctionWrapper.cpp b/src/FunctionWrapper.cpp index abd4a59..e0b1ac2 100644 --- a/src/FunctionWrapper.cpp +++ b/src/FunctionWrapper.cpp @@ -11,11 +11,12 @@ #include #include "TypeMapper.h" +#include "cxxwrap_version.h" std::ostream& FunctionWrapper::gen_ctor(std::ostream& o){ int nargsmin = std::max(1, method.min_args); //no-arg ctor, aka "default ctor" generate elsewhere with direct call to gen_ctor(std::ostream& o, int nindents, - // const std::string& varname, bool templated, bool finalize, const std::string& arg_list) + // const std::string& varname_, bool templated, bool finalize, const std::string& arg_list) int nargsmax = clang_getNumArgTypes(method_type); indent(o << "\n", nindents) @@ -28,7 +29,8 @@ FunctionWrapper::gen_ctor(std::ostream& o){ for(int nargs = nargsmin; nargs <= nargsmax; ++nargs){ std::stringstream buf; gen_arg_list(buf, nargs, "", /*argtypes_only = */ true); - gen_ctor(o, nindents, varname, templated_, finalize, buf.str()); + gen_ctor(o, nindents, varname_, templated_, finalize, buf.str(), + cxxwrap_version_); } generated_jl_functions_.insert(name_jl_); return o; @@ -36,13 +38,25 @@ FunctionWrapper::gen_ctor(std::ostream& o){ std::ostream& FunctionWrapper::gen_ctor(std::ostream& o, int nindents, - const std::string& varname, bool templated, + const std::string& varname_, bool templated, bool finalize, - const std::string& arg_list){ - indent(o, nindents) << varname << "." << (templated ? "template " : "") + const std::string& arg_list, long cxxwrap_version_){ + + const char* finalize_true; + const char* finalize_false; + if(cxxwrap_version_ < cxxwrap_v0_15){ + finalize_true = "true"; + finalize_false = "false"; + } else{ + finalize_true = "jlcxx::finalize_policy::yes"; + finalize_false = "jlcxx::finalize_policy::no"; + } + + indent(o, nindents) << varname_ << "." << (templated ? "template " : "") << "constructor<" << arg_list - << ">(/*finalize=*/" << (finalize?"true":"false") << ");\n"; + << ">(/*finalize=*/" + << (finalize?finalize_true:finalize_false) << ");\n"; return o; } @@ -159,7 +173,7 @@ FunctionWrapper::gen_accessors(std::ostream& o, bool getter_only, int* ngens) { //tA.method("x", [](const ns::A& a) -> const T& { return a.x; }); // or //tA.method("x", [](const ns::A& a) -> T { return a.x; }); - indent(o, nindents) << varname << ".method(\"" << target_name_jl << "\", [](" + indent(o, nindents) << varname_ << ".method(\"" << target_name_jl << "\", [](" << "const " << classname << ref << " a) -> " << (return_by_copy ? copy_return_type(target_type) : @@ -173,7 +187,7 @@ FunctionWrapper::gen_accessors(std::ostream& o, bool getter_only, int* ngens) { //tA.method("x", [](ns::A& a) -> T& { return a.x; }); // or //tA.method("x", [](ns::A& a) -> T { return a.x; }); - indent(o, nindents) << varname << ".method(\"" << target_name_jl << "\", [](" + indent(o, nindents) << varname_ << ".method(\"" << target_name_jl << "\", [](" << classname << ref << " a) -> " << (return_by_copy ? copy_return_type(target_type) @@ -197,7 +211,7 @@ FunctionWrapper::gen_accessors(std::ostream& o, bool getter_only, int* ngens) { << "to provide write access to the field " << target_name << " (\" __HERE__ \")\");\n"; //tA.method("x!", [](ns::A& a, int val) -> T& { return a.x = val; }); - indent(o, nindents) << varname << ".method(\"" << target_name_jl << "!\", [](" + indent(o, nindents) << varname_ << ".method(\"" << target_name_jl << "!\", [](" << classname << ref << " a, " << const_type(target_type) << " val) -> " << non_const_type_or_pod(target_type) << " { return a" << op << target_name << " = val; });\n"; @@ -216,13 +230,13 @@ FunctionWrapper::gen_accessors(std::ostream& o, bool getter_only, int* ngens) { auto rtype = gen_setters ? non_const_type_or_pod(target_type) : const_type(target_type); //types.method("ns!A!x", []() -> T& { return ns::A::x; }); - indent(o, nindents) << varname << ".method(\"" << target_name_jl << "\", []()" + indent(o, nindents) << varname_ << ".method(\"" << target_name_jl << "\", []()" << "-> " << rtype << " { return " << fqn << "; });\n"; if(gen_setters){ //types.method("ns!A!x!", [](int val) -> T& { return ns::A::x = val; }); - indent(o, nindents) << varname << ".method(\"" << target_name_jl << "!\", [](" + indent(o, nindents) << varname_ << ".method(\"" << target_name_jl << "!\", [](" << const_type(target_type) << " val)" << "-> " << rtype << " { return " << fqn << " = val; });\n"; @@ -251,8 +265,7 @@ FunctionWrapper::gen_setindex(std::ostream& o) const{ << " to wrap " << signature() << " (\" __HERE__ \")\");\n" << "// defined in " << clang_getCursorLocation(method.cursor) << "\n"; - - indent(o, nindents) << varname << ".method(\"setindex!\",\n"; + indent(o, nindents) << varname_ << ".method(\"setindex!\",\n"; indent(o, nindents+1) << "[](" << classname << "& a, " << short_arg_list_cxx << " i, " << std::regex_replace(fix_template_type(fully_qualified_name(return_type_)), std::regex("\\s*&\\s*$"), " const &") @@ -279,7 +292,7 @@ FunctionWrapper::gen_getindex(std::ostream& o, << " (\" __HERE__ \")\");\n"; indent(o, nindents) << "// defined in " << clang_getCursorLocation(method.cursor) << "\n"; - indent(o, nindents) << varname << ".method(\"getindex\",\n"; + indent(o, nindents) << varname_ << ".method(\"getindex\",\n"; indent(o, nindents + 1) << "[](" << classname << "& a, " << short_arg_list_cxx << " i){\n"; indent(o, nindents + 1) << "return a[i];\n"; @@ -337,7 +350,7 @@ FunctionWrapper::gen_func_with_cast(std::ostream& o){ msg2 = std::string("of class ") + classname; } - indent(o, nindents) << varname << ".method("; + indent(o, nindents) << varname_ << ".method("; if(name_jl_.size() > 0){ //note: operator() is a special case @@ -394,7 +407,7 @@ FunctionWrapper::gen_func_with_lambdas(std::ostream& o){ for(int itype = 0; itype < ntypes; ++itype){ for(int nargs = nargsmin; nargs <= nargsmax; ++nargs){ - indent(o, nindents) << varname << ".method(\"" << name_jl_<< "\", []("; + indent(o, nindents) << varname_ << ".method(\"" << name_jl_<< "\", []("; std::string sep; if(!is_static_ && classname.size() > 0){ o << classname << cv << ref_types[itype] << " a"; @@ -467,13 +480,16 @@ FunctionWrapper::arg_decl(int iarg, bool argtypes_only) const{ } -FunctionWrapper::FunctionWrapper(const MethodRcd& method, const TypeRcd* pTypeRcd, +FunctionWrapper::FunctionWrapper(const MethodRcd& method, + const TypeRcd* pTypeRcd, const TypeMapper& type_map, + long cxxwrap_version, std::string varname, std::string classname, int nindents, bool templated): method(method), - varname(varname), + varname_(varname), classname(classname), + cxxwrap_version_(cxxwrap_version), nindents(nindents), all_lambda_(false), pTypeRcd(pTypeRcd), @@ -502,11 +518,6 @@ FunctionWrapper::FunctionWrapper(const MethodRcd& method, const TypeRcd* pTypeRc class_prefix = this->classname + "::"; } - if(varname.size() == 0){ - this->varname = "t"; - } - - is_static_ = clang_Cursor_getStorageClass(cursor) == CX_SC_Static; return_type_ = clang_getResultType(method_type); inaccessible_type = !isAccessible(return_type_); @@ -516,9 +527,19 @@ FunctionWrapper::FunctionWrapper(const MethodRcd& method, const TypeRcd* pTypeRc //cursor name of global functions are not fully qualified. name_cxx = fully_qualified_name(cursor); } - + std::string name_jl_suffix = jl_type_name(name_cxx); + is_static_ = clang_Cursor_getStorageClass(cursor) == CX_SC_Static; + + if(name_cxx == "operator new" || name_cxx == "operator delete" + || name_cxx == "operator new[]" || name_cxx == "operator delete[]" + ) is_static_ = true; + + if(varname.size() == 0){ + varname_ = is_static_ ? "module_" : "t"; + } + static std::regex opregex("(^|.*::)operator[[:space:]]*(.*)$"); std::cmatch m; override_base_ = false; @@ -616,17 +637,13 @@ FunctionWrapper::FunctionWrapper(const MethodRcd& method, const TypeRcd* pTypeRc setindex_ = true; } } - - if(name_cxx == "operator new" || name_cxx == "operator delete" - || name_cxx == "operator new[]" || name_cxx == "operator delete[]" - ) is_static_ = true; - - + if(is_static_){ - if(templated_) + if(templated_){ name_jl_ = jl_type_name(pTypeRcd->type_name + "::") + name_jl_suffix; - else - name_jl_ = jl_type_name(class_prefix) + name_jl_suffix; + } else{ + name_jl_ = jl_type_name(class_prefix) + name_jl_suffix; + } } else{ //FIXME: Add prefix. The namespace? name_jl_ = name_jl_suffix; diff --git a/src/FunctionWrapper.h b/src/FunctionWrapper.h index 6885bf3..76c70f1 100644 --- a/src/FunctionWrapper.h +++ b/src/FunctionWrapper.h @@ -26,8 +26,10 @@ class TypeMapper; // class FunctionWrapper{ public: - FunctionWrapper(const MethodRcd& method, const TypeRcd* pTypeRcd, + FunctionWrapper(const MethodRcd& method, + const TypeRcd* pTypeRcd, const TypeMapper& type_mapper, + long cxxwrap_version, std::string varname = std::string(), std::string classname = std::string(), int nindents = 0, @@ -55,7 +57,8 @@ class FunctionWrapper{ gen_ctor(std::ostream& o, int nindents, const std::string& varname, bool templated, bool finalize, - const std::string& arg_list); + const std::string& arg_list, + long cxxwrap_version); /// Tells if the Base module must be overriden. /// This is the case for operators. @@ -133,8 +136,9 @@ class FunctionWrapper{ private: + long cxxwrap_version_; MethodRcd method; - std::string varname; + std::string varname_; std::string classname; std::string class_prefix; int nindents; diff --git a/src/cxxwrap_version.cpp b/src/cxxwrap_version.cpp new file mode 100644 index 0000000..4783144 --- /dev/null +++ b/src/cxxwrap_version.cpp @@ -0,0 +1,14 @@ +#include "cxxwrap_version.h" + +//according to https://github.com/JuliaInterop/libcxxwrap-julia/releases +//we have breaking changes with libcxxwrap_jll versions, +//v0.10.0 (not used by any CxxWrap release), +//v0.11.0 (CxxWrap v0.14.0), +//v0.12.0 (CxxWrap v0.15.0) +std::tuple version_libcxxwrap_bounds(long cxxwrap_version){ + if(cxxwrap_version < cxxwrap_v0_15){ + return std::tuple(11*1000, 12*1000); + } else{ + return std::tuple(12*1000, 13*1000); + } +} diff --git a/src/cxxwrap_version.h b/src/cxxwrap_version.h new file mode 100644 index 0000000..dfd046e --- /dev/null +++ b/src/cxxwrap_version.h @@ -0,0 +1,11 @@ +#include + +//Default value for the cxxwrap_version config parameter +static const char* default_cxxwrap_version = "0.15.0"; + +static const char* supported_cxxwrap_versions = "0.14.x, 0.15.x"; + +static int cxxwrap_v0_15 = 15 * 1000; //v0.15 + +std::tuple version_libcxxwrap_bounds(long cxxwrap_version); + diff --git a/src/main.cpp b/src/main.cpp index fac36a7..c48923d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ using namespace std::string_view_literals; #include "CodeTree.h" #include "utils.h" #include "uuid_utils.h" +#include "cxxwrap_version.h" using namespace codetree; @@ -76,13 +77,17 @@ find_include_path(const fs::path& p, const std::vector& inc_dirs){ } +void append_to_table(toml::table& dest, toml::table& src){ + for (const auto& [key, value]: src) { + dest.insert_or_assign(key, value); + } +} int main(int argc, char* argv[]){ - std::srand(std::time(nullptr)); - cxxopts::Options option_list("wrapit", "Generates wrappers from a c++ header file for Cxx.jl.\n"); + // clang-format off option_list.add_options() ("h,help", "Display this help and exit") @@ -95,6 +100,12 @@ int main(int argc, char* argv[]){ "configuration file. It defines two variables WRAPIT_INPUTS and " "WRAPIT_PRODUCTS repectively with the list of input header files " "the result depends on and the list of produced files") + ("get", "Retrieves a configuration parameter. Retrieval of only few " + "parameters are currently supported.", + cxxopts::value()) + ("add-cfg", "Set a configuration parameter on top of what is defined in the" + " configuration file.", + cxxopts::value>()) ("output-prefix", "Prefix inserted to output paths", cxxopts::value()->default_value("")) ("resource-dir", std::string("Change the clang resource directory path (see clang " @@ -150,6 +161,45 @@ int main(int argc, char* argv[]){ } }; + if(options["add-cfg"].count() > 0){ + auto params= options["add-cfg"].as>(); + for(const auto& p: params){ + toml::table to_append; + try{ + to_append = toml::parse(p); + } catch (const toml::parse_error& err) { + std::cerr << "Error parsing --add-cfg parameter, '" << p << "': " + << err.what() << "\n"; + exit(1); + } + try{ + append_to_table(toml_config, to_append); + } catch (const toml::parse_error& err) { + std::cerr << "Failed to set parameter specified by --add-cfg, '" + << p << "': " << err.what() << "\n"; + exit(1); + } + } + } + + auto cxxwrap_version_str = toml_config["cxxwrap_version"].value_or(std::string("")); + std::string mess; + if(cxxwrap_version_str.size() == 0){//default version + mess = "Error. Bug found in wrapit. Cxx wrap version string defined in the file cxxwrap_version.h is not valid."; + cxxwrap_version_str = default_cxxwrap_version; + } else{ + std::stringstream buf; + buf << "Error. The format of the value of the cxxwrap_version " + "configuration parameter (\"" << cxxwrap_version_str + << "\") is not valid. Expected format: x.y.z, x.y, or x"; + mess = buf.str(); + } + auto cxxwrap_version = version_string_to_int(cxxwrap_version_str); + if(cxxwrap_version < 0){ + std::cerr << mess; + exit(1); + } + auto include_dirs = read_vpath("include_dirs", { fs::path(".")} ); //auto to_parse = read_vpath_include("input", include_dirs); auto to_parse = read_vstring("input"); @@ -234,14 +284,44 @@ int main(int argc, char* argv[]){ } - auto open_mode = std::ofstream::out; - if(options.count("force") == 0){ - open_mode |= std::ofstream::app; + auto version = toml_config["version"].value_or(std::string()); + + if(options.count("get")){ + auto param = options["get"].as(); + if(param == "cxxwrap_version") std::cout << cxxwrap_version_str << "\n"; + else if(param == "module_name") std::cout << module_name << "\n"; + else if(param == "version") std::cout << version << "\n"; + else if(param == "lib_basname") std::cout << lib_basename << "\n"; + else if(param == "export_jl_fname") std::cout << out_export_jl_fname << "\n"; + else if(param == "module_jl_fname") std::cout << out_jl_fname << "\n"; + else std::cerr << "Retrieval of parameter '" << param + << "' is not supported"; + exit(0); } + auto uuid = toml_config["uuid"].value_or(std::string()); - bool in_err = false; + if(uuid.size() == 0){ + uuid = gen_uuid(); + std::cerr << "The configuration file misses the uuid parameter. Following " + "generated uuid will be used for the generate Julia project. Add the " + "following line in the .wit configuration file to use same uuid " + "for next versions of the code.\n" + "uuid = \"" << uuid << "\"\n\n"; + } else if (!validate_uuid(uuid)){ + std::cerr << "The value \"" << uuid << "\" of the uuid parameter found in " + "the .wit configuration file is not valid. You can run first with an " + "empty string to generate a new uuid, which will be displayed to the " + "output (stderr).\n"; + return 1; + } + bool in_err = false; + auto open_mode = std::ofstream::out; + if(options.count("force") == 0){ + open_mode |= std::ofstream::app; + } + auto open_file = [&](const std::string& fname){ std::ofstream f(fname, open_mode); if(f.tellp()!=0){ @@ -282,24 +362,6 @@ int main(int argc, char* argv[]){ if(in_err) return -1; - auto uuid = toml_config["uuid"].value_or(std::string()); - - if(uuid.size() == 0){ - uuid = gen_uuid(); - std::cerr << "The configuration file misses the uuid parameter. Folloing " - "generated uuid will be used for the generate Julia project. Add the " - "following line in the .wit configuration file to use same uuid " - "for next versions of the code.\n" - "uuid = \"" << uuid << "\"\n\n"; - } else if (!validate_uuid(uuid)){ - std::cerr << "The value \"" << uuid << "\" of the uuid parameter found in " - "the .wit configuration file is not valid. You can run first with an " - "empty string to generate a new uuid, which will be displayed to the " - "output (stderr).\n"; - return 1; - } - - auto version = toml_config["version"].value_or(std::string()); verbose = verbosity; @@ -307,6 +369,8 @@ int main(int argc, char* argv[]){ tree.set_force_mode(options.count("force") > 0); + tree.set_cxxwrap_version(cxxwrap_version); + tree.add_std_option(cxx_std); tree.auto_veto(auto_veto); diff --git a/src/utils.cpp b/src/utils.cpp index 49ec86e..adf8da7 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -19,6 +19,42 @@ int verbose = 0; CXPrintingPolicy pp = nullptr; +long version_int_base = 1000; + +long version_string_to_int(const std::string& version_string){ + static std::regex r("v?([[:digit:]]{1,3}\\.)?([[:digit:]]{1,3}\\.)?([[:digit:]]{1,3})"); + std::smatch matches; + if(!std::regex_match(version_string, matches, r)){ + return -1; + } else{ + if(matches[2].str().size() > 0){ //x.y.z + return strtol(matches[3].str().c_str(), 0, 0) + + version_int_base * strtol(matches[2].str().c_str(), 0, 0) + + version_int_base * version_int_base * strtol(matches[1].str().c_str(), 0, 0); + } else if(matches[1].str().size() > 0){ //x.y + //Beware that it is the second ()? group of the regex that + //will be omitted and we have x in matches[1]; y in matches[3] + return version_int_base * strtol(matches[3].str().c_str(), 0, 0) + + version_int_base * version_int_base * strtol(matches[1].str().c_str(), 0, 0); + } else{ //x + std::cerr << "=>C\n"; + return version_int_base * version_int_base * strtol(matches[3].str().c_str(), 0, 0); + } + } +} + +std::string version_int_to_string(long iversion, int depth){ + std::stringstream buf; + buf << (iversion / version_int_base / version_int_base); + if(depth>1) buf << "." << (iversion / version_int_base) % version_int_base; + if(depth>2) buf << "." << iversion % version_int_base; + return buf.str(); +} + +int version_major(long version_int){ + return version_int / version_int_base / version_int_base; +} + std::ostream& indent(std::ostream& o, int n){ for(int i = 0; i < n; ++i){ diff --git a/src/utils.h b/src/utils.h index c9e10e6..b56f490 100644 --- a/src/utils.h +++ b/src/utils.h @@ -15,9 +15,22 @@ static const char* one_indent = " "; extern int verbose; +extern long version_int_base; std::ostream& indent(std::ostream& o, int n); +//Converts version string x.y.z (optionnally prefixed by the letter v) +//to an int which is larger for newer version +//and is defined as 10^6*x + 10^3*y + z +//return -1 if the text format was not recognised or one of the +//version subnumber includes than 3 digits. +//(long return type ensures the type has a minimum of 32 bits. +long version_string_to_int(const std::string& version_string); + +std::string version_int_to_string(long version_int, int depth = 3); + +int version_major(long version_int); + template bool has(const T& collection, const U& element){ return std::find(collection.begin(), collection.end(), element) != collection.end(); diff --git a/src/uuid_utils.cpp b/src/uuid_utils.cpp index 99afcbe..0e85329 100644 --- a/src/uuid_utils.cpp +++ b/src/uuid_utils.cpp @@ -1,16 +1,21 @@ #include "uuid_utils.h" #include +#include std::string gen_uuid(){ + unsigned char buf[32]; //unsigned is essential for the modulo + // operation done later in the code. + arc4random_buf(buf, sizeof(buf)); const char* v = "0123456789abcdef"; //3fb17ebc-bc38-4939-bc8b-74f2443281d4 //8 dash 4 dash 4 dash 4 dash 12 std::string s; s.resize(36); + auto p = buf; for(int i = 0; i < 36; ++i) { if(i != 8 && i != 13 && i != 18 && i != 23){ - s[i] = v[rand()%16]; + s[i] = v[*(p++)%16]; } else{ s[i] = '-'; } diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 739c096..0000000 --- a/test/Project.toml +++ /dev/null @@ -1,6 +0,0 @@ -[deps] -CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" -Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/TestAccessAndDelete/runTestAccessAndDelete.jl b/test/TestAccessAndDelete/runTestAccessAndDelete.jl index b5b22b7..8b5e003 100644 --- a/test/TestAccessAndDelete/runTestAccessAndDelete.jl +++ b/test/TestAccessAndDelete/runTestAccessAndDelete.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestAccessAndDelete") using TestAccessAndDelete function runtest() diff --git a/test/TestAutoAdd/runTestAutoAdd.jl b/test/TestAutoAdd/runTestAutoAdd.jl index 1a4cab0..126ab82 100644 --- a/test/TestAutoAdd/runTestAutoAdd.jl +++ b/test/TestAutoAdd/runTestAutoAdd.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestAutoAdd") using TestAutoAdd function runtest() diff --git a/test/TestCtorDefVal/runTestCtorDefVal.jl b/test/TestCtorDefVal/runTestCtorDefVal.jl index cf9d8f5..9d709a0 100644 --- a/test/TestCtorDefVal/runTestCtorDefVal.jl +++ b/test/TestCtorDefVal/runTestCtorDefVal.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestCtorDefVal") using TestCtorDefVal function runtest() diff --git a/test/TestEmptyClass/runTestEmptyClass.jl b/test/TestEmptyClass/runTestEmptyClass.jl index dc6b1a1..e1434a6 100644 --- a/test/TestEmptyClass/runTestEmptyClass.jl +++ b/test/TestEmptyClass/runTestEmptyClass.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestEmptyClass") using TestEmptyClass function runtest() diff --git a/test/TestEnum/runTestEnum.jl b/test/TestEnum/runTestEnum.jl index 82676ad..01b795e 100644 --- a/test/TestEnum/runTestEnum.jl +++ b/test/TestEnum/runTestEnum.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestEnum") using TestEnum function runtest() diff --git a/test/TestInheritance/runTestInheritance.jl b/test/TestInheritance/runTestInheritance.jl index b017b4f..4bbcfef 100644 --- a/test/TestInheritance/runTestInheritance.jl +++ b/test/TestInheritance/runTestInheritance.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestInheritance") using TestInheritance function runtest() diff --git a/test/TestNamespace/runTestNamespace.jl b/test/TestNamespace/runTestNamespace.jl index 6977c2b..550b508 100644 --- a/test/TestNamespace/runTestNamespace.jl +++ b/test/TestNamespace/runTestNamespace.jl @@ -2,7 +2,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestNamespace") using TestNamespace function runtest() diff --git a/test/TestNoFinalizer/runTestNoFinalizer.jl b/test/TestNoFinalizer/runTestNoFinalizer.jl index 3ea0fdd..9f2ed56 100644 --- a/test/TestNoFinalizer/runTestNoFinalizer.jl +++ b/test/TestNoFinalizer/runTestNoFinalizer.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestNoFinalizer") using TestNoFinalizer function runtest() diff --git a/test/TestOperators/runTestOperators.jl b/test/TestOperators/runTestOperators.jl index 1658e72..0573c69 100644 --- a/test/TestOperators/runTestOperators.jl +++ b/test/TestOperators/runTestOperators.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestOperators") using TestOperators # Test an operator executing exp. diff --git a/test/TestOrder/runTestOrder.jl b/test/TestOrder/runTestOrder.jl index 746c5c2..06621b7 100644 --- a/test/TestOrder/runTestOrder.jl +++ b/test/TestOrder/runTestOrder.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestOrder") if "-s" in ARGS #Serialize mode Test.TESTSET_PRINT_ENABLE[] = false diff --git a/test/TestPointers/runTestPointers.jl b/test/TestPointers/runTestPointers.jl index 826fe50..ddce675 100644 --- a/test/TestPointers/runTestPointers.jl +++ b/test/TestPointers/runTestPointers.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestPointers") using TestPointers using CxxWrap: CxxPtr, ConstCxxPtr diff --git a/test/TestPropagation/Makefile b/test/TestPropagation/Makefile index 2114f24..7b28fe8 100644 --- a/test/TestPropagation/Makefile +++ b/test/TestPropagation/Makefile @@ -9,7 +9,11 @@ CXXFLAGS += $(patsubst -std=gnu%,,$(shell $(JL_SHARE)/julia-config.jl --cflags)) LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags) LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) + CXXWRAP_PREFIX=$(shell julia -e 'import Pkg; haskey(Pkg.dependencies(), Base.UUID("1f15a43c-97ca-5a2a-ae31-89f07a497df4")) || Pkg.add("CxxWrap"); using CxxWrap; print(CxxWrap.prefix_path())') +CXXWRAP_VERSION:=$(shell julia --project=${BUILD_DIR} -e "import CxxWrap; print(pkgversion(CxxWrap));") +WRAPIT_OPT=--add-cfg cxxwrap_version=\"$(CXXWRAP_VERSION)\" + CXXWRAP_CPPFLAGS=-I $(CXXWRAP_PREFIX)/include -std=c++20 LDLIBS +=-L $(CXXWRAP_PREFIX)/lib -lcxxwrap_julia @@ -57,13 +61,13 @@ clean: #use of % instead of $(BUILD_DIR) prevents make to run the recipee multiple times when running with -j %/libTestPropagation1/src/jlTestPropagation1.cxx %/libTestPropagation1/src/jlTestPropagation1.h %/TestPropagation1/src/TestPropagation1.jl: TestPropagation1.wit - wrapit --force --output-prefix $* $< + wrapit $(WRAPIT_OPT) --force --output-prefix $* $< %/libTestPropagation2/src/jlTestPropagation2.cxx %/libTestPropagation2/src/jlTestPropagation2.h %/TestPropagation2/src/TestPropagation2.jl: TestPropagation2.wit - wrapit --force --output-prefix $* $< + wrapit $(WRAPIT_OPT) --force --output-prefix $* $< %/libTestPropagation3/src/jlTestPropagation3.cxx %/libTestPropagation3/src/jlTestPropagation3.h %/TestPropagation3/src/TestPropagation3.jl: TestPropagation3.wit - wrapit --force --output-prefix $* $< + wrapit $(WRAPIT_OPT) --force --output-prefix $* $< $(BUILD_DIR)/libTestPropagation1/build/%.o: $(BUILD_DIR)/libTestPropagation1/src/%.cxx [ -d $(@D) ] || mkdir -p $(@D) diff --git a/test/TestPropagation/runTestPropagation1.jl b/test/TestPropagation/runTestPropagation1.jl index c459271..1438f3c 100644 --- a/test/TestPropagation/runTestPropagation1.jl +++ b/test/TestPropagation/runTestPropagation1.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestPropagation1") import TestPropagation1 function runtest() diff --git a/test/TestPropagation/runTestPropagation2.jl b/test/TestPropagation/runTestPropagation2.jl index f8075e5..066b8b0 100644 --- a/test/TestPropagation/runTestPropagation2.jl +++ b/test/TestPropagation/runTestPropagation2.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestPropagation2") import TestPropagation2 function runtest() diff --git a/test/TestPropagation/runTestPropagation3.jl b/test/TestPropagation/runTestPropagation3.jl index d27f370..cfdb675 100644 --- a/test/TestPropagation/runTestPropagation3.jl +++ b/test/TestPropagation/runTestPropagation3.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestPropagation3") import TestPropagation3 function runtest() diff --git a/test/TestSizet/runTestSizet.jl b/test/TestSizet/runTestSizet.jl index 2bcef9b..7ee9a21 100644 --- a/test/TestSizet/runTestSizet.jl +++ b/test/TestSizet/runTestSizet.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestSizet") using TestSizet function runtest() diff --git a/test/TestStdString/runTestStdString.jl b/test/TestStdString/runTestStdString.jl index 38db2fb..0a29000 100644 --- a/test/TestStdString/runTestStdString.jl +++ b/test/TestStdString/runTestStdString.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestStdString") using TestStdString function runtest() diff --git a/test/TestStdVector/TestStdVector.wit b/test/TestStdVector/TestStdVector.wit index dccf133..76a3f3c 100644 --- a/test/TestStdVector/TestStdVector.wit +++ b/test/TestStdVector/TestStdVector.wit @@ -10,5 +10,8 @@ export = "all" fields_and_variables = false +# Currently not working with CxxWrap 0.15.0, use 0.14.x +cxxwrap_version = "0.14" + # all generated code in a single file: #n_classes_per_file = 0 diff --git a/test/TestStdVector/runTestStdVector.jl b/test/TestStdVector/runTestStdVector.jl index eb91718..8815574 100644 --- a/test/TestStdVector/runTestStdVector.jl +++ b/test/TestStdVector/runTestStdVector.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestStdVector") using TestStdVector using CxxWrap: StdVector diff --git a/test/TestStringView/runTestStringView.jl b/test/TestStringView/runTestStringView.jl index 91c9a48..b32ea23 100644 --- a/test/TestStringView/runTestStringView.jl +++ b/test/TestStringView/runTestStringView.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestStringView") using TestStringView function runtest() diff --git a/test/TestTemplate1/runTestTemplate1.jl b/test/TestTemplate1/runTestTemplate1.jl index bfa735d..18a13f3 100644 --- a/test/TestTemplate1/runTestTemplate1.jl +++ b/test/TestTemplate1/runTestTemplate1.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path=joinpath(@__DIR__, "build", "TestTemplate1")) using TestTemplate1 function runtest() diff --git a/test/TestTemplate2/runTestTemplate2.jl b/test/TestTemplate2/runTestTemplate2.jl index b362049..8a596f0 100644 --- a/test/TestTemplate2/runTestTemplate2.jl +++ b/test/TestTemplate2/runTestTemplate2.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestTemplate2") using TestTemplate2 diff --git a/test/TestUsingType/TestUsingType.wit b/test/TestUsingType/TestUsingType.wit index bc2b014..1ff991b 100644 --- a/test/TestUsingType/TestUsingType.wit +++ b/test/TestUsingType/TestUsingType.wit @@ -12,6 +12,10 @@ export = "all" auto_veto = true +# Currently not working with CxxWrap 0.15.0, use 0.14.x +cxxwrap_version = "0.14" + + # all generated code in a single file: n_classes_per_file = 0 diff --git a/test/TestUsingType/runTestUsingType.jl b/test/TestUsingType/runTestUsingType.jl index 69c8eb9..32de2a2 100644 --- a/test/TestUsingType/runTestUsingType.jl +++ b/test/TestUsingType/runTestUsingType.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestUsingType") using TestUsingType function runtest() diff --git a/test/TestVarField/runTestVarField.jl b/test/TestVarField/runTestVarField.jl index 681eef0..71d4628 100755 --- a/test/TestVarField/runTestVarField.jl +++ b/test/TestVarField/runTestVarField.jl @@ -1,11 +1,18 @@ #!/usr/bin/env julia using Test using Serialization +import Pkg function runtest() + args = collect(Base.julia_cmd()) + push!(args, "") + iscript = length(args) + push!(args, "-s") + ENV["JULIA_PROJECT"] = Pkg.project().path @testset verbose=true "TestVarField" begin - for test_script in ["runTestVarFieldOn.jl" "runTestVarFieldOff.jl"] - Test.record(Test.get_testset(), deserialize(open(Cmd(`julia --project=.. $test_script -s`, dir=@__DIR__)))) + for testname in ["TestVarFieldOn" "TestVarFieldOff"] + args[iscript] = "run" * testname * ".jl" + Test.record(Test.get_testset(), deserialize(open(Cmd(Cmd(args), dir=@__DIR__)))) end end end diff --git a/test/TestVarField/runTestVarFieldOff.jl b/test/TestVarField/runTestVarFieldOff.jl index 6c522d0..41c209b 100644 --- a/test/TestVarField/runTestVarFieldOff.jl +++ b/test/TestVarField/runTestVarFieldOff.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestVarFieldOff") using TestVarFieldOff function runtest() diff --git a/test/TestVarField/runTestVarFieldOn.jl b/test/TestVarField/runTestVarFieldOn.jl index 1fc635d..229070a 100644 --- a/test/TestVarField/runTestVarFieldOn.jl +++ b/test/TestVarField/runTestVarFieldOn.jl @@ -1,7 +1,9 @@ using Test using Serialization -push!(LOAD_PATH, "$(@__DIR__)/build") +import Pkg +Pkg.activate(;temp=true) +Pkg.develop(path="$(@__DIR__)/build/TestVarFieldOn") using TestVarFieldOn function runtest() diff --git a/test/WrapitTestSetup.cmake b/test/WrapitTestSetup.cmake index 912d4fa..6e5261c 100644 --- a/test/WrapitTestSetup.cmake +++ b/test/WrapitTestSetup.cmake @@ -1,12 +1,9 @@ -## Standard pieces for setting up WrapIt tests with CMake +# Standard pieces for setting up WrapIt tests with CMake # This files needs: # - wrapit executable, which will be looked for in the standard program path. # Its location can be specified with -DWRAPIT=... # -# - print-cxx-path.jl, which will be looked for in the same directory containing -# this file -# # Assumptions: # # - the test script is named run.jl, with the cmake projet name @@ -20,39 +17,13 @@ # It will be replaced by the value define by the project() CMakeLists.txt statement when generating # the actual .wit file from the .wit.in -# Path for CxxWrap cmake files: -find_program(PRINT_CXXWRAP_PATH print-cxxwrap-path.jl - PATHS "${CMAKE_CURRENT_LIST_DIR}/../buildtools") - -if(PRINT_CXXWRAP_PATH STREQUAL PRINT_CXXWRAP_PATH-NOTFOUND) - message(FATAL_ERROR "Failed to find the print-cxxwarp-path.jl build tool.") -endif() - -execute_process( - COMMAND "${PRINT_CXXWRAP_PATH}" - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE CXXWRAP_PREFIX - RESULT_VARIABLE result) - -if(NOT result EQUAL 0) - message(FATAL_ERROR "Execution of ${PRINT_CXXWRAP_PATH} failed") -endif() - -list(APPEND CMAKE_PREFIX_PATH ${CXXWRAP_PREFIX}) +# julia is used to retrieve the CxxWrap library paths +find_program(JULIA julia REQUIRED) # Some standard options and paths set(CMAKE_MACOSX_RPATH 1) #set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}/deps") -# Loaction of JLCxx - this should be injected via DCMAKE_PREFIX_PATH -# from the CxxWrap.prefix_path() in Julia -find_package(JlCxx) -get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION) -get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY) -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${JlCxx_location}") - -message(STATUS "Found JlCxx at ${JlCxx_location}") - # C++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -78,11 +49,76 @@ set(WRAPIT_WIT_FILE "${CMAKE_SOURCE_DIR}/${CMAKE_PROJECT_NAME}.wit") set(WRAPPER_LIB "jl${CMAKE_PROJECT_NAME}") set(WRAPPER_JULIA_PACKAGE_DIR "${CMAKE_PROJECT_NAME}") set(WRAPPER_JULIA_PACKAGE_FILE "${CMAKE_PROJECT_NAME}.jl") + +# julia is used to retrieve the CxxWrap library paths +find_program(JULIA julia REQUIRED) + +message(STATUS "Wrapit configuration file: ${WRAPIT_WIT_FILE}") + +execute_process( + COMMAND ${JULIA} "--project=${CMAKE_BINARY_DIR}" -e "import TOML; print(get(TOML.parse(open(\"${WRAPIT_WIT_FILE}\")), \"cxxwrap_version\", \"\"));" + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE CXXWRAP_REQUESTED_VERSION + RESULT_VARIABLE result + ) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to parse ${WRAPIT_WIT_FILE}") +endif() + +if("${CXXWRAP_REQUESTED_VERSION}" STREQUAL "") + execute_process( + COMMAND ${JULIA} --project=${CMAKE_BINARY_DIR} -e "import Pkg; Pkg.add(\"CxxWrap\"); import CxxWrap; print(pkgversion(CxxWrap));" + OUTPUT_VARIABLE CXXWRAP_INSTALLED_VERSION + RESULT_VARIABLE result + ) + #if no CxxWrap version requirement was specified in the .wit file, + #we align it to the version that was installed + set(WRAPIT_OPT --add-cfg "cxxwrap_version=\"${CXXWRAP_INSTALLED_VERSION}\"") + message(STATUS ${WRAPIT}) +else() + execute_process( + COMMAND ${JULIA} --project=${CMAKE_BINARY_DIR} -e "import Pkg; import CxxWrap; Pkg.add(name=\"CxxWrap\", version=\"${CXXWRAP_REQUESTED_VERSION}\"); Pkg.resolve(); print(pkgversion(CxxWrap));" + OUTPUT_VARIABLE CXXWRAP_INSTALLED_VERSION + RESULT_VARIABLE result) +endif() + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to install CxxWrap") +elseif("${CXXWRAP_REQUESTED_VERSION}" STREQUAL "") + message(STATUS "CxxWrap version requested to be compatible with any version, using v${CXXWRAP_INSTALLED_VERSION}") +else() + message(STATUS "CxxWrap version requested to be compatible with ${CXXWRAP_REQUESTED_VERSION}, using version: ${CXXWRAP_INSTALLED_VERSION}") +endif() + +execute_process( + COMMAND "${JULIA}" --project=${CMAKE_BINARY_DIR} -e "import CxxWrap; print(CxxWrap.prefix_path())" + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE CXXWRAP_PREFIX + RESULT_VARIABLE result) + +if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to retrieve CxxWrap library path") +else() + message(STATUS "CxxWrap library path prefix: ${CXXWRAP_PREFIX}") +endif() + +#This find_package(JlCxx...) command modifies CMAKE_CXX_STANDARD. +#So we save our value and restore it. +set(SAVED_CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD}) +find_package(JlCxx PATHS ${CXXWRAP_PREFIX}) +set(CMAKE_CXX_STANDARD ${SAVED_CMAKE_CXX_STANDARD}) + +get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION) +get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY) +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${JlCxx_location}") + + #configure_file(${CMAKE_SOURCE_DIR}/${CMAKE_PROJECT_NAME}.wit.in ${CMAKE_BINARY_DIR}/${WRAPIT_WIT_FILE} @ONLY) # Generate the wrapper code. This is done at configure time. execute_process( - COMMAND "${WRAPIT}" -v "${WRAPIT_VERBOSITY}" --force --update --cmake --output-prefix "${CMAKE_BINARY_DIR}" "${WRAPIT_WIT_FILE}" + COMMAND "${WRAPIT}" ${WRAPIT_OPT} -v "${WRAPIT_VERBOSITY}" --force --update --cmake --output-prefix "${CMAKE_BINARY_DIR}" "${WRAPIT_WIT_FILE}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMAND_ECHO STDERR RESULT_VARIABLE result) @@ -132,4 +168,4 @@ enable_testing() uncapitalize(${PROJECT_NAME} TEST_SCRIPT) set(TEST_SCRIPT "${CMAKE_SOURCE_DIR}/${TEST_SCRIPT}.jl") set(TEST_NAME ${CMAKE_PROJECT_NAME}) -add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND env JULIA_LOAD_PATH=:${CMAKE_BINARY_DIR}/${WRAPPER_JULIA_PACKAGE_DIR}/src julia --project=.. "${TEST_SCRIPT}") +add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND env JULIA_LOAD_PATH=:${CMAKE_BINARY_DIR}/${WRAPPER_JULIA_PACKAGE_DIR}/src julia --project=${CMAKE_BINARY_DIR} "${TEST_SCRIPT}") diff --git a/test/make.rules b/test/make.rules index 29e7991..23c5073 100644 --- a/test/make.rules +++ b/test/make.rules @@ -13,7 +13,15 @@ CXXFLAGS += $(patsubst -std=gnu%,,$(shell $(JL_SHARE)/julia-config.jl --cflags)) LDFLAGS += $(shell $(JL_SHARE)/julia-config.jl --ldflags) LDLIBS += $(shell $(JL_SHARE)/julia-config.jl --ldlibs) WRAPIT = $(shell which wrapit) -CXXWRAP_PREFIX=$(shell ../../buildtools/print-cxxwrap-path.jl) +WIT_FILE = $(MODULE_NAME).wit + +ifeq ($(CXXWRAP_VERSION),) +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(\"CxxWrap\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +CXXWRAP_VERSION=$(shell julia -e "import CxxWrap; print(pkgversion(CxxWrap));") +WRAPIT_OPT=--add-cfg cxxwrap_version=\"$(CXXWRAP_VERSION)\" +else +CXXWRAP_PREFIX=$(shell mkdir -p $(BUILD_DIR); julia --project=$(BUILD_DIR) -e "import Pkg; Pkg.add(name=\"CxxWrap\", version=\"$(CXXWRAP_VERSION)\"); Pkg.resolve(); import CxxWrap; print(CxxWrap.prefix_path())") +endif WRAPIT_PRODUCTS+=jl$(MODULE_NAME).cxx WRAPIT_ALL_PRODUCTS=lib$(MODULE_NAME)/src/$(WRAPIT_PRODUCTS) lib$(MODULE_NAME)/src/jl$(MODULE_NAME).h $(MODULE_NAME)/src/$(MODULE_NAME).jl @@ -57,7 +65,7 @@ clean: # Use of % instead of $(BUILD_DIR) instructs make that the recipee produces all the targets at once and prevent multiple executions with the -j N option. $(addprefix %,$(WRAPIT_ALL_PRODUCTS)): $(MODULE_NAME).wit - $(WRAPIT) -v $(WRAPIT_VERBOSITY) --force --output-prefix $(BUILD_DIR) $< + $(WRAPIT) $(WRAPIT_OPT) -v $(WRAPIT_VERBOSITY) --force --output-prefix $(BUILD_DIR) $< $(BUILD_DIR)/lib$(MODULE_NAME)/src/%.cxx: %.cxx [ -d $(@D) ] || mkdir -p $(@D) diff --git a/test/runtests.jl b/test/runtests.jl index 1ffe587..246345f 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,10 +1,5 @@ #!/usr/bin/env julia # -# Bootstrap environment for the first run -using Pkg -Pkg.activate(@__DIR__) -Pkg.instantiate() - using Test using Serialization tests = [ "TestSizet", "TestCtorDefVal", "TestAccessAndDelete", "TestNoFinalizer", "TestInheritance", @@ -18,7 +13,7 @@ testExamples = true #List of test can also be specified on the command line if length(ARGS) > 0 - tests = filter(x->x ≠ "examples", ARGS) + tests = replace.(filter(x->x ≠ "examples", ARGS), r"/$" => "") if length(ARGS) == length(tests) testExamples = false end @@ -29,7 +24,7 @@ ncores=Sys.CPU_THREADS @testset verbose=true "Tests" begin for t in tests - @testset "$t" failfast=true begin + @testset "$t" failfast=false begin source_dir = t build_dir = joinpath(source_dir, "build") test_script = "run" * t * ".jl" @@ -39,11 +34,12 @@ ncores=Sys.CPU_THREADS println("Try to run cmake...") #cmake based build # Configure and build with CMake - run(`cmake -S $source_dir -B $build_dir`) + run(`cmake --fresh -S $source_dir -B $build_dir`) run(`cmake --build $build_dir -j $ncores`) else println("source_dir:", source_dir) #assumes plain make build + run(`make -C $source_dir clean`) run(`make -C $source_dir -j $ncores`) end true diff --git a/test/test_examples.jl b/test/test_examples.jl index 71b6661..3a7501c 100755 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -4,6 +4,8 @@ using Test import Pkg Pkg.activate(; temp=true) +#use current project also for spawned julia processes: +ENV["JULIA_PROJECT"]=Pkg.project().path ex_basedir = normpath(joinpath(pwd(), "../examples")) @@ -31,11 +33,6 @@ catch println(stderr, "ROOT software not found. The ROOT example won't be tested.") end -#FIXME. -#Temporary trick to get the 'make test' command run in an environment -#with CxxWrap installed until. Also a nice way to get them run in -#a clean environment. -ENV["JULIA_PROJECT"]=Pkg.project().path if run_exroot ex = "ex002-ROOT"