From 4c13aa30a1714b1fdbebb465f4621f332e241505 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Wed, 20 Jan 2021 21:46:55 -0600 Subject: dependency: Add JDK system dependency The JDK system dependency is important for detecting JDK include paths that may be useful when developing a JNI interface. --- docs/markdown/Machine-files.md | 1 + docs/markdown/snippets/jdk-system-dependency.md | 16 +++++++ mesonbuild/dependencies/__init__.py | 5 +- mesonbuild/dependencies/dev.py | 56 ++++++++++++++++++++++ mesonbuild/envconfig.py | 12 +++-- mesonbuild/environment.py | 5 +- test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.c | 9 ++++ test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.h | 21 ++++++++ test cases/java/9 jdk/lib/meson.build | 14 ++++++ test cases/java/9 jdk/lib/native.c | 11 +++++ test cases/java/9 jdk/meson.build | 18 +++++++ .../java/9 jdk/src/com/mesonbuild/JdkTest.java | 15 ++++++ test cases/java/9 jdk/src/meson.build | 17 +++++++ 13 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 docs/markdown/snippets/jdk-system-dependency.md create mode 100644 test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.c create mode 100644 test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.h create mode 100644 test cases/java/9 jdk/lib/meson.build create mode 100644 test cases/java/9 jdk/lib/native.c create mode 100644 test cases/java/9 jdk/meson.build create mode 100644 test cases/java/9 jdk/src/com/mesonbuild/JdkTest.java create mode 100644 test cases/java/9 jdk/src/meson.build diff --git a/docs/markdown/Machine-files.md b/docs/markdown/Machine-files.md index 39e02a6..6c0b051 100644 --- a/docs/markdown/Machine-files.md +++ b/docs/markdown/Machine-files.md @@ -236,6 +236,7 @@ section. `exe_wrapper` specified in `[binaries]` to run generated executables in CMake subprojects. This setting has no effect if the `exe_wrapper` was not specified. The default value is `true`. (*new in 0.56.0*) +- `java_home` is an absolute path pointing to the root of a Java installation. ### CMake variables diff --git a/docs/markdown/snippets/jdk-system-dependency.md b/docs/markdown/snippets/jdk-system-dependency.md new file mode 100644 index 0000000..bccd16e --- /dev/null +++ b/docs/markdown/snippets/jdk-system-dependency.md @@ -0,0 +1,16 @@ +## JDK System Dependency + +When building projects such as those interacting with the JNI, you need access +to a few header files located in a Java installation. This system dependency +will add the correct include paths to your target. It assumes that either +`JAVA_HOME` will be set to a valid Java installation, or the default `javac` on +your system is a located in the `bin` directory of a Java installation. Note: +symlinks are resolved. + +```meson +jdk = dependency('jdk', version : '>=1.8') +``` + +Currently this system dependency only works on `linux`, `win32`, and `darwin`. +This can easily be extended given the correct information about your compiler +and platform in an issue. diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index afd4adc..722ede4 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -21,7 +21,9 @@ from .base import ( # noqa: F401 InternalDependency, PkgConfigDependency, CMakeDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language, DependencyFactory) -from .dev import ValgrindDependency, gmock_factory, gtest_factory, llvm_factory, zlib_factory +from .dev import ( + ValgrindDependency, JDKSystemDependency, gmock_factory, gtest_factory, + llvm_factory, zlib_factory) from .coarrays import coarray_factory from .mpi import mpi_factory from .scalapack import scalapack_factory @@ -196,6 +198,7 @@ packages.update({ 'llvm': llvm_factory, 'valgrind': ValgrindDependency, 'zlib': zlib_factory, + 'jdk': JDKSystemDependency, 'boost': BoostDependency, 'cuda': CudaDependency, diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 2ac91b1..e9181bd 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -18,6 +18,8 @@ import glob import os import re +import pathlib +import shutil import typing as T from .. import mesonlib, mlog @@ -32,6 +34,7 @@ from ..compilers.c import AppleClangCCompiler from ..compilers.cpp import AppleClangCPPCompiler if T.TYPE_CHECKING: + from ..envconfig import MachineInfo from .. environment import Environment @@ -505,6 +508,59 @@ class ZlibSystemDependency(ExternalDependency): return [DependencyMethods.SYSTEM] +class JDKSystemDependency(ExternalDependency): + def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + super().__init__('jdk', environment, kwargs) + + m = self.env.machines[self.for_machine] + + if 'java' not in environment.coredata.compilers[self.for_machine]: + environment.detect_compiler_for('java', self.for_machine) + self.javac = environment.coredata.compilers[self.for_machine]['java'] + self.version = self.javac.version + + if 'version' in kwargs and not version_compare(self.version, kwargs['version']): + mlog.error(f'Incorrect JDK version found ({self.version}), wanted {kwargs["version"]}') + self.is_found = False + return + + self.java_home = environment.properties[self.for_machine].get_java_home() + if not self.java_home: + self.java_home = pathlib.Path(shutil.which(self.javac.exelist[0])).resolve().parents[1] + + platform_include_dir = self.__machine_info_to_platform_include_dir(m) + if platform_include_dir is None: + mlog.error("Could not find a JDK platform include directory for your OS, please open an issue or provide a pull request.") + self.is_found = False + return + + java_home_include = self.java_home / 'include' + self.compile_args.append(f'-I{java_home_include}') + self.compile_args.append(f'-I{java_home_include / platform_include_dir}') + self.is_found = True + + @staticmethod + def get_methods() -> T.List[DependencyMethods]: + return [DependencyMethods.SYSTEM] + + @staticmethod + def __machine_info_to_platform_include_dir(m: 'MachineInfo') -> T.Optional[str]: + """Translates the machine information to the platform-dependent include directory + + When inspecting a JDK release tarball or $JAVA_HOME, inside the `include/` directory is a + platform dependent folder that must be on the target's include path in addition to the + parent `include/` directory. + """ + if m.is_linux(): + return 'linux' + elif m.is_windows(): + return 'win32' + elif m.is_darwin(): + return 'darwin' + + return None + + llvm_factory = DependencyFactory( 'LLVM', [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL], diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index f2792c5..c6a4df3 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -136,9 +136,9 @@ class CMakeSkipCompilerTest(Enum): class Properties: def __init__( self, - properties: T.Optional[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = None, + properties: T.Optional[T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]] = None, ): - self.properties = properties or {} # type: T.Dict[str, T.Union[str, bool, int, T.List[str]]] + self.properties = properties or {} # type: T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]] def has_stdlib(self, language: str) -> bool: return language + '_stdlib' in self.properties @@ -210,13 +210,17 @@ class Properties: assert isinstance(res, bool) return res + def get_java_home(self) -> T.Optional[Path]: + value = T.cast(T.Optional[str], self.properties.get('java_home')) + return Path(value) if value else None + def __eq__(self, other: object) -> bool: if isinstance(other, type(self)): return self.properties == other.properties return NotImplemented # TODO consider removing so Properties is less freeform - def __getitem__(self, key: str) -> T.Union[str, bool, int, T.List[str]]: + def __getitem__(self, key: str) -> T.Optional[T.Union[str, bool, int, T.List[str]]]: return self.properties[key] # TODO consider removing so Properties is less freeform @@ -224,7 +228,7 @@ class Properties: return item in self.properties # TODO consider removing, for same reasons as above - def get(self, key: str, default: T.Union[str, bool, int, T.List[str]] = None) -> T.Union[str, bool, int, T.List[str]]: + def get(self, key: str, default: T.Optional[T.Union[str, bool, int, T.List[str]]] = None) -> T.Optional[T.Union[str, bool, int, T.List[str]]]: return self.properties.get(key, default) class MachineInfo: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 54f608d..fc9b703 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -881,12 +881,13 @@ class Environment: self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env)) def _set_default_properties_from_env(self) -> None: - """Properties which can alkso be set from the environment.""" + """Properties which can also be set from the environment.""" # name, evar, split opts: T.List[T.Tuple[str, T.List[str], bool]] = [ ('boost_includedir', ['BOOST_INCLUDEDIR'], False), ('boost_librarydir', ['BOOST_LIBRARYDIR'], False), ('boost_root', ['BOOST_ROOT', 'BOOSTROOT'], True), + ('java_home', ['JAVA_HOME'], False), ] for (name, evars, split), for_machine in itertools.product(opts, MachineChoice): @@ -944,7 +945,7 @@ class Environment: def is_library(self, fname): return is_library(fname) - def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.List[str]: + def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.Optional[T.List[str]]: return self.binaries[for_machine].lookup_entry(name) @staticmethod diff --git a/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.c b/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.c new file mode 100644 index 0000000..075e37b --- /dev/null +++ b/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.c @@ -0,0 +1,9 @@ +#include + +#include "com_mesonbuild_JdkTest.h" + +JNIEXPORT jint JNICALL Java_com_mesonbuild_JdkTest_jdk_1test + (JNIEnv *env, jclass clazz) +{ + return (jint)0xdeadbeef; +} diff --git a/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.h b/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.h new file mode 100644 index 0000000..40083ac --- /dev/null +++ b/test cases/java/9 jdk/lib/com_mesonbuild_JdkTest.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_mesonbuild_JdkTest */ + +#ifndef _Included_com_mesonbuild_JdkTest +#define _Included_com_mesonbuild_JdkTest +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_mesonbuild_JdkTest + * Method: jdk_test + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_mesonbuild_JdkTest_jdk_1test + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/test cases/java/9 jdk/lib/meson.build b/test cases/java/9 jdk/lib/meson.build new file mode 100644 index 0000000..a947769 --- /dev/null +++ b/test cases/java/9 jdk/lib/meson.build @@ -0,0 +1,14 @@ +sources = files( + 'native.c', + 'com_mesonbuild_JdkTest.c', +) + +jdkjava = shared_module( + 'jdkjava', + sources, + dependencies : [jdk], +) + +jdkjava_dep = declare_dependency( + link_with : jdkjava, +) diff --git a/test cases/java/9 jdk/lib/native.c b/test cases/java/9 jdk/lib/native.c new file mode 100644 index 0000000..0b5e718 --- /dev/null +++ b/test cases/java/9 jdk/lib/native.c @@ -0,0 +1,11 @@ +#include + +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved) +{ + return JNI_VERSION_1_8; +} + +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *vm, void *reserved) +{} diff --git a/test cases/java/9 jdk/meson.build b/test cases/java/9 jdk/meson.build new file mode 100644 index 0000000..310ba5e --- /dev/null +++ b/test cases/java/9 jdk/meson.build @@ -0,0 +1,18 @@ +project('jdkjava', ['c', 'java']) + +if build_machine.system() == 'cygwin' + error('MESON_SKIP_TEST: cygwin test failures') +endif + +if build_machine.system() == 'windows' and build_machine.cpu_family() == 'x86' + error('MESON_SKIP_TEST: failing builds on 32bit Windows because a 32bit JDK isn not available in the Azure Pipelines Windows images') +endif + +fs = import('fs') + +java = find_program('java') + +jdk = dependency('jdk', version : '>=1.8') + +subdir('lib') +subdir('src') diff --git a/test cases/java/9 jdk/src/com/mesonbuild/JdkTest.java b/test cases/java/9 jdk/src/com/mesonbuild/JdkTest.java new file mode 100644 index 0000000..35c47ce --- /dev/null +++ b/test cases/java/9 jdk/src/com/mesonbuild/JdkTest.java @@ -0,0 +1,15 @@ +package com.mesonbuild; + +public final class JdkTest { + private static native int jdk_test(); + + public static void main(String[] args) { + if (jdk_test() != 0xdeadbeef) { + throw new RuntimeException("jdk_test() did not return 0"); + } + } + + static { + System.loadLibrary("jdkjava"); + } +} diff --git a/test cases/java/9 jdk/src/meson.build b/test cases/java/9 jdk/src/meson.build new file mode 100644 index 0000000..d1b9ee3 --- /dev/null +++ b/test cases/java/9 jdk/src/meson.build @@ -0,0 +1,17 @@ +jdkjar = jar( + 'jdkjar', + 'com' / 'mesonbuild' / 'JdkTest.java', + main_class : 'com.mesonbuild.JdkTest', +) + +test( + 'jdktest', + java, + args: [ + '-Djava.library.path=@0@'.format(fs.parent(jdkjava.full_path())), + '-jar', + jdkjar, + ], + protocol : 'exitcode', + depends : [jdkjava], +) -- cgit v1.1