You've already forked libadalang
mirror of
https://github.com/AdaCore/libadalang.git
synced 2026-02-12 12:28:54 -08:00
356 lines
12 KiB
Python
356 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import os.path as P
|
|
import subprocess
|
|
|
|
from e3.fs import mkdir, rm
|
|
from e3.testsuite import logger
|
|
from e3.testsuite.driver.classic import TestAbortWithFailure, TestSkip
|
|
|
|
from drivers.base_driver import BaseDriver
|
|
|
|
|
|
class JavaDriver(BaseDriver):
|
|
"""
|
|
Driver to build and run tests for the Java bindings.
|
|
"""
|
|
|
|
ni_main: str
|
|
"""
|
|
Path to the native-image pre-compiled Java test main (see the
|
|
build_ni_main method).
|
|
"""
|
|
|
|
# Template for the Java test main source
|
|
ni_main_template = """import java.util.Arrays;
|
|
:IMPORTS:
|
|
|
|
public final class NativeImageMain {
|
|
private static String[] toForward(String[] srcArgs) {
|
|
return Arrays.copyOfRange(srcArgs, 1, srcArgs.length);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
if(args.length < 1)
|
|
throw new RuntimeException("Usage: main [test_class] ...");
|
|
switch(args[0]) {
|
|
:TEST_CASES:
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
@property
|
|
def mode(self) -> str:
|
|
"""
|
|
Return the Java bindings execution mode for this test ("graal_c_api" or
|
|
"jni").
|
|
"""
|
|
result = self.test_env["mode"]
|
|
assert result in ("graal_c_api", "jni")
|
|
return result
|
|
|
|
@staticmethod
|
|
def in_java_bindings(java_bindings_dir: str, *args) -> str | None:
|
|
"""
|
|
Return the absolute path composed of parts provided in `args` from the
|
|
directory provided in `java_bindings_dir`. This function checks that
|
|
the result is an existing file, otherwise it returns `None`.
|
|
"""
|
|
bindings_dir = os.path.abspath(java_bindings_dir)
|
|
requested_file = os.path.join(bindings_dir, *args)
|
|
return requested_file if os.path.isfile(requested_file) else None
|
|
|
|
@staticmethod
|
|
def jar_file(java_bindings_dir: str) -> str | None:
|
|
"""
|
|
Return the path to Libadalang's JAR file, or None if it cannot be
|
|
found.
|
|
"""
|
|
return JavaDriver.in_java_bindings(
|
|
java_bindings_dir,
|
|
"target",
|
|
"libadalang.jar",
|
|
)
|
|
|
|
@staticmethod
|
|
def lib_jar(java_bindings_dir: str, lib_name: str) -> str | None:
|
|
"""
|
|
Return the path to the requested Java library JAR file that is located
|
|
in the tree of the provided Java bindings directory.
|
|
If the required lib file doesn't exist, this function returns None.
|
|
"""
|
|
return JavaDriver.in_java_bindings(
|
|
java_bindings_dir,
|
|
"target",
|
|
"lib",
|
|
lib_name,
|
|
)
|
|
|
|
@staticmethod
|
|
def java_exec() -> str:
|
|
"""
|
|
Return the path to the "java" executable.
|
|
"""
|
|
return os.path.realpath(os.path.join(
|
|
os.environ['JAVA_HOME'], 'bin', 'java'
|
|
))
|
|
|
|
@staticmethod
|
|
def javac_exec() -> str:
|
|
"""
|
|
Return the path to the "javac" executable.
|
|
"""
|
|
return os.path.realpath(os.path.join(
|
|
os.environ['JAVA_HOME'], 'bin', 'javac'
|
|
))
|
|
|
|
@staticmethod
|
|
def ni_compiler_exec() -> str:
|
|
"""
|
|
Return the path to the "native-image" compiler executable.
|
|
"""
|
|
return os.path.realpath(os.path.join(
|
|
os.environ['GRAAL_HOME'],
|
|
'bin',
|
|
('native-image.cmd' if os.name == 'nt' else 'native-image')
|
|
))
|
|
|
|
@classmethod
|
|
def build_ni_main(
|
|
cls,
|
|
working_dir: str,
|
|
java_bindings_dir: str,
|
|
drivers: list[JavaDriver],
|
|
) -> None:
|
|
"""
|
|
Build a single main for all the given Graal C API test.
|
|
|
|
Also set the "ni_main" attribute for all these drivers.
|
|
"""
|
|
# If there are no Graal C API tests to run, do nothing
|
|
if not drivers:
|
|
return
|
|
|
|
logger.info("Pre-compile Graal C API tests...")
|
|
|
|
# Ensure Libadalang's Java bindings are available
|
|
libadalang_jar = cls.jar_file(java_bindings_dir)
|
|
if libadalang_jar is None:
|
|
raise RuntimeError(
|
|
"Cannot find the Java bindings JAR archive, make sure you"
|
|
" built libadalang with the '--enable-java' flag"
|
|
)
|
|
|
|
# Create a build directory for the native-image build
|
|
ni_dir = os.path.join(working_dir, "_native_image_build")
|
|
ni_bin_dir = os.path.join(ni_dir, "bin")
|
|
ni_bin = os.path.join(ni_bin_dir, "main")
|
|
if os.path.isdir(ni_dir):
|
|
rm(ni_dir)
|
|
mkdir(ni_dir)
|
|
mkdir(ni_bin_dir)
|
|
|
|
# Locate the javac compiler and build a class path to give access to
|
|
# Libadalangs' Java bindings, as well as test classes compiled in
|
|
# "ni_dir".
|
|
javac_exec = cls.javac_exec()
|
|
class_path = os.pathsep.join([
|
|
libadalang_jar,
|
|
cls.lib_jar(java_bindings_dir, "langkit_support.jar"),
|
|
cls.lib_jar(java_bindings_dir, "truffle-api.jar"),
|
|
cls.lib_jar(java_bindings_dir, "polyglot.jar"),
|
|
ni_dir,
|
|
])
|
|
|
|
def javac(java_filename: str) -> None:
|
|
"""
|
|
Shortcut to compile a Java class.
|
|
"""
|
|
subprocess.check_call(
|
|
[
|
|
javac_exec,
|
|
'-cp', class_path,
|
|
'-encoding', 'utf8',
|
|
'-d', ni_dir,
|
|
java_filename,
|
|
]
|
|
)
|
|
|
|
# Populate it with the Java sources to build
|
|
import_stmts = []
|
|
case_stmts = []
|
|
for test in drivers:
|
|
test_env = test.test_env
|
|
|
|
# Copy the test Java source file and add a "package" statement
|
|
# at the top of it.
|
|
main_class = test_env.get('main_class', 'Main')
|
|
src_java_file_name = os.path.join(
|
|
test_env['test_dir'],
|
|
test_env.get('java_path', '.'),
|
|
f"{main_class}.java"
|
|
)
|
|
java_file_name = os.path.join(ni_dir, f"{main_class}.java")
|
|
with open(src_java_file_name) as f:
|
|
contents = f.read()
|
|
with open(java_file_name, 'w') as f:
|
|
print("package tests;", file=f)
|
|
f.write(contents)
|
|
|
|
# Compile the test Java file
|
|
javac(java_file_name)
|
|
|
|
# Add the test class to the native-image main Java content
|
|
import_stmts.append(f"import tests.{main_class};")
|
|
case_stmts.append(f'case "{main_class}": {main_class}.main'
|
|
'(toForward(args)); break;')
|
|
|
|
# Communicate the path to the final native-image executable to
|
|
# test drivers.
|
|
test.ni_main = ni_bin
|
|
|
|
# Create the native-image Java main file content, write it and
|
|
# compile it.
|
|
ni_main_content = cls.ni_main_template.replace(
|
|
':IMPORTS:',
|
|
'\n'.join(import_stmts)
|
|
).replace(
|
|
':TEST_CASES:',
|
|
'\n'.join(case_stmts)
|
|
)
|
|
ni_main_file_name = os.path.join(ni_dir, "NativeImageMain.java")
|
|
with open(ni_main_file_name, 'w') as f:
|
|
print(ni_main_content, file=f)
|
|
javac(ni_main_file_name)
|
|
|
|
os_specific_options = []
|
|
if os.name != "nt":
|
|
def find_file_in_env(file_name, env_var) -> str | None:
|
|
for dir in os.environ.get(env_var, "").split(os.pathsep):
|
|
if os.path.isfile(os.path.join(dir, file_name)):
|
|
return dir
|
|
return None
|
|
|
|
# Find the directory that contains the "libadalang.h" header file
|
|
header_dir = find_file_in_env("libadalang.h", "C_INCLUDE_PATH")
|
|
assert header_dir is not None
|
|
|
|
# Find the directory that contains the LAL shared library
|
|
lib_dirs = [
|
|
dir for dir in [
|
|
find_file_in_env("libadalang.so", "LIBRARY_PATH"),
|
|
find_file_in_env("libz.so", "LIBRARY_PATH"),
|
|
]
|
|
if dir is not None
|
|
]
|
|
|
|
# We also need to provide rpath-links to the compiler to allow it
|
|
# to find libraries during linking phase.
|
|
ld_library_path = os.environ.get('LD_LIBRARY_PATH')
|
|
rpaths = (
|
|
[
|
|
f"-Wl,-rpath-link={p}"
|
|
for p in ld_library_path.split(os.pathsep)
|
|
]
|
|
if ld_library_path else
|
|
[]
|
|
)
|
|
|
|
# Create native-image options to provide required information when
|
|
# spawning GCC.
|
|
os_specific_options.extend([
|
|
f"--native-compiler-options=-I{header_dir}",
|
|
*[
|
|
f"--native-compiler-options=-L{lib_dir}"
|
|
for lib_dir in lib_dirs
|
|
],
|
|
*[f"--native-compiler-options={rp}" for rp in rpaths],
|
|
])
|
|
else:
|
|
# Ensure the compiler isn't emitting warnings about CPU features
|
|
os_specific_options.append("-march=native")
|
|
|
|
# Run the native-image compiler
|
|
subprocess.check_call([
|
|
cls.ni_compiler_exec(),
|
|
'-cp', class_path,
|
|
'--no-fallback',
|
|
"-Ob",
|
|
"--silent",
|
|
"--enable-native-access=org.graalvm.truffle",
|
|
"-J--sun-misc-unsafe-memory-access=allow",
|
|
"-H:+UnlockExperimentalVMOptions",
|
|
"-H:-StrictQueryCodeCompilation",
|
|
*os_specific_options,
|
|
'NativeImageMain',
|
|
ni_bin,
|
|
])
|
|
|
|
logger.info("Done")
|
|
|
|
def run(self):
|
|
# Verify the Java bindings directory
|
|
bindings_dir = self.env.java_bindings
|
|
if bindings_dir is None:
|
|
raise TestSkip('Test requires Java bindings')
|
|
|
|
# Verify that the libadalang bindings are compiled
|
|
libadalang_jar = self.jar_file(bindings_dir)
|
|
if libadalang_jar is None:
|
|
raise TestAbortWithFailure(
|
|
"Cannot find the Java bindings JAR archive, make sure you"
|
|
" built libadalang with the '--enable-java' flag"
|
|
)
|
|
|
|
# Get the class name and source file for the Java main
|
|
main_class = self.test_env.get("main_class", "Main")
|
|
main_java = self.test_dir(
|
|
self.test_env.get("java_path", "."), f"{main_class}.java"
|
|
)
|
|
|
|
# Get the project path
|
|
project_path = self.test_dir(self.test_env.get("projects_path", "."))
|
|
|
|
if self.mode == 'graal_c_api':
|
|
# Run the pre-compiled native-image produced executable with the
|
|
# main Java class as first argument.
|
|
assert self.ni_main, (
|
|
"Test driver cannot access the pre-compiled Graal C API tests"
|
|
)
|
|
args = [self.ni_main, main_class]
|
|
|
|
else:
|
|
# Run the Java main class as Java program
|
|
|
|
# Give access to the Libadalang Java bindings and to the tests'
|
|
# Java sources.
|
|
class_path = P.pathsep.join([
|
|
libadalang_jar,
|
|
self.lib_jar(bindings_dir, "langkit_support.jar"),
|
|
self.lib_jar(bindings_dir, "truffle-api.jar"),
|
|
self.test_env['working_dir']
|
|
])
|
|
|
|
# Get the java.library.path from LD_LIBRARY_PATH and compiled JNI
|
|
# stubs.
|
|
java_library_path = P.pathsep.join(
|
|
[P.join(bindings_dir, 'jni'), os.environ['LD_LIBRARY_PATH']]
|
|
)
|
|
|
|
# Prepare the command to run the Java main
|
|
args = [
|
|
self.java_exec(),
|
|
'-cp', class_path,
|
|
'-Dfile.encoding=UTF-8',
|
|
# Enable the Java application to load and use native libraries
|
|
"--enable-native-access=ALL-UNNAMED",
|
|
f"-Djava.library.path={java_library_path}",
|
|
]
|
|
args.append(main_java)
|
|
|
|
# Run the test main. Mains expect a GPR project path as their only
|
|
# argument.
|
|
self.run_and_check(args + [project_path])
|