diff --git a/README-CMake.md b/README-CMake.md index 2cec1653a..0943e7a7d 100644 --- a/README-CMake.md +++ b/README-CMake.md @@ -279,6 +279,10 @@ The following useful options can be passed to CMake whilst configuring. * ``INSTALL_DOTNET_BINDINGS`` - BOOL. If set to ``TRUE`` and ``BUILD_DOTNET_BINDINGS`` is ``TRUE`` then running the ``install`` target will install Z3's .NET bindings. * ``DOTNET_CSC_EXECUTABLE`` - STRING. The path to the C# compiler to use. Only relevant if ``BUILD_DOTNET_BINDINGS`` is set to ``TRUE``. * ``DOTNET_GACUTIL_EXECUTABLE`` - STRING. The path to the gacutil program to use. Only relevant if ``BUILD_DOTNET_BINDINGS`` is set to ``TRUE``. +* ``BUILD_JAVA_BINDINGS`` - BOOL. If set to ``TRUE`` then Z3's Java bindings will be built. +* ``INSTALL_JAVA_BINDINGS`` - BOOL. If set to ``TRUE`` and ``BUILD_JAVA_BINDINGS`` is ``TRUE`` then running the ``install`` target will install Z3's Java bindings. +* ``Z3_JAVA_JAR_INSTALLDIR`` - STRING. The path to directory to install the Z3 Java ``.jar`` file. This path should be relative to ``CMAKE_INSTALL_PREFIX``. +* ``Z3_JAVA_JNI_LIB_INSTALLDIRR`` - STRING. The path to directory to install the Z3 Java JNI bridge library. This path should be relative to ``CMAKE_INSTALL_PREFIX``. On the command line these can be passed to ``cmake`` using the ``-D`` option. In ``ccmake`` and ``cmake-gui`` these can be set in the user interface. @@ -286,8 +290,29 @@ Example ``` cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_TRACING=FALSE ../ + ``` +## Z3 API Bindings + +Z3 exposes various language bindings for its API. Below are some notes on building +and/or installing these bindings when building Z3 with CMake. + +### Java bindings + +The CMake build uses the ``FindJava`` and ``FindJNI`` cmake modules to detect the +installation of Java. If CMake fails to find your installation of Java set the +``JAVA_HOME`` environment variable when invoking CMake so that it points at the +correct location. For example + +``` +JAVA_HOME=/usr/lib/jvm/default cmake -DBUILD_JAVA_BINDINGS=ON ../ +``` +Note that the built ``.jar`` file is named ``com.microsoft.z3-VERSION.jar`` +where ``VERSION`` is the Z3 version. Under non Windows systems a +symbolic link named ``com.microsoft.z3.jar`` is provided. This symbolic +link is not created when building under Windows. + ## Developer/packager notes These notes are help developers and packagers of Z3. diff --git a/contrib/cmake/src/CMakeLists.txt b/contrib/cmake/src/CMakeLists.txt index 13403afb1..574dd1089 100644 --- a/contrib/cmake/src/CMakeLists.txt +++ b/contrib/cmake/src/CMakeLists.txt @@ -234,4 +234,16 @@ if (BUILD_DOTNET_BINDINGS) add_subdirectory(api/dotnet) endif() +################################################################################ +# Java bindings +################################################################################ +option(BUILD_JAVA_BINDINGS "Build Java bindings for Z3" OFF) +if (BUILD_JAVA_BINDINGS) + if (NOT BUILD_LIBZ3_SHARED) + message(FATAL_ERROR "The Java bindings will not work with a static libz3. " + "You either need to disable BUILD_JAVA_BINDINGS or enable BUILD_LIBZ3_SHARED") + endif() + add_subdirectory(api/java) +endif() + # TODO: Implement support for other bindigns diff --git a/contrib/cmake/src/api/java/CMakeLists.txt b/contrib/cmake/src/api/java/CMakeLists.txt new file mode 100644 index 000000000..6e16ddb74 --- /dev/null +++ b/contrib/cmake/src/api/java/CMakeLists.txt @@ -0,0 +1,236 @@ +find_package(Java REQUIRED) +find_package(JNI REQUIRED) +include(UseJava) + +# Sanity check for dirty source tree +foreach (file_name "enumerations" "Native.cpp" "Native.java") + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${file_name}") + message(FATAL_ERROR "\"${CMAKE_CURRENT_SOURCE_DIR}/${file_name}\"" + ${z3_polluted_tree_msg}) + endif() +endforeach() + +set(Z3_JAVA_PACKAGE_NAME "com.microsoft.z3") + +# Rule to generate ``Native.java`` and ``Native.cpp`` +set(Z3_JAVA_NATIVE_JAVA "${CMAKE_CURRENT_BINARY_DIR}/Native.java") +set(Z3_JAVA_NATIVE_CPP "${CMAKE_CURRENT_BINARY_DIR}/Native.cpp") +add_custom_command(OUTPUT "${Z3_JAVA_NATIVE_JAVA}" "${Z3_JAVA_NATIVE_CPP}" + COMMAND "${PYTHON_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/scripts/update_api.py" + ${Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN} + "--java-output-dir" + "${CMAKE_CURRENT_BINARY_DIR}" + "--java-package-name" + ${Z3_JAVA_PACKAGE_NAME} + DEPENDS + ${Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN} + "${CMAKE_SOURCE_DIR}/scripts/update_api.py" + ${Z3_GENERATED_FILE_EXTRA_DEPENDENCIES} + # FIXME: When update_api.py no longer uses ``mk_util`` drop this dependency + "${CMAKE_SOURCE_DIR}/scripts/mk_util.py" + COMMENT "Generating \"${Z3_JAVA_NATIVE_JAVA}\" and \"${Z3_JAVA_NATIVE_CPP}\"" + ${ADD_CUSTOM_COMMAND_USES_TERMINAL_ARG} +) + +# Add rule to build native code that provides a bridge between +# ``Native.java`` and libz3's interfac3. +add_library(z3java SHARED ${Z3_JAVA_NATIVE_CPP}) +target_link_libraries(z3java PRIVATE libz3) +# FIXME: +# Not sure if using all the flags used by the Z3 components is really necessary +# here. At the bare minimum setting _AMD64_ depending on the target is +# necessary but seeing as the Python build system uses all the flags used for building +# Z3's components to build ``Native.cpp`` lets do the same for now. +target_compile_options(z3java PRIVATE ${Z3_COMPONENT_CXX_FLAGS}) +target_compile_definitions(z3java PRIVATE ${Z3_COMPONENT_CXX_DEFINES}) +z3_append_linker_flag_list_to_target(z3java ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) +target_include_directories(z3java PRIVATE + "${CMAKE_SOURCE_DIR}/src/api" + "${CMAKE_BINARY_DIR}/src/api" + ${JNI_INCLUDE_DIRS} +) +# FIXME: Should this library have SONAME and VERSION set? + +# This prevents CMake from automatically defining ``z3java_EXPORTS`` +set_property(TARGET z3java PROPERTY DEFINE_SYMBOL "") + +# Rule to generate the ``com.microsoft.z3.enumerations`` package +# FIXME: This list of files is fragile +set(Z3_JAVA_ENUMERATION_PACKAGE_FILES + Z3_ast_kind.java + Z3_ast_print_mode.java + Z3_decl_kind.java + Z3_error_code.java + Z3_goal_prec.java + Z3_lbool.java + Z3_param_kind.java + Z3_parameter_kind.java + Z3_sort_kind.java + Z3_symbol_kind.java +) +set(Z3_JAVA_ENUMERATION_PACKAGE_FILES_FULL_PATH "") +foreach (enum_file ${Z3_JAVA_ENUMERATION_PACKAGE_FILES}) + list(APPEND Z3_JAVA_ENUMERATION_PACKAGE_FILES_FULL_PATH + "${CMAKE_CURRENT_BINARY_DIR}/enumerations/${enum_file}" + ) +endforeach() +add_custom_command(OUTPUT ${Z3_JAVA_ENUMERATION_PACKAGE_FILES_FULL_PATH} + COMMAND "${PYTHON_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/scripts/mk_consts_files.py" + ${Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN} + "--java-output-dir" + "${CMAKE_CURRENT_BINARY_DIR}" + "--java-package-name" + ${Z3_JAVA_PACKAGE_NAME} + DEPENDS + ${Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN} + "${CMAKE_SOURCE_DIR}/scripts/mk_consts_files.py" + ${Z3_GENERATED_FILE_EXTRA_DEPENDENCIES} + COMMENT "Generating ${Z3_JAVA_PACKAGE_NAME}.enumerations package" + ${ADD_CUSTOM_COMMAND_USES_TERMINAL_ARG} +) + +set(Z3_JAVA_JAR_SOURCE_FILES + AlgebraicNum.java + ApplyResultDecRefQueue.java + ApplyResult.java + ArithExpr.java + ArithSort.java + ArrayExpr.java + ArraySort.java + ASTDecRefQueue.java + AST.java + AstMapDecRefQueue.java + ASTMap.java + AstVectorDecRefQueue.java + ASTVector.java + BitVecExpr.java + BitVecNum.java + BitVecSort.java + BoolExpr.java + BoolSort.java + Constructor.java + ConstructorList.java + Context.java + DatatypeExpr.java + DatatypeSort.java + EnumSort.java + Expr.java + FiniteDomainExpr.java + FiniteDomainNum.java + FiniteDomainSort.java + FixedpointDecRefQueue.java + Fixedpoint.java + FPExpr.java + FPNum.java + FPRMExpr.java + FPRMNum.java + FPRMSort.java + FPSort.java + FuncDecl.java + FuncInterpDecRefQueue.java + FuncInterpEntryDecRefQueue.java + FuncInterp.java + Global.java + GoalDecRefQueue.java + Goal.java + IDecRefQueue.java + IDisposable.java + InterpolationContext.java + IntExpr.java + IntNum.java + IntSort.java + IntSymbol.java + ListSort.java + Log.java + ModelDecRefQueue.java + Model.java + OptimizeDecRefQueue.java + Optimize.java + ParamDescrsDecRefQueue.java + ParamDescrs.java + ParamsDecRefQueue.java + Params.java + Pattern.java + ProbeDecRefQueue.java + Probe.java + Quantifier.java + RatNum.java + RealExpr.java + RealSort.java + ReExpr.java + RelationSort.java + ReSort.java + SeqExpr.java + SeqSort.java + SetSort.java + SolverDecRefQueue.java + Solver.java + Sort.java + StatisticsDecRefQueue.java + Statistics.java + Status.java + StringSymbol.java + Symbol.java + TacticDecRefQueue.java + Tactic.java + TupleSort.java + UninterpretedSort.java + Version.java + Z3Exception.java + Z3Object.java +) +set(Z3_JAVA_JAR_SOURCE_FILES_FULL_PATH "") +foreach (java_src_file ${Z3_JAVA_JAR_SOURCE_FILES}) + list(APPEND Z3_JAVA_JAR_SOURCE_FILES_FULL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${java_src_file}") +endforeach() +# Add generated files to list +list(APPEND Z3_JAVA_JAR_SOURCE_FILES_FULL_PATH + ${Z3_JAVA_NATIVE_JAVA} + ${Z3_JAVA_ENUMERATION_PACKAGE_FILES_FULL_PATH} +) + +# Convenient top-level target +add_custom_target(build_z3_java_bindings + ALL + DEPENDS + z3java + z3JavaJar +) + +# Rule to build ``com.microsoft.z3.jar`` +# TODO: Should we set ``CMAKE_JNI_TARGET`` to ``TRUE``? +add_jar(z3JavaJar + SOURCES ${Z3_JAVA_JAR_SOURCE_FILES_FULL_PATH} + OUTPUT_NAME ${Z3_JAVA_PACKAGE_NAME} + OUTPUT_DIR "${CMAKE_BINARY_DIR}" + VERSION "${Z3_VERSION}" +) + +############################################################################### +# Install +############################################################################### +option(INSTALL_JAVA_BINDINGS "Install Java bindings when invoking install target" ON) +if (INSTALL_JAVA_BINDINGS) + # Provide cache variables for the install locations that the user can change. + # This defaults to ``/usr/local/java`` which seems to be the location for ``.jar`` + # files on Linux distributions + set(Z3_JAVA_JAR_INSTALLDIR + "${CMAKE_INSTALL_DATAROOTDIR}/java" + CACHE + PATH + "Directory to install Z3 Java jar file relative to install prefix" + ) + # FIXME: I don't think this the right installation location + set(Z3_JAVA_JNI_LIB_INSTALLDIR + "${CMAKE_INSTALL_LIBDIR}" + CACHE + PATH + "Directory to install Z3 Java JNI bridge library relative to install prefix" + ) + install(TARGETS z3java DESTINATION "${Z3_JAVA_JNI_LIB_INSTALLDIR}") + # Note: Don't use ``DESTINATION`` here as the version of ``UseJava.cmake`` shipped + # with CMake 2.8.12.2 handles that incorrectly. + install_jar(z3JavaJar "${Z3_JAVA_JAR_INSTALLDIR}") +endif() diff --git a/scripts/mk_consts_files.py b/scripts/mk_consts_files.py index 482b13c94..e582d8468 100755 --- a/scripts/mk_consts_files.py +++ b/scripts/mk_consts_files.py @@ -17,6 +17,11 @@ def main(args): parser.add_argument("api_files", nargs="+") parser.add_argument("--z3py-output-dir", dest="z3py_output_dir", default=None) parser.add_argument("--dotnet-output-dir", dest="dotnet_output_dir", default=None) + parser.add_argument("--java-output-dir", dest="java_output_dir", default=None) + parser.add_argument("--java-package-name", + dest="java_package_name", + default=None, + help="Name to give the Java package (e.g. ``com.microsoft.z3``).") pargs = parser.parse_args(args) if not mk_genfile_common.check_files_exist(pargs.api_files): @@ -41,6 +46,20 @@ def main(args): logging.info('Generated "{}"'.format(output)) count += 1 + if pargs.java_output_dir: + if pargs.java_package_name == None: + logging.error('Java package name must be specified') + return 1 + if not mk_genfile_common.check_dir_exists(pargs.java_output_dir): + return 1 + outputs = mk_genfile_common.mk_z3consts_java_internal( + pargs.api_files, + pargs.java_package_name, + pargs.java_output_dir) + for generated_file in outputs: + logging.info('Generated "{}"'.format(generated_file)) + count += 1 + if count == 0: logging.info('No files generated. You need to specific an output directory' ' for the relevant langauge bindings') diff --git a/scripts/mk_genfile_common.py b/scripts/mk_genfile_common.py index 0734d52b0..b8d6ac5e1 100644 --- a/scripts/mk_genfile_common.py +++ b/scripts/mk_genfile_common.py @@ -255,6 +255,116 @@ def mk_z3consts_dotnet_internal(api_files, output_dir): z3consts.close() return z3consts_output_path + +def mk_z3consts_java_internal(api_files, package_name, output_dir): + """ + Generate "com.microsoft.z3.enumerations" package from the list of API + header files in ``api_files`` and write the package directory into + the ``output_dir`` directory + + Returns a list of the generated java source files. + """ + blank_pat = re.compile("^ *$") + comment_pat = re.compile("^ *//.*$") + typedef_pat = re.compile("typedef enum *") + typedef2_pat = re.compile("typedef enum { *") + openbrace_pat = re.compile("{ *") + closebrace_pat = re.compile("}.*;") + + DeprecatedEnums = [ 'Z3_search_failure' ] + gendir = os.path.join(output_dir, "enumerations") + if not os.path.exists(gendir): + os.mkdir(gendir) + + generated_enumeration_files = [] + for api_file in api_files: + api = open(api_file, 'r') + + SEARCHING = 0 + FOUND_ENUM = 1 + IN_ENUM = 2 + + mode = SEARCHING + decls = {} + idx = 0 + + linenum = 1 + for line in api: + m1 = blank_pat.match(line) + m2 = comment_pat.match(line) + if m1 or m2: + # skip blank lines and comments + linenum = linenum + 1 + elif mode == SEARCHING: + m = typedef_pat.match(line) + if m: + mode = FOUND_ENUM + m = typedef2_pat.match(line) + if m: + mode = IN_ENUM + decls = {} + idx = 0 + elif mode == FOUND_ENUM: + m = openbrace_pat.match(line) + if m: + mode = IN_ENUM + decls = {} + idx = 0 + else: + assert False, "Invalid %s, line: %s" % (api_file, linenum) + else: + assert mode == IN_ENUM + words = re.split('[^\-a-zA-Z0-9_]+', line) + m = closebrace_pat.match(line) + if m: + name = words[1] + if name not in DeprecatedEnums: + efile = open('%s.java' % os.path.join(gendir, name), 'w') + generated_enumeration_files.append(efile.name) + efile.write('/**\n * Automatically generated file\n **/\n\n') + efile.write('package %s.enumerations;\n\n' % package_name) + + efile.write('/**\n') + efile.write(' * %s\n' % name) + efile.write(' **/\n') + efile.write('public enum %s {\n' % name) + efile.write + first = True + # Iterate over key-value pairs ordered by value + for k, v in sorted(decls.items(), key=lambda pair: pair[1]): + if first: + first = False + else: + efile.write(',\n') + efile.write(' %s (%s)' % (k, v)) + efile.write(";\n") + efile.write('\n private final int intValue;\n\n') + efile.write(' %s(int v) {\n' % name) + efile.write(' this.intValue = v;\n') + efile.write(' }\n\n') + efile.write(' public static final %s fromInt(int v) {\n' % name) + efile.write(' for (%s k: values()) \n' % name) + efile.write(' if (k.intValue == v) return k;\n') + efile.write(' return values()[0];\n') + efile.write(' }\n\n') + efile.write(' public final int toInt() { return this.intValue; }\n') + # efile.write(';\n %s(int v) {}\n' % name) + efile.write('}\n\n') + efile.close() + mode = SEARCHING + else: + if words[2] != '': + if len(words[2]) > 1 and words[2][1] == 'x': + idx = int(words[2], 16) + else: + idx = int(words[2]) + decls[words[1]] = idx + idx = idx + 1 + linenum = linenum + 1 + api.close() + return generated_enumeration_files + + ############################################################################### # Functions for generating a "module definition file" for MSVC ############################################################################### diff --git a/scripts/mk_util.py b/scripts/mk_util.py index be641ba35..38bf2a854 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -2753,109 +2753,19 @@ def mk_z3consts_dotnet(api_files): # Extract enumeration types from z3_api.h, and add Java definitions def mk_z3consts_java(api_files): - blank_pat = re.compile("^ *$") - comment_pat = re.compile("^ *//.*$") - typedef_pat = re.compile("typedef enum *") - typedef2_pat = re.compile("typedef enum { *") - openbrace_pat = re.compile("{ *") - closebrace_pat = re.compile("}.*;") - java = get_component(JAVA_COMPONENT) - - DeprecatedEnums = [ 'Z3_search_failure' ] - gendir = os.path.join(java.src_dir, "enumerations") - if not os.path.exists(gendir): - os.mkdir(gendir) - + full_path_api_files = [] for api_file in api_files: api_file_c = java.find_file(api_file, java.name) api_file = os.path.join(api_file_c.src_dir, api_file) - - api = open(api_file, 'r') - - SEARCHING = 0 - FOUND_ENUM = 1 - IN_ENUM = 2 - - mode = SEARCHING - decls = {} - idx = 0 - - linenum = 1 - for line in api: - m1 = blank_pat.match(line) - m2 = comment_pat.match(line) - if m1 or m2: - # skip blank lines and comments - linenum = linenum + 1 - elif mode == SEARCHING: - m = typedef_pat.match(line) - if m: - mode = FOUND_ENUM - m = typedef2_pat.match(line) - if m: - mode = IN_ENUM - decls = {} - idx = 0 - elif mode == FOUND_ENUM: - m = openbrace_pat.match(line) - if m: - mode = IN_ENUM - decls = {} - idx = 0 - else: - assert False, "Invalid %s, line: %s" % (api_file, linenum) - else: - assert mode == IN_ENUM - words = re.split('[^\-a-zA-Z0-9_]+', line) - m = closebrace_pat.match(line) - if m: - name = words[1] - if name not in DeprecatedEnums: - efile = open('%s.java' % os.path.join(gendir, name), 'w') - efile.write('/**\n * Automatically generated file\n **/\n\n') - efile.write('package %s.enumerations;\n\n' % java.package_name) - - efile.write('/**\n') - efile.write(' * %s\n' % name) - efile.write(' **/\n') - efile.write('public enum %s {\n' % name) - efile.write - first = True - for k in decls: - i = decls[k] - if first: - first = False - else: - efile.write(',\n') - efile.write(' %s (%s)' % (k, i)) - efile.write(";\n") - efile.write('\n private final int intValue;\n\n') - efile.write(' %s(int v) {\n' % name) - efile.write(' this.intValue = v;\n') - efile.write(' }\n\n') - efile.write(' public static final %s fromInt(int v) {\n' % name) - efile.write(' for (%s k: values()) \n' % name) - efile.write(' if (k.intValue == v) return k;\n') - efile.write(' return values()[0];\n') - efile.write(' }\n\n') - efile.write(' public final int toInt() { return this.intValue; }\n') - # efile.write(';\n %s(int v) {}\n' % name) - efile.write('}\n\n') - efile.close() - mode = SEARCHING - else: - if words[2] != '': - if len(words[2]) > 1 and words[2][1] == 'x': - idx = int(words[2], 16) - else: - idx = int(words[2]) - decls[words[1]] = idx - idx = idx + 1 - linenum = linenum + 1 - api.close() + full_path_api_files.append(api_file) + generated_files = mk_genfile_common.mk_z3consts_java_internal( + full_path_api_files, + java.package_name, + java.src_dir) if VERBOSE: - print("Generated '%s'" % ('%s' % gendir)) + for generated_file in generated_files: + print("Generated '{}'".format(generated_file)) # Extract enumeration types from z3_api.h, and add ML definitions def mk_z3consts_ml(api_files):