diff --git a/espresso-shared/mx.espresso-shared/suite.py b/espresso-shared/mx.espresso-shared/suite.py index e79cdb4e486e..d774d92df33d 100644 --- a/espresso-shared/mx.espresso-shared/suite.py +++ b/espresso-shared/mx.espresso-shared/suite.py @@ -40,6 +40,7 @@ "read" : "https://github.com/oracle/graal.git", "write" : "git@github.com:oracle/graal.git", }, + "ignore_suite_commit_info": True, # ------------- licenses diff --git a/espresso/.gitignore b/espresso/.gitignore index 80ec5a8e4230..c0fcf140fbcf 100644 --- a/espresso/.gitignore +++ b/espresso/.gitignore @@ -46,15 +46,6 @@ *.idb *.pdb -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - ### Eclipse ### .metadata diff --git a/espresso/docs/hacking.md b/espresso/docs/hacking.md index 72f976ae05d5..708d4614dc77 100644 --- a/espresso/docs/hacking.md +++ b/espresso/docs/hacking.md @@ -49,6 +49,7 @@ You can find out where the espresso standalones are by running `mx path --output $ mx path --output ESPRESSO_NATIVE_STANDALONE $ mx path --output ESPRESSO_JVM_STANDALONE ``` +> Note: If you used options like `--env ...` or `--dynamicimports ...` while building, you should also use them with `mx path`: e.g., `mx --env native-ce path ...`. `mx espresso` runs Espresso from a standalone (jvm or native). It mimics the `java` command. @@ -68,7 +69,7 @@ The `mx espresso` launcher adds some overhead, to execute Espresso native image ```bash $ mx --env native-ce build # Always build first -$ export ESPRESSO=`mx path --output ESPRESSO_NATIVE_STANDALONE`/bin/java +$ export ESPRESSO=`mx --quiet --no-warning --env native-ce path --output ESPRESSO_NATIVE_STANDALONE`/bin/java $ time $ESPRESSO -cp my.jar HelloWorld ``` diff --git a/espresso/mx.espresso/launchers/java.cmd b/espresso/mx.espresso/launchers/java.cmd new file mode 100644 index 000000000000..7f144454a312 --- /dev/null +++ b/espresso/mx.espresso/launchers/java.cmd @@ -0,0 +1,48 @@ +:: +:: ---------------------------------------------------------------------------------------------------- +:: +:: Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. +:: DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +:: +:: This code is free software; you can redistribute it and/or modify it +:: under the terms of the GNU General Public License version 2 only, as +:: published by the Free Software Foundation. Oracle designates this +:: particular file as subject to the "Classpath" exception as provided +:: by Oracle in the LICENSE file that accompanied this code. +:: +:: This code is distributed in the hope that it will be useful, but WITHOUT +:: ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +:: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +:: version 2 for more details (a copy is included in the LICENSE file that +:: accompanied this code). +:: +:: You should have received a copy of the GNU General Public License version +:: 2 along with this work; if not, write to the Free Software Foundation, +:: Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +:: +:: Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +:: or visit www.oracle.com if you need additional information or have any +:: questions. +:: +:: ---------------------------------------------------------------------------------------------------- +@echo off + +setlocal enabledelayedexpansion + +call :getScriptLocation location + + +"%location%\espresso" %* + +exit /b %errorlevel% +:: Function are defined via labels, so have to be defined at the end of the file and skipped +:: in order not to be executed. + +:: If this script is in `%PATH%` and called quoted without a full path (e.g., `"java"`), `%~dp0` is expanded to `cwd` +:: rather than the path to the script. +:: This does not happen if `%~dp0` is accessed in a subroutine. +:getScriptLocation variableName + set "%~1=%~dp0" + exit /b 0 + +endlocal diff --git a/espresso/mx.espresso/launchers/java.sh b/espresso/mx.espresso/launchers/java.sh new file mode 100755 index 000000000000..4af0cacbcb2f --- /dev/null +++ b/espresso/mx.espresso/launchers/java.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +source="${BASH_SOURCE[0]}" +while [ -h "$source" ] ; do + prev_source="$source" + source="$(readlink "$source")"; + if [[ "$source" != /* ]]; then + # if the link was relative, it was relative to where it came from + dir="$( cd -P "$( dirname "$prev_source" )" && pwd )" + source="$dir/$source" + fi +done +location="$( cd -P "$( dirname "$source" )" && pwd )" +exec "${location}/espresso" "${@}" \ No newline at end of file diff --git a/espresso/mx.espresso/launchers/javac.cmd b/espresso/mx.espresso/launchers/javac.cmd new file mode 100644 index 000000000000..cb29324424db --- /dev/null +++ b/espresso/mx.espresso/launchers/javac.cmd @@ -0,0 +1,153 @@ +:: +:: ---------------------------------------------------------------------------------------------------- +:: +:: Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. +:: DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +:: +:: This code is free software; you can redistribute it and/or modify it +:: under the terms of the GNU General Public License version 2 only, as +:: published by the Free Software Foundation. Oracle designates this +:: particular file as subject to the "Classpath" exception as provided +:: by Oracle in the LICENSE file that accompanied this code. +:: +:: This code is distributed in the hope that it will be useful, but WITHOUT +:: ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +:: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +:: version 2 for more details (a copy is included in the LICENSE file that +:: accompanied this code). +:: +:: You should have received a copy of the GNU General Public License version +:: 2 along with this work; if not, write to the Free Software Foundation, +:: Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +:: +:: Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +:: or visit www.oracle.com if you need additional information or have any +:: questions. +:: +:: ---------------------------------------------------------------------------------------------------- +@echo off + +setlocal enabledelayedexpansion + +call :getScriptLocation location + + +:: The two white lines above this comment are significant. + +set "jvm_args=--add-modules=ALL-DEFAULT" +set "javac_args=" + +call :escape_args %* +for %%a in (%args%) do ( + call :unescape_arg %%a + call :process_arg !arg! + if errorlevel 1 exit /b 1 +) + +if "%VERBOSE_GRAALVM_LAUNCHERS%"=="true" echo on + +"%location%\espresso" %jvm_args% -m jdk.compiler/com.sun.tools.javac.Main %javac_args% + +exit /b %errorlevel% +:: Function are defined via labels, so have to be defined at the end of the file and skipped +:: in order not to be executed. + +:escape_args + set "args=%*" + :: Without early exit on empty contents, substitutions fail. + if "!args!"=="" exit /b 0 + set "args=%args:,=##GR_ESC_COMMA##%" + set "args=%args:;=##GR_ESC_SEMI##%" + :: Temporarily, so that args are split on '=' only. + set "args=%args: =##GR_ESC_SPACE##%" + :: Temporarily, otherwise we won't split on '=' inside quotes. + set "args=%args:"=##GR_ESC_QUOTE##%" + :: We can't replace equal using the variable substitution syntax. + call :replace_equals %args% + set "args=%args:##GR_ESC_SPACE##= %" + set "args=%args:##GR_ESC_QUOTE##="%" + exit /b 0 + +:replace_equals + setlocal + :: The argument passed to this function was split on =, because all other + :: delimiters were replaced in escape_args. + set "arg=%1" + if "!arg!"=="" goto :end_replace_equals + set "args=%1" + shift + :loop_replace_equals + set "arg=%1" + if "!arg!"=="" goto :end_replace_equals + set "args=%args%##GR_ESC_EQUAL##%arg%" + shift + goto :loop_replace_equals + :end_replace_equals + endlocal & ( set "args=%args%" ) + exit /b 0 + +:unescape_arg + set "arg=%*" + set "arg=%arg:##GR_ESC_COMMA##=,%" + set "arg=%arg:##GR_ESC_SEMI##=;%" + set "arg=%arg:##GR_ESC_EQUAL##==%" + exit /b 0 + +:is_quoted + setlocal + set "args=%*" + set /a argslen=0 + for %%a in (%args%) do set /a argslen+=1 + if %argslen% gtr 1 ( + set "quoted=false" + ) else ( + if "!args:~0,1!!args:~-1!"=="""" ( set "quoted=true" ) else ( set "quoted=false" ) + ) + endlocal & ( set "quoted=%quoted%" ) + exit /b 0 + +:unquote_arg + :: Sets %arg% to a version of the argument with outer quotes stripped, if present. + call :is_quoted %* + setlocal + set "maybe_quoted=%*" + if %quoted%==true ( set "arg=%~1" ) else ( set "arg=!maybe_quoted!" ) + endlocal & ( set "arg=%arg%" ) + exit /b 0 + +:process_vm_arg + if %arg_quoted%==false ( + call :is_quoted %* + set "arg_quoted=%quoted%" + ) + call :unquote_arg %* + set "vm_arg=%arg%" + + if %arg_quoted%==true ( set "arg="%vm_arg%"" ) else ( set "arg=%vm_arg%" ) + set "jvm_args=%jvm_args% !arg!" + exit /b 0 + +:process_arg + set "original_arg=%*" + call :unquote_arg !original_arg! + set "arg_quoted=%quoted%" + + if "!arg:~0,2!"=="-J" ( + set prefix=vm + call :unquote_arg !arg:~2! + call :process_vm_arg !arg! + if errorlevel 1 exit /b 1 + ) else ( + :: Use !original_arg! instead of !arg! to preserve surrounding quotes if present. + set "launcher_args=%launcher_args% !original_arg!" + ) + exit /b 0 + +:: If this script is in `%PATH%` and called quoted without a full path (e.g., `"java"`), `%~dp0` is expanded to `cwd` +:: rather than the path to the script. +:: This does not happen if `%~dp0` is accessed in a subroutine. +:getScriptLocation variableName + set "%~1=%~dp0" + exit /b 0 + +endlocal diff --git a/espresso/mx.espresso/launchers/javac.sh b/espresso/mx.espresso/launchers/javac.sh new file mode 100755 index 000000000000..b3533249220b --- /dev/null +++ b/espresso/mx.espresso/launchers/javac.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +source="${BASH_SOURCE[0]}" +while [ -h "$source" ] ; do + prev_source="$source" + source="$(readlink "$source")"; + if [[ "$source" != /* ]]; then + # if the link was relative, it was relative to where it came from + dir="$( cd -P "$( dirname "$prev_source" )" && pwd )" + source="$dir/$source" + fi +done +location="$( cd -P "$( dirname "$source" )" && pwd )" + +jvm_args=("--add-modules=ALL-DEFAULT") +javac_args=() + +process_arg() { + if [[ "$1" == -J* ]]; then + jvm_args+=("${1#-J}") + else + launcher_args+=("$1") + fi +} + +for o in "$@"; do + process_arg "$o" +done + +if [[ "${VERBOSE_GRAALVM_LAUNCHERS}" == "true" ]]; then + set -x +fi + +exec "${location}/espresso" "${jvm_args[@]}" -m jdk.compiler/com.sun.tools.javac.Main "${javac_args[@]}" \ No newline at end of file diff --git a/espresso/mx.espresso/mx_espresso.py b/espresso/mx.espresso/mx_espresso.py index cb8a86bb31f3..25e263c2d13d 100644 --- a/espresso/mx.espresso/mx_espresso.py +++ b/espresso/mx.espresso/mx_espresso.py @@ -95,7 +95,7 @@ def _espresso_standalone_command(args, with_sulong=False, allow_jacoco=True, jdk + jacoco_args # This is needed for Truffle since JEP 472: Prepare to Restrict the Use of JNI + ['--enable-native-access=org.graalvm.truffle'] - + [mx.distribution('ESPRESSO_LAUNCHER').mainClass] + args + + ["--module", "org.graalvm.espresso.launcher/" + mx.distribution('ESPRESSO_LAUNCHER').mainClass] + args ) def javavm_deps(): diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index 74611b590e81..63a794f2e05c 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -575,19 +575,45 @@ "maven": False, }, + "ESPRESSO_JVM_STANDALONE_JAVA_LINKS": { + "type": "dir", + "platformDependent": True, + "platforms": "local", + "os": { + "windows": { + "layout": { + "bin/java.cmd": "file:mx.espresso/launchers/java.cmd", + "bin/javac.cmd": "file:mx.espresso/launchers/javac.cmd", + }, + }, + "": { + "layout": { + "bin/java": "file:mx.espresso/launchers/java.sh", + "bin/javac": "file:mx.espresso/launchers/javac.sh", + }, + }, + }, + "maven": False, + }, + "ESPRESSO_JVM_STANDALONE": { "type": "dir", "pruning_mode": "optional", "description": "Espresso JVM standalone distribution for testing", "platformDependent": True, "platforms": "local", + "defaultDereference": "never", "layout": { - "bin/": ["dependency:espresso:espresso"], + "bin/": [ + "dependency:espresso:espresso", + "dependency:ESPRESSO_JVM_STANDALONE_JAVA_LINKS/bin/*", + ], "./": [{ "source_type": "dependency", "dependency": "espresso:JAVA_HOME", "path": "*", "exclude": [ + "bin", # those can't run without /server "lib/jfr", "lib/static", "/server", diff --git a/sdk/src/org.graalvm.launcher.native/src/launcher.cc b/sdk/src/org.graalvm.launcher.native/src/launcher.cc index 1bd053229aa0..5710bae01d73 100644 --- a/sdk/src/org.graalvm.launcher.native/src/launcher.cc +++ b/sdk/src/org.graalvm.launcher.native/src/launcher.cc @@ -47,10 +47,14 @@ #include #include +#include #include #include +#include #include +#include + #define QUOTE(name) #name #define STR(macro) QUOTE(macro) @@ -94,6 +98,7 @@ #define VM_MODULE_PATH_ARG_PREFIX "--vm.-module-path=" #define VM_LIBRARY_PATH_ARG_PREFIX "--vm.Djava.library.path=" #define VM_STACK_SIZE_ARG_PREFIX "--vm.Xss" +#define VM_ARG_FILE_ARG_PREFIX "--vm.@" #define VM_ARG_OFFSET (sizeof(VM_ARG_PREFIX)-1) #define VM_CP_ARG_OFFSET (sizeof(VM_CP_ARG_PREFIX)-1) @@ -102,6 +107,7 @@ #define VM_MODULE_PATH_ARG_OFFSET (sizeof(VM_MODULE_PATH_ARG_PREFIX)-1) #define VM_LIBRARY_PATH_ARG_OFFSET (sizeof(VM_LIBRARY_PATH_ARG_PREFIX)-1) #define VM_STACK_SIZE_ARG_OFFSET (sizeof(VM_STACK_SIZE_ARG_PREFIX)-1) +#define VM_ARG_FILE_ARG_OFFSET (sizeof(VM_ARG_FILE_ARG_PREFIX)-1) #define STARTS_WITH(ARG, PREFIX) (ARG.rfind(PREFIX, 0) != std::string::npos) #define IS_VM_ARG(ARG) STARTS_WITH(ARG, VM_ARG_PREFIX) @@ -111,6 +117,7 @@ #define IS_VM_MODULE_PATH_ARG(ARG) STARTS_WITH(ARG, VM_MODULE_PATH_ARG_PREFIX) #define IS_VM_LIBRARY_PATH_ARG(ARG) STARTS_WITH(ARG, VM_LIBRARY_PATH_ARG_PREFIX) #define IS_VM_STACK_SIZE_ARG(ARG) STARTS_WITH(ARG, VM_STACK_SIZE_ARG_PREFIX) +#define IS_VM_ARG_FILE_ARG(ARG) STARTS_WITH(ARG, VM_ARG_FILE_ARG_PREFIX) #define NMT_ARG_NAME "XX:NativeMemoryTracking" #define NMT_ENV_NAME "NMT_LEVEL_" @@ -345,6 +352,12 @@ static std::string vm_path(std::string exeDir, bool jvmMode) { } static size_t parse_size(std::string_view str); +static void expand_vm_arg_file(const char *arg_file, + std::vector *vmArgs, + std::ostringstream *cp, + std::ostringstream *modulePath, + std::ostringstream *libraryPath, + size_t* stack_size); static void parse_vm_option( std::vector *vmArgs, @@ -363,6 +376,9 @@ static void parse_vm_option( *modulePath << CP_SEP_STR << option.substr(VM_MODULE_PATH_ARG_OFFSET); } else if (IS_VM_LIBRARY_PATH_ARG(option)) { *libraryPath << CP_SEP_STR << option.substr(VM_LIBRARY_PATH_ARG_OFFSET); + } else if (IS_VM_ARG_FILE_ARG(option)) { + std::string arg_file(option.substr(VM_ARG_FILE_ARG_OFFSET)); + expand_vm_arg_file(arg_file.c_str(), vmArgs, cp, modulePath, libraryPath, stack_size); } else if (IS_VM_ARG(option)) { if (IS_VM_STACK_SIZE_ARG(option)) { *stack_size = parse_size(option.substr(VM_STACK_SIZE_ARG_OFFSET)); @@ -375,6 +391,163 @@ static void parse_vm_option( } } +enum ArgFileState { + FIND_NEXT, + IN_COMMENT, + IN_QUOTE, + IN_ESCAPE, + SKIP_LEAD_WS, + IN_TOKEN +}; +// Parse @arg-files as handled by libjli. See libjli/args.c. +static std::optional arg_file_next_token(std::ifstream &input) { + ArgFileState state = FIND_NEXT; + int currentQuoteChar = -1; + std::istream::int_type ch; + + std::ostringstream token; + std::ostringstream::pos_type start = token.tellp(); + + while ((ch = input.get()) != std::istream::traits_type::eof()) { + // Skip white space characters + if (state == FIND_NEXT || state == SKIP_LEAD_WS) { + while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { + if ((ch = input.get()) == std::istream::traits_type::eof()) { + goto done; + } + } + state = (state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE; + // Deal with escape sequences + } else if (state == IN_ESCAPE) { + // concatenation directive + if (ch == '\n' || ch == '\r') { + state = SKIP_LEAD_WS; + } else { + // escaped character + switch (ch) { + case 'n': + token << '\n'; + break; + case 'r': + token << '\r'; + break; + case 't': + token << '\t'; + break; + case 'f': + token << '\f'; + break; + default: + token << (char) ch; + break; + } + state = IN_QUOTE; + } + continue; + // ignore comment to EOL + } else if (state == IN_COMMENT) { + while (ch != '\n' && ch != '\r') { + if ((ch = input.get()) == std::istream::traits_type::eof()) { + goto done; + } + } + state = FIND_NEXT; + continue; + } + + assert(state != IN_ESCAPE); + assert(state != FIND_NEXT); + assert(state != SKIP_LEAD_WS); + assert(state != IN_COMMENT); + + switch (ch) { + case ' ': + case '\t': + case '\f': + if (state == IN_QUOTE) { + token << (char) ch; + continue; + } + // fallthrough + case '\n': + case '\r': + return token.str(); + case '#': + if (state == IN_QUOTE) { + token << (char) ch; + continue; + } + state = IN_COMMENT; + break; + case '\\': + if (state != IN_QUOTE) { + token << (char) ch; + continue; + } + state = IN_ESCAPE; + break; + case '\'': + case '"': + if (state == IN_QUOTE && currentQuoteChar != ch) { + // not matching quote + token << (char) ch; + continue; + } + if (state == IN_TOKEN) { + currentQuoteChar = ch; + state = IN_QUOTE; + } else { + state = IN_TOKEN; + } + break; + default: + token << (char) ch; + break; + } + } +done: + if (token.tellp() == start) { + return {}; + } + return token.str(); +} + +static void expand_vm_arg_file(const char *arg_file, + std::vector *vmArgs, + std::ostringstream *cp, + std::ostringstream *modulePath, + std::ostringstream *libraryPath, + size_t* stack_size) { + if (debug) { + std::cout << "Expanding VM arg file " << arg_file << std::endl; + } + std::ifstream input(arg_file); + if (input.fail()) { + std::cerr << "Error: could not open `" << arg_file << "': " << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + + while (true) { + std::optional token = arg_file_next_token(input); + if (token.has_value()) { + if (STARTS_WITH(token.value(), "--class-path=")) { + *cp << CP_SEP_STR << token.value().substr(sizeof("--class-path=") - 1); + } else if (STARTS_WITH(token.value(), "--module-path=")) { + *modulePath << CP_SEP_STR << token.value().substr(sizeof("--module-path=") - 1); + } else if (STARTS_WITH(token.value(), "-Djava.library.path=")) { + *libraryPath << CP_SEP_STR << token.value().substr(sizeof("-Djava.library.path=") - 1); + } else { + if (STARTS_WITH(token.value(), "-Xss")) { + *stack_size = parse_size(token.value().substr(sizeof("-Xss") - 1)); + } + vmArgs->push_back(token.value()); + } + } else { + break; + } + } +} + struct MainThreadArgs { int argc; char **argv;